Kubernetes 教學系列 - 如何建立 Pod

這次來介紹在 K8S 究竟如何建立 Pod,而 Pod 在 K8S 上是什麼功用可以參考我寫的文章:Kubernetes 元件原理介紹。Pod 是節點裡面的最小單位,簡單來說就是 Pod 就是放置容器服務的地方。通常一個 Pod 對應到一個容器,也可以一個 Pod 裡面放置多個容器。因此如何建立 Pod 是要先建立我們的 Container。

建立 Container

建立 Container 的方式我們選擇使用 Docker,事實上只要是 Container-based 的應用,K8S 的 Pod 可以佈署,而不是只有 Docker 出產的 Container 才可以使用,只是因為 Docker 實在是太方便了。而有關 Docker 的詳細介紹這邊就不詳談了。這次的應用我們用 Golang 來示範。

Golang 程式碼

直接上程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
router := gin.Default()
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
router.Run()
}

這邊利用 gin 框架來實現 web service,而我們簡單定義一個 GET API,用來之後看我們的 Container 是否有被成功建立。

Dockerfile 撰寫

1
2
3
4
5
FROM golang:1.13
WORKDIR /web-app
ADD . /web-app
RUN cd /web-app && go build -o web-app
ENTRYPOINT ["./web-app"]

接下來可以 build 我們 Container 所需要的 Image

1
docker build -t web-app .

試著運行在 Local 運行 Container,並且將其 web-app 的 port 導引到 local 的 8080 port,並看是否可以正常運作:

1
docker run -p 8080:8080 -it web-app

如果 OK 的話會看到 Gin 框架的 debug log 輸出:

這時候利用 curl 工具來存取 /health API 試試看是否可以 JSON Response:

1
curl http://localhost:8080/health

正常的話會看到:

1
{"status":"ok"}

將 Image Push 到 Container Registry

現在 Container 運行正常了,但是我們需要將 Container 放置在 Container Registry 上,由於架設 K8S 我要採用的方式是使用 Minikube 來架設,但是 Minikube 本身也有提供 Container Registry,因此我們可以將 Image 推到 Minikube 裡面也可以推到 DockerHub 上。

推到 DockerHub

1
2
3
4
# 先將Image打上tag => account/image_name:version
docker tag web-app kennychenfight/web-app:latest
# 接著push到DockerHub
docker push kennychenfight/web-app

推到 Minikube

基本上這個原理就是,因為 Minikube 裡面也有一個 Docker,因此就是透過在 Minikube 的 Docker 將 local 的 Image Build 出來放置在 Minikube Container Registry。

先執行這個指令:

1
2
3
4
5
6
7
8
minikube docker-env
# 可以看到如下的內容,內容會根據你的OS而產生不同的環境變數設計,基本上它就是將我們在Local端的Docker換成Minikube內的Docker Daemon
$Env:DOCKER_TLS_VERIFY = "1"
$Env:DOCKER_HOST = "tcp://127.0.0.1:32770"
$Env:DOCKER_CERT_PATH = "C:\Users\kenny\.minikube\certs"
$Env:MINIKUBE_ACTIVE_DOCKERD = "minikube"
# To point your shell to minikube's docker-daemon, run:
# & minikube -p minikube docker-env | Invoke-Expression

接著可以看到會提示我們執行怎樣的指令可以做替換:

1
& minikube -p minikube docker-env | Invoke-Expression

接著 Local 端的 Docker 就變成是在 Minikube 內的 Docker 了,然後在 Local 上重新 Build Image 即可。

建立 Pod 設定檔

現在,Image 有了,Container run 起來也能夠正常運行了,就差 Pod 設定檔了。對於 K8S 而言,我們如果要 Deploy Pod,都需要撰寫 Pod 的設定檔。設定檔的格式是採用 yml 的方式,所以撰寫起來算是很容易理解,直接上 Pod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: web-app-pod
labels:
app: web-app
annotations:
builder: kennychen
spec:
containers:
- name: web-app
image: kennychenfight/web-app
ports:
- containerPort: 8080
  • apiVersion
    apiVersion 是代表目前 Kubernetes 中該元件的版本號。

  • metadata
    最主要可以拿來定義以下三個欄位:

    • metadata.name
      代表這個 Pod 的名稱

    • metadata.labels

      這個 Label 下面可以以 Key Value 的方式來自定義值,為什麼需要這個?這是因為 K8S 可以透過 Label Selector 將 Pod 進行分 Group,如此一來我們就可以對某 Group 進行某特定指令的效果。因此通常會定義一個 Pod 裡面裝的 Container 的名字之類的來做識別。

    • metadata. annotations
      annotations 也是跟 Label 一樣可以以 Key Value 的方式來自定義值,通常會定義一些 Build, release, or image information like timestamps, release IDs, git branch, PR numbers, image hashes, and registry address 等等,直接抄官方說法 XD,因為這些訊息可以供外部進行查詢使用。

  • spec

前面都只是定義 Pod 相關資訊而已,最後 spec 的部分才是定義 Pod 裡面的 container 從哪裡來等設定。可以注意到是用 containers 因此這邊其實更可以知道一個 Pod 可以設定多個 Container 的。

  • container.name
    設定 container 的名稱
  • container.image
    這是代表 image 的路徑,如果是在 DockerHub 上的話就寫如上面那樣即可。
  • container.ports
    ports 的部分可以設定 containerPort,這個代表是這個 container 只能開放哪個 port 來讓外部資源存取。所以根據程式碼是 listen 8080 port,所以這邊就是定義 8080 port。

在 K8S 上建立 Pod

在 K8S 上不管是建立甚麼資源通常都會使用兩種指令:

1
2
kubectl apply -f pod.yaml
kubectl create -f pod.yaml

通常第一次建立資源的時候會使用 create 指令,而在之後如果有更改資源的設定檔想要更新的話就會採用 apply,而如果重複使用 create 的話在第二次會報錯誤,apply 則是會建立或更新的操作。

這邊先透過 Minikube 建立起 K8S cluster 環境:

1
minikube start

現在的 minikube 建立 cluster 的方式有三種,分別是 hyper,virtualbox,docker,如果沒有在 start 這邊特定指定 **–driver** 的話會預設採用 docker 來做建立,所以本篇文章是透過 Docker 建立起 K8S cluster 環境。

接著就可以先透過 create 來產生 Pod:

1
kubectl create -f pod.yaml

接著來看是否有被成功建立:

這樣會取得所有的 pods

1
kubectl get pods

如果運作正常就會看到以下的結果:

1
2
NAME          READY   STATUS    RESTARTS   AGE
web-app-pod 1/1 Running 0 54s

可以用 kubectl describe 指令可以看到更多關於 web-app-pod 這個物件的資訊:

1
kubectl describe pod web-app-pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Name:         web-app-pod
Namespace: default
Priority: 0
Node: minikube/192.168.49.2
Start Time: Wed, 16 Dec 2020 15:04:36 +0800
Labels: app=web-app
Annotations: <none>
Status: Running
IP: 172.17.0.3
IPs:
IP: 172.17.0.3
Containers:
web-app:
Container ID: docker://ec1e1e8bc942167dbd1d74b226ec084fed568b29d6dccccc687d4cdfa781fd48
Image: kennychenfight/web-app
Image ID: docker-pullable://kennychenfight/web-app@sha256:19e73df433fefa29ed2e19a9076d452a6fd2506b203f3fb0868ea5401a64003f
Port: 8080/TCP
Host Port: 0/TCP
State: Running
Started: Wed, 16 Dec 2020 15:05:27 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-4pvd6 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-4pvd6:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-4pvd6
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m5s default-scheduler Successfully assigned default/web-app-pod to minikube
Normal Pulling 3m4s kubelet, minikube Pulling image "kennychenfight/web-app"
Normal Pulled 2m15s kubelet, minikube Successfully pulled image "kennychenfight/web-app" in 49.5584472s
Normal Created 2m14s kubelet, minikube Created container web-app
Normal Started 2m14s kubelet, minikube Started container web-app

這邊看到的資訊有些也是對應 pod 設定檔而出現相對應的值,例如 Name,Labels,Annotations,Containers 等等,而在下方的 Events 可以看到當建立 pod 的時候會執行怎樣的操作,拉 image,建立 Container 等行為,如果有錯誤的話也會顯示在這邊。

如何與 Pod 中的 container 互動

現在 pod 內的 Container 已經被成功起起來了,根據我們的 Code,是開啟 listen 8080 port web-server,但是由於 Pod 是被包在 Cluster 裡面的 Node,以 Minikube 來說,在這個 K8S cluster 只有一個 Node,而 Node 放著我們剛剛建立出來的 Pod,但是外部是沒辦法直接與 Pod 中的 Container 互動的。

使用 kubectl port-forward

port-forward 的指令可以讓 pod 中的某個 port number 與 Local 端的 port 做映射,讓我們存取 Local 端的 port 的時候就能將流量導到 pod 中。

1
kubectl port-forward web-app-pod 8080:8080

指定 pod 名稱,前面為 Local port,後面為 pod 內的 port。會出現以下的內容即成功~

1
2
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

接著我們透過 curl 工具來存取 localhost:8080/health 試試看:

1
curl http://localhost:8080/health

沒意外的話會得到 {"status": "ok"},這樣就能成功存取到。

透過在 K8S 上建立 Service

可以直接透過 kubectl expose 指令在 K8S 上建立一個 Service。當然也是可以撰寫 Service 設定檔來達到這件事情,這在之後的文章會詳談 Service 的功用,可以將 Service 想成是負責將 pod 做對外連線的資源。

1
kubectl expose pod web-app-pod --type=NodePort --name=web-app-service

然後再透過 kubectl get services,看是否有成功建立 Service:

1
kubectl get services
1
2
3
NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19m
web-app-service NodePort 10.101.196.177 <none> 8080:31014/TCP 5s

其實這邊就可以看的出來,指令 --type=NodePort 的功用就是會在 Node 上產生隨機 port,這邊是 31014 與 pod port 8080 做對應。可是要記住的是 Node 本身在 Minikube 是一台虛擬機,所以他只是將 Cluster 這個 Node 虛擬機與 pod port 做映射,而現在的問題是如果去存取 Node 的 port,外部才可以連線。

可以透過 minikube 的指令直接取得 service url:

1
minikube service my-pod-service --url

如果 Minikube Driver 是用 Docker 的話會得到以下的內容,因為事實上 Node 是透過 Docker 模擬出來的,所以 Local 要直接做存取的話,Minikube 這邊會透過將 Node Container Port 與 Local Port 在做一次映射:

1
2
3
4
5
6
7
8
🏃  Starting tunnel for service web-app-service.
|-----------|-----------------|-------------|------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|-----------------|-------------|------------------------|
| default | web-app-service | | http://127.0.0.1:64191 |
|-----------|-----------------|-------------|------------------------|
http://127.0.0.1:64191
❗ Because you are using a Docker driver on windows, the terminal needs to be open to run it.

所以這邊就會拿到 local 端的 64191 port 的對應,反之如果你直接 exec 到 Minikube Node 的 Container 也是可以的:

1
2
docker exec -it 47c8098c5313 /bin/bash
curl http://localhost:31014/health

這樣的話也可以存取 Pod 內的 Container。

Q&A

  • 網路上有比較多的範例都是 Minikube Driver 是採用 Virtualbox 的做法,所以再取得 service url 的時候會取得虛擬機的 ip 與 Node 對應的 port,所以這樣的話就不會像上面範例一樣是採用 Docker 並且再轉一次映射到 Local 的做法。

總結

這篇文章主要是介紹從建立自己的 Image 及 Container 再來如何在 K8S 建立 Pod 等操作,下一篇文章會介紹 Pod 常用的其他指令操作,還有其他更細節的地方。

最後最後!請聽我一言!

如果你還沒有註冊 Like Coin,你可以透過我的邀請註冊連結來免費註冊,註冊完後就可以在文章最下方幫我按下 Like 按鈕,而 Like 最多可以點五次,如此一來你不用付出任何一塊錢,就能給我寫這篇文章最大的回饋!