Kubernetes 基礎教學(二)實作範例:Pod、Service、Deployment、Ingress

如何建立一個 Pod?什麼是 Service、Deployment、Ingress 以及如何實作它們?

image

Kubernetes(K8S)是一個可以幫助我們管理微服務(microservices)的系統,他可以自動化地部署及管理多台機器上的多個容器(Container)。簡單來說,他可以做到:

  • 同時部署多個容器到多台機器上(Deployment)
  • 服務的乘載量有變化時,可以對容器做自動擴展(Scaling)
  • 管理多個容器的狀態,自動偵測並重啟故障的容器(Management)

系列文目錄

在系列文的上一篇文章中,我們了解了構成 Kubernetes 的四個重要元素:Pod、Node、Master、Cluster,並安裝好了我們要實際動手玩 Kubernetes 前需要的套件與工具。接下來在這篇文章中,我們會透過例子來實際建立那些在 Kubernetes 中常見的元件們。

如何建立一個 Pod

基本運作與安裝中,我們下載好了我們需要的三個程式:Minikube、Virtual Box、Kubectl,接下來我們就可以來練習如何建立我們的第一個 Pod 了。

啟動 Minikube

下載完 minikube 之後,我們可以先透過

minikube

瞧瞧所有 minikube 的指令,然後透過

minikube start

就可以啟動 minikube。另外再補充五個常用的指令:

列出 minikube 的狀態

minikube status

停止 minikube 運行

minikube stop

ssh 進入 minikube 中

minikube ssh

查詢 minikube 對外的 ip

minikube ip

透過 minikube 提供的瀏覽器 GUI 查看 Cluster 狀況

minikube dashboard

準備 Pod 中運行的目標程式

在啟動 Minikube 後,在接下來我們要來選定一個要在我們 Pod 中運行的程式。我們要把這個程式打包成 Image 後上傳到 DockerHub 上,在這邊我們的目標範例程式是一個 Node.js 的 Web App,相關的程式碼可以在這個 Github Repo 上找到。

簡單來說,這個程式的邏輯就是會建立一個 Server 監聽在 3000 port,收到 request 進來後會渲染 docker.html 這個檔案,這時網頁上就會出現一隻可愛的小鯨魚。

你也可以試著自己透過 docker build -t 先建立好 docker image

docker build -t yourDockerAccount/yourDockerApp

然後再透過 docker push 上傳到 Dockerhub

docker push yourDockerAccount/yourDockerApp:latest

在這邊我已經把上面的 Node.js Web App 上傳到這個 Dockerhub Repo

接下來我們就要正式來建立一個 Pod 了。

撰寫 Pod 的身分證

還記得我們在介紹 Kubernetes 時有提到,每個 Pod 都有一個身分證,也就是屬於這個 Pod 的 .yaml 檔。我們透過撰寫下面的這個 .yaml 檔就可以建立出 Pod。

kubernetes-demo.yaml

apiVersion: v1
 kind: Pod
 metadata:
   name: kubernetes-demo-pod
   labels:
     app: demoApp
 spec:
   containers:
     - name: kubernetes-demo-container
       image: hcwxd/kubernetes-demo
       ports:
         - containerPort: 3000

apiVersion

  • 該元件的版本號

kind

  • 該元件是什麼屬性,常見有 PodNodeServiceNamespaceReplicationController

metadata

  • name 指定該 Pod 的名稱
  • labels 指定該 Pod 的標籤,這裡我們暫時幫它上標籤為 app: demoApp

spec

  • container.name 指定運行出的 Container 的名稱
  • container.image 指定 Container 要使用哪個 Image,這裡會從 DockerHub 上搜尋
  • container.ports 指定該 Container 有哪些 port number 是允許外部資源存取

透過 kubectl 建立 Pod

有了身份證後,我們就可以透過 kubectl 指令來建立 Pod

kubectl create -f kubernetes-demo.yaml

看到 pod/kubernetes-demo-pod created 的字樣就代表我們建立成功我們的第一個 Pod 了。我們可以再透過指令

kubectl get pods

看到我們運行中的 Pod:

NAME                  READY   STATUS    RESTARTS   AGE
kubernetes-demo-pod   1/1     Running   0          60s

連線到我們 Pod 的服務資源

建立好我們的 Pod 之後,打開瀏覽器的 localhost:3000 我們會發現怎麼什麼都看不到。這是因為在 Pod 中所指定的 port,跟我們本機端的 port 是不相通的。因此,我們必須還要透過 kubectl port-forward,把我們兩端的 port 做 mapping。

kubectl port-forward kubernetes-demo-pod 3000:3000

做好 mapping 後,再打開瀏覽器的 localhost:3000 ,我們就可以迎接一隻可愛的小鯨魚囉!

image 1

Kubernetes 進階三元件

了解完如何從無到有建立一個 Kubernetes Cluster 並產生一個 Pod 後,接下來我們要認識在現實應用中,我們還會搭配到哪些 Kubernetes 的進階元件。其中最重要的三個進階元件就是:Service、Ingress、Deployment。

Service

還記得上面提到我們在連線到一個 Pod 的服務資源時,會使用到 port-forward 的指令。但如果我們有多個 Pods 想要同時被連線時,我們就可以用到 Service 這個進階元件。簡單來說,Service 就是 Kubernetes 中用來定義「一群 Pod 要如何被連線及存取」的元件。

要建立一個 Service,一樣要撰寫屬於他的身分證。

service.yaml

apiVersion: v1
 kind: Service
 metadata:
   name: my-service
 spec:
   selector:
     app: demoApp
   type: NodePort
   ports:
     - protocol: TCP
       port: 3001
       targetPort: 3000
       nodePort: 30390

apiVersion

  • 該元件的版本號

kind

  • 該元件是什麼屬性,常見有 Po``dNodeServiceNamespaceReplicationController

metadata

  • name 指定該 Pod 的名稱

spec

  • selector 該 Service 的連線規則適用在哪一群 Pods,還記得我們在建立 Pod 的時候,會幫它上 label,這時就可以透過 app: demoApp,去找到那群 label 的 app 屬性是 demoApp 的 Pods 們
  • ports - targetPort 指定我們 Pod 上允許外部資源存取 Port Number - port 指定我們 Pod 上的 targetPort 要 mapping 到 Service 中 ClusterIP 中的哪個 port - nodePort 指定我們 Pod 上的 targetPort 要 mapping 到 Node 上的哪個 port

接下來我們先重新建立我們的 Pod

kubectl create -f kubernetes-demo.yaml

接下來我們透過 service.yaml 來建立我們的 Service 元件

kubectl create -f service.yaml

然後我們可以透過

kubectl get services

取得我們新建立 Service 的資料

NAME       TYPE      CLUSTER-IP     EXTERNAL-IP PORT(S)          AGE
my-service NodePort  10.110.237.205 <none>      3001:30391/TCP   60s

有了建立好的 Service 後,我們可以透過兩種方式連線我們的 Pod 的服務資源。首先,要從外部連線到我們的 Pod 資源服務,我們必須要先有我們的 Kubernetes Cluster(在這邊是 minikube)對外開放的 IP。我們先透過指令

minikube ip

得到我們 minikube 的 ip

192.168.99.100

接著打開我們的瀏覽器,輸入上面的 ip 加上我們在 yaml 檔指定的 nodePort,在這邊是 192.168.99.100:``30390,就會得到我們的小鯨魚了。

而如果不從瀏覽器,而是直接從 minikube 裡面連線到我們的 Pod 則要先透過指令

minikube ssh

ssh 進入我們的 minikube cluster,接著輸入指令

curl <CLUSTER-IP>:<port>

其中 CLUSTER-IP 就是我們用 kubectl get services 得到我們 Service 的 IP,而 port 就是我們在 yaml 檔指定的 port,在這邊合起來就是 10.110.237.205:3001,於是我們

curl 10.110.237.205:3001

就可以在 minikube 裡面得到我們的小鯨魚囉!

Deployment

了解了 Service 後,接下來要來暸解第二個進階元件:Deployment。今天當我們同時要把一個 Pod 做橫向擴展,也就是複製多個相同的 Pod 在 Cluster 中同時提供服務,並監控如果有 Pod 當機我們就要重新把它啟動時,如果我們要一個 Pod 一個 Pod 透過指令建立並監控是很花時間的。因此,我們可以透過 Deployment 這個特殊元件幫我們達成上述的要求。

同樣要建立一個 Deployment,要先撰寫屬於他的身分證。

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: demoApp
    spec:
      containers:
        - name: kubernetes-demo-container
          image: hcwxd/kubernetes-demo
          ports:
            - containerPort: 3000
  selector:
    matchLabels:
      app: demoApp

apiVersion

  • 該元件的版本號

kind

  • 該元件是什麼屬性,常見有 PodNodeServiceNamespaceReplicationController

metadata

  • name 指定該 Pod 的名稱

spec

  • replicas 指定要建立多少個相同的 Pod,在這邊給的數字是所謂的 Desire State,當 Cluster 運行時如果 Pod 數量低於此數字,Kubernetes 就會自動幫我們增加 pod,反之就會幫我們關掉 Pod
  • template 指定這個 Deployment 建立的 Pod 們統一的設定,包括 metadata 以及這些 Pod 的 Containers,這邊我們就沿用之前建立 Pod 的設定
  • selector 指定這個 Deployment 的規則要適用到哪些 Pod,在這邊就是指定我們在 template 中指定的 labels

接下來我們就可以透過指令

kubectl create -f deployment.yaml

建立好我們的 Deployment,這時我們可以查看我們的 Deployment 有沒有被建立好

kubectl get deploy

NAME            READY   UP-TO-DATE   AVAILABLE   AGE
my-deployment   3/3     3            3           60s

接著我們在看 Pod 們有沒有乖乖按照 Deployment 建立

kubectl get pods

NAME                             READY   STATUS    RESTARTS   AGE
my-deployment-5454f687cd-bxjfz   1/1     Running   0          60s
my-deployment-5454f687cd-gszbr   1/1     Running   0          60s
my-deployment-5454f687cd-k6zfv   1/1     Running   0          60s

這邊我們可以看到三個 Pod 都被建立好了,我們就成功做到了 Pod 的橫向擴展。而除了 Pod 的橫向擴展外,Deployment 的另外一個好處就是可以幫我們做到無停機的系統升級(Zero Downtime Rollout)。也就是說,當我們要更新我們的 Pod 時,Kubernetes 並不會直接砍掉我們所有的 Pod,而是會建立新的 Pod,等新的 Pod 開始正常運行後,再來取代舊的 Pod。

舉例來說,假設我們現在想要更新我們 Pod 對外的 Port,我們可以先透過指令

kubectl edit deployments my-deployment

接著我們會看到我們的 Yaml 檔

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "2"
  creationTimestamp: "2019-04-26T04:18:26Z"
  generation: 2
  labels:
    app: demoApp
  name: my-deployment
  namespace: default
  resourceVersion: "328692"
  selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/my-deployment
  uid: 56608fb5-67da-11e9-933f-08002789461f
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: demoApp
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: demoApp
    spec:
      containers:
        - image: hcwxd/kubernetes-demo
          imagePullPolicy: Always
          name: kubernetes-demo-container
          ports:
            - containerPort: 3000
              protocol: TCP
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30

我們把其中 containerPort: 3000 改成 3001 後儲存,Kubernetes 就會開始幫我們進行更新。這時我們繼續用指令 kubectl get pods 就會看到

NAME                          READY STATUS            RESTARTS   AGE
my-deployment-5454f687cd-bxjf 1/1   Running           0          60s
my-deployment-5454f687cd-gszb 1/1   Terminating       0          60s
my-deployment-5454f687cd-k6zf 1/1   Running           0          60s
my-deployment-78dc8dcb89-5927 0/1   ContainerCreating 0          1s
my-deployment-78dc8dcb89-dwtl 1/1   Running           0          5s

從上面可以看到,Kubernetes 會永遠保持有 3 個 Pods 在正常運作,如果有新的 Pod 還在 ContainerCreating 的階段時,他還不會關掉對應要被取代的 Pod。而在過一段時間我們輸入同樣指令可以看到

NAME                            READY   STATUS        RESTARTS   AGE
my-deployment-5454f687cd-bxjf   1/1     Terminating   0          60s
my-deployment-5454f687cd-gszb   1/1     Terminating   0          60s
my-deployment-5454f687cd-k6zf   1/1     Terminating   0          60s
my-deployment-78dc8dcb89-5927   1/1     Running       0          11s
my-deployment-78dc8dcb89-7b7h   1/1     Running       0          7s
my-deployment-78dc8dcb89-dwtl   1/1     Running       0          15s

我們三個新的 Pod 都被成功部署上去用來取代舊的 Pod 了,靠著這樣的機制,我們就可以確保系統在更新的時候不會有服務暫時無法使用的狀況。這時我們可以透過指令

kubectl rollout history deployment my-deployment

看到我們目前更改過的版本

deployment.extensions/my-deployment
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

從上面可以看出來,我們目前有兩個版本,如果我們發現版本 2 的程式有問題,想要先讓服務先恢復成版本 1 的程式(Rollback)時,我們還可以透過指令

kubectl rollout undo deploy my-deployment

讓我們的 Pod 都恢復成版本 1。甚至之後如果版本變的較多後,我們也可以指定要 Rollback 到的版本

kubectl rollout undo deploy my-deployment --to-revision=2

Ingress

了解完了 Service 跟 Deployment 後,接下來就輪到概念稍微複雜的 Ingress 元件了。 在上面有提到 Service 就是 Kubernetes 中用來定義「一群 Pod 要如何被連線及存取」的元件。 但在 Service 中,我們是將每個 Service 元件對外的 port number 跟 Node 上的 port number 做 mapping,這樣在我們的 Service 變多時,port number 以及分流規則的管理變得相當困難。

而 Ingress 可以透過 HTTP/HTTPS,在我們眾多的 Service 前搭建一個 reverse-proxy。這樣 Ingress 可以幫助我們統一一個對外的 port number,並且根據 hostname 或是 pathname 決定封包要轉發到哪個 Service 上,如同下圖的比較:

image 3

在 Kubernetes 中,Ingress 這項服務其實是由 Ingress Resources、Ingress Server、Ingress Controller 構成。其中 Ingress Resources 就是定義 Ingress 的身分證,而 Ingress Server 則是實體化用來接收 HTTP/HTTPS 連線的網路伺服器。但實際上,Ingress Server 有各式各樣的實作,就如同市面上的 Web Server 琳瑯滿目一樣。因此,Ingress Controller 就是一個可以把定義好的 Ingress Resources 設定轉換成特定 Ingress Server 實作的角色。

舉例來說,Kubernetes 由官方維護的兩種 Ingress Controller 就有 ingress-gceingress-nginx,分別可以對應轉換成 GCE 與 Nginx。也有其他非官方在維護的 Controller,詳細的列表可見官網的 additional-controllers

接下來我們要來試著建立一個 Ingress 物件去根據 hostname 轉發封包到不同的 Pod 上面。所以第一步,我們要用 Deployment 建立好幾個不同的 Pod。在這邊我們直接透過準備好的兩個 Image 來建立其中的 Container,blue-whale 這個 Image 裡的程式會監聽 3000 port 然後在瀏覽器上被存取時會吐出藍色的鯨魚,purple-whale 則會吐出紫色的鯨魚。

deployment.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: blue-nginx
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: blue-nginx
    spec:
      containers:
        - name: nginx
          image: hcwxd/blue-whale
          ports:
            - containerPort: 3000
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: purple-nginx
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: purple-nginx
    spec:
      containers:
        - name: nginx
          image: hcwxd/purple-whale
          ports:
            - containerPort: 3000

接著我們就可以透過 kubectl create -f deployment.yaml 建立好我們的 Pod。

AME                            READY   STATUS    RESTARTS  AGE
blue-nginx-6b68c797c7-28tkz    1/1     Running   0         60s
blue-nginx-6b68c797c7-8ww8l    1/1     Running   0         60s
purple-nginx-84854fd7c-8g4nl   1/1     Running   0         60s
purple-nginx-84854fd7c-tmrbs   1/1     Running   0         60s

建立好了 Pod 們後,接下來我們就要建立這些 Pod 對外的各自 Service,在這邊我們會把各至 Container 上的 3000 port 全部都轉到 80 port 上。

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: blue-service
spec:
  type: NodePort
  selector:
    app: blue-nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: purple-service
spec:
  type: NodePort
  selector:
    app: purple-nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000

透過 kubectl create -f service.yaml 建立好我們的 Pod。

NAME            TYPE      CLUSTER-IP      EXTERNAL-IP  PORT(S)
blue-service    NodePort  10.111.192.164  <none>       80:30492/TCP
purple-service  NodePort  0.107.21.77     <none>       80:32086/TCP

最後,我們就可以來建立我們的主角 Ingress 了!在這邊我們的 Ingress 只有很簡單的規則,他會把所有發送到 blue.demo.com 的封包交給 service blue-service 負責,而根據上面 service.yaml 的定義,他會再轉交給 blue-nginx 這個 Pod。而發送給 purple.demo.com 則會轉交給 purple-nginx

在這邊,我們要先記得使用指令 minikube addons enable ingress 來啟用 minikube 的 ingress 功能。接著,我們就來撰寫 ingress 的身分證。

ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: web
spec:
  rules:
    - host: blue.demo.com
      http:
        paths:
          - backend:
              serviceName: blue-service
              servicePort: 80
    - host: purple.demo.com
      http:
        paths:
          - backend:
              serviceName: purple-service
              servicePort: 80

我們一樣透過 kubectl create -f ingress.yaml 來建立我們的 ingress 物件。並使用 kubectl get ingress 來查看我們的 ingress 狀況:

NAME   HOSTS                           ADDRESS     PORTS   AGE
web    blue.demo.com,purple.demo.com   10.0.2.15   80      60s

接下來我們要來測試 ingress 有沒有乖乖幫我們轉發。因為我們的 Cluster 實際上對外的 ip 都是我們透過指令 minikube ip 會看到的 192.168.99.100,這樣我們要怎麼同時讓這個 ip 可以是我們設定規則中的 blue.demo.com 以及 purple.demo.com 呢?

因為我們知道在 DNS 解析網址時,會先查找本機上 /etc/hosts 後才會到其他 DNS Server 上尋找。所以我們可以透過一個小技巧,在本機上把 blue.demo.com 以及 purple.demo.com 都指向 192.168.99.100。透過指令

echo 192.168.99.100   blue.demo.com  >> /etc/hosts
echo 192.168.99.100   purple.demo.com >> /etc/hosts

或是透過 sudo vim /etc/hosts 手動加上這兩條規則,我們就成功搞定 DNS 可以來測試了。接下來我們打開瀏覽器,輸入 blue.demo.com 就可以得到熟悉的藍色小鯨魚

image 2

然後輸入 purple.demo.com 就可以得到紫色小鯨魚囉!

image 4

在實際建立過 Pod、Service、Deployment 還有 Ingress 後,在接下來的文章,我們要來介紹一個可以讓這個建立流程變得更簡單的工具,也就是 Kubernetes 中的 Package Manager:Helm!

Kubernetes 基礎教學(三)Helm 介紹與建立 Chart


Discuss this article on: Twitter | Facebook.
To get updates when I write, sign up my newsletter below: