Kubernetes 基礎教學(二)實作範例:Pod、Service、Deployment、Ingress
如何建立一個 Pod?什麼是 Service、Deployment、Ingress 以及如何實作它們?
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
- 該元件的版本號
kind
- 該元件是什麼屬性,常見有
Pod
、Node
、Service
、Namespace
、ReplicationController
等
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
,我們就可以迎接一隻可愛的小鯨魚囉!
Kubernetes 進階三元件
了解完如何從無到有建立一個 Kubernetes Cluster 並產生一個 Pod 後,接下來我們要認識在現實應用中,我們還會搭配到哪些 Kubernetes 的進階元件。其中最重要的三個進階元件就是:Service、Ingress、Deployment。
Service
還記得上面提到我們在連線到一個 Pod 的服務資源時,會使用到 port-forward
的指令。但如果我們有多個 Pods 想要同時被連線時,我們就可以用到 Service 這個進階元件。簡單來說,Service 就是 Kubernetes 中用來定義「一群 Pod 要如何被連線及存取」的元件。
要建立一個 Service,一樣要撰寫屬於他的身分證。
service.yaml
apiVersion
- 該元件的版本號
kind
- 該元件是什麼屬性,常見有
Pod
、Node
、Service
、Namespace
、ReplicationController
等
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
- 該元件的版本號
kind
- 該元件是什麼屬性,常見有
Pod
、Node
、Service
、Namespace
、ReplicationController
等
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 deployNAME READY UP-TO-DATE AVAILABLE AGE
my-deployment 3/3 3 3 60s
接著我們在看 Pod 們有沒有乖乖按照 Deployment 建立
kubectl get podsNAME 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 上,如同下圖的比較:
在 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-gce 跟 ingress-nginx,分別可以對應轉換成 GCE 與 Nginx。也有其他非官方在維護的 Controller,詳細的列表可見官網的 additional-controllers。
接下來我們要來試著建立一個 Ingress 物件去根據 hostname 轉發封包到不同的 Pod 上面。所以第一步,我們要用 Deployment 建立好幾個不同的 Pod。在這邊我們直接透過準備好的兩個 Image 來建立其中的 Container,blue-whale 這個 Image 裡的程式會監聽 3000 port 然後在瀏覽器上被存取時會吐出藍色的鯨魚,purple-whale 則會吐出紫色的鯨魚。
deployment.yaml
接著我們就可以透過 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
透過 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
我們一樣透過 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
就可以得到熟悉的藍色小鯨魚
然後輸入 purple.demo.com
就可以得到紫色小鯨魚囉!
在實際建立過 Pod、Service、Deployment 還有 Ingress 後,在接下來的文章,我們要來介紹一個可以讓這個建立流程變得更簡單的工具,也就是 Kubernetes 中的 Package Manager:Helm!
☞ Kubernetes 基礎教學(三)Helm 介紹與建立 Chart
如果有任何對於文章的問題,歡迎直接透過臉書跟我討論。如果這篇文章能夠對你有幫助,歡迎給我一點 Claps 並在 Medium 上持續 Follow 我囉!感恩惜福 <(_ _)>
Want to Learn More? Weekly I/O!
Weekly I/O is a project where I share my learning Input/Output. Every Sunday, I write an email newsletter with five things I discovered and learned that week.
Sign up here and let me share a curated list of learning Input/Output with you 🎉