上一篇 我們介紹了如何建立最基本的 Pod 和連線到應用的方式,然而這樣的建立方式是無法滿足實際應用中的需求的。實際應用上會有多伺服器運行同個應用、伺服器故障需要故障轉移等等的問題,因此 Kubernetes 提供了一些 Pod 的進階功能,如下 :
- Service
- Deployment
- Ingress
Service
Service 是一個抽象化的物件,它定義了一群的 Pod 和存取這些 Pod 的規則。簡單來說就是要讓使用者和 Kubernetes Cluster 進行隔離,使用者只需要告訴 Service 我的請求而不需要知道誰會幫我處理也不需要知道處理的過程和細節,Service 收到請求後就會依照定義的規則將請求送到對應的 Pod。
Pod 和 Pod 之間的也會透過 Service 來存取。
Service 處理 Port 對應
上一篇 我們使用了 port-forward 來將 Pod 的 Port 對應到主機,而 Service 也可以指定對應的 Port 讓 Pod 可以被外部存取。
這裡我們先使用 kubectl 的 expose
指令來讓 Pod 的 Port 開放給外部存取,expose
指令會自動建立一個 Service。
1 | kubectl expose pod <pod name> --type=<service type> --name=<service name> \ |
type 可以指定這個 Service 的類型,常見的類型如下 :
- ClusterIP : 預設類型,使用 Cluster 內部的 IP 來暴露 Service,因此只能在 Cluster 內部存取。
- NodePort : 使用 Node 的 IP 和 Port 來暴露 Service,在 Cluster 外可以透過 NodeIP:NodePort 來存取 Service。而 Cluster 內部所需的 Cluster IP 會自動被建立。
- LoadBalancer : 使用雲端供應商提供的 LoadBalancer 來開放 Service,會自動建立相對應的 NodePort 和 Cluster IP。
- ExternalName : 將 Service 關聯到 ExternalName,也就是 Domain。例如 : foo.sample.com。
port 和 target-port 我們搭配下面這張圖來說明 Kubernetes 外部是如何連進到內部的 Container。這裡我們以 NodePort 這個類型來介紹。
首先外部會先向 NodeIP:NodePort 發出 Request,這個 NodePort 再建立 Service 時就會被開放給外部存取。接著會再找到 Service 所開放的 Port 將 Request 傳入,這個 Service 開放的 Port 就是 kubectl expose
所要定義的 port
。
當 Service 接收到 Request 時,會再依照目標的 Pod 所開放的 Port 將 Request 轉傳給 Pod。這裡目標 Pod 所開放的 Port 即為 kubectl expose
所要定義的 target-port
,而這個 target-port 也就是 Container 的 Port。
範例
這個範例的 Type 我們使用 NodePort,因為要讓瀏覽器可以連上必須讓 Service 開放給外部連線。
1 | $ kubectl expose pod kubernetes-pod --type=NodePort |
這裡可以看到沒有指定 port 和 target-port,如果沒有指定的話 target-port 會自動代入 Container 開放的 Port,port 也會自動代入和 target-port 一樣的值。
而上方有提到 Service 建立時會自動開放 NodePort,讓外部透過這個 NodePort 連到對應的 Service。Kubernetes 會從 30000-32767 這段區間自動分配 NodePort 對應到 Service。
使用 kubectl get services
來查看一下剛剛建立的 Service,可以看到 kubernetes-pod
這個 Service 已經被建立起來了。而後面的 PORT 9000:31847
的 9000 即為 Service 開放的 Port 對應到被自動指派的 NodePort 31847。
1 | $ kubectl get services |
我們可以再使用 kubectl describe services
指令來查看 Service 的詳細資訊,這裡就可以很清楚的看到 NodePort、Port 和 TargetPort 分別是多少。
1 | $ kubectl describe services kubernetes-pod |
現在我們就可以使用 minikube service
指令來查看要連線的 Service,並取得連線的 url。前面的 IP 是 Node 的 IP,而後面接的 Port 就是前面提到的 NodePort。將這段 url 貼到瀏覽器就可以成功打開 potainer。
1 | $ minikube service kubernetes-pod --url |
建立 Service 的 yaml 檔
前面介紹了使用 kubectl expose
可以建立一個 Port 對應連接的 Service,而其實 Service 也可以使用 yaml 檔定義好再建立。
以下是一個基本的 Service yaml 檔格式。
1 | apiVersion: v1 |
經過上述的介紹相信大家都看得懂這個 yaml 檔了,不過這裡要介紹一下前面沒有提到的 selector。
selector 是這個 Service 過濾的條件,而這裡會指定 label 和他必須符合的值。像是基本格式中指定了 app
這個 label,而在這個 Node 當中的任何一個 Pod 只要有 app 這個 label 並且值符合就會被挑選出來,而 label 可以定義多個。
範例
下面這個範例 target-port 因為是 Container 開放出來的 Port 所以還是指定 9000,而 port 這裡就可以進行更改了,我們改成 9001。最後 NodePort 在 上一篇 有說只能分配在 30000-32767,所以我們就給 30001。
另外可以再看到 selector,這裡指定的是 name 這個標籤並且值要是 k8s-pod,這個設定是要符合 上一篇 建立 Pod 時 yaml 檔的設定,這樣建的 Pod 才會被選出來。
kubernetes-service.yml
1 | apiVersion: v1 |
yaml 檔完成後就可以將 Service 建立起來了。
1 | $ kubectl create -f kubernetes-service.yml |
建好後再次查看 Service 可以看到已經成功被建立起來,並且 Port 的對應也跟 yaml 檔的設定一樣。
1 | $ kubectl get services kubernetes-service |
最後取得連線到 Service 的 url,就可以在瀏覽器上成功打開 potainer。
1 | $ minikube service kubernetes-service --url |
Deployment
Deployment 用於橫向擴展 Pod,會將相同的 Pod 複製成指定的數量,目的是為了在 Pod 故障或無法即時回應時透過其他複製的 Pod 頂替上來,以確保應用可以不中斷的繼續提供服務。當然,也可以用於負載平衡。所以最好的做法是相同的 Pod 要部署在不同的 Node 上,也就是不同主機上。
這樣的方式可以簡化建立 Pod 的步驟和監控管理的部分,透過 Deployment 可以自動做完這些事情。
Replica Set
Deployment 其實是搭配了 Replica Set 這項功能來使用的,Replica Set 是用來確保 Pod 的數量會一直和使用者指定的一樣。Replica Set 會在 Pod 故障時重新啟動新的 Pod,並把舊的 Pod 刪除。
而 Deployment 是更高層次的概念,他會管理建立出來的 Replica Set,如下圖 :
Kubernetes 官方更推薦使用 Deployment 來建立 Replica Set 而非自己建立 Replica Set。因為 Deployment 提供了更好的自動維護的功能,因此這裡就不介紹單獨建立 Replica Set 的方法了。
使用 Deployment 的好處
-
自動部署應用
一個應用一定會不斷的更新,Deployment 可以使用 Rolling Update(滾動式更新) 來不斷的更新 Pod 和部署應用。 -
Pod 更新過程不停機 (zero downtime)
當 Pod 需要更新時,會先產生一個新的 Replica Set,並在新的 Replica Set 產生一個新的 Pod,接著會在舊的 Replica Set 刪除一個舊的 Pod ,重複這個動作直到所有需要更新的 Pod 都完成。因此更新的過程完全不會發生停機的問題,Pod 的數量也都維持不會少於指定的數量。
- 可以 Rollback 回先前的版本
每次進行滾動更新時,會把 Replica Set 紀錄起來,類似於 git 的紀錄。當更新後系統發生問題,就可以回到之前穩定的版本。
建立 Deployment 的 yaml 檔
要建立 Deployment 同樣要撰寫一個 yaml 檔來定義再啟動,下面是一個基本的 Deployment yaml 檔的格式。
1 | apiVersion: apps/v1 |
spec :
-
replicas
定義要建立幾個 Pod。 -
selector
這裡的 Selector 定義是要讓 Deployment 知道他要管理哪些 Pod,也是用給標籤的概念。 -
template
定義這個 Deployment 在建立 Pod 時的統一設定,Deployment 建立時就會依照這個樣板做出多個一模一樣的 Pod。
範例
這個範例一樣使用 Portainer 作為 Container 的應用,並指定建立 3 個 Pod。
kubernetes-deployment.yml
1 | apiVersion: apps/v1 |
完成 yaml 檔後就可以將 Deployment 建立起來。
1 | $ kubectl create -f kubernetes-deployment.yml |
使用 kubectl 的 get deploy
指令來查看剛建好的 Deployment。
1 | $ kubectl get deploy |
接著再來查看 Pod 是否有成功被建立起來,可以看到建立起了三個 Pod。
1 | $ kubectl get pods |
更新 Deployment
要不停機的更新 Deployment 可以使用 kubectl edit deploy
指令,就可以打開 deployment 的 yaml 檔進行編輯並再儲存後自動進行更新。
1 | $ kubectl edit deploy <deployment name> |
範例
這裡使用前面建好的 Deployment 來操作。
1 | $ kubectl edit deploy kubernetes-deployment |
執行了上面的指令後會換到編輯畫面,如下。這裡就可以進行 Deployment 的修改。
1 | # Please edit the object below. Lines beginning with a '#' will be ignored, |
這裡我們只修改 Container 的 name 來做測試,把 portainer 改成 portainer2。
1 | spec: |
接著可以使用 kubectl get pods
來查看 pod 的更新狀況。
首先可以看到 kubernetes-deployment-78b5df786-jfp2q
這個 Pod 正在被建立起來,而原先的 Pod 也都還在,這符合我們上面所說的會先產生新的 Pod,而在新的 Pod 還沒建好前舊的是不會被刪除以達到不停機更新。
1 | NAME READY STATUS RESTARTS AGE |
接著因為 kubernetes-deployment-78b5df786-jfp2q
已經建立好了,所以其中一個 Pod kubernetes-deployment-6758964498-m5clb
也就被停止了,後續會自動被刪除。與此同時下一個新的 Pod 又被建立了起來,就這樣持續相同動作直到建立完指定數量的 Pod。
1 | NAME READY STATUS RESTARTS AGE |
最後可以看到 Pod 都被更新成新的了。
1 | NAME READY STATUS RESTARTS AGE |
更新狀況
可以使用 kubectl rollout status
來查看更新的狀況。
1 | $ kubectl rollout status deploy <deployment name> |
範例
這裡一樣使用前面更新的 Deployment 來查看更新狀況,可以看到更新成功。
1 | $ kubectl rollout status deploy kubernetes-deployment |
更新歷史紀錄
可以使用 kubectl rollout history
來查看更新的歷史紀錄。
1 | kubectl rollout history deploy <deployment name> |
範例
下面的範例可以看到有兩筆紀錄, Revision 是更新的版號。
1 | $ kubectl rollout history deploy kubernetes-deployment |
退回更新前的版本
可以使用 kubectl rollout undo
來退回更新前的版本,還可以指定要退回哪一版,不指定就是上一版。
1 | $ kubectl rollout undo deploy <deployment name> --to-revision=<revision number> |
範例
下面這個範例不指定版號,所以就是退回前一版。
1 | $ kubectl rollout undo deploy kubernetes-deployment |
退版之後再次取得 pod 來看可以發現 NAME 從 kubernetes-deployment-78b5df786
變成了 kubernetes-deployment-6758964498
,而 kubernetes-deployment-6758964498
正是上一版 Pod 的 NAME。
1 | $ kubectl get pods |
Ingress
Ingress 用於將所有進入 Service 的 Port 統一,回想一下前面我們介紹 Service 時會制定開放的 Port,但是一旦 Service 的數量越來越多時就很難去維護了。如下圖 :
所以 Ingress 的作法是依據 Request 指定的路徑或是 Domain 來將 Request 導向到對應的 Service。
而統一的 Port 可以是 HTTP 的 80 或 HTTPS 的 443,只要對外開放的 Port 統一,在維護上就可以更加輕鬆也可以因此達到 Reverse Proxy 的作用。如下圖 :
建立 Ingress 的 yaml 檔
Ingress 同樣是要透過撰寫 yaml 檔來建立,下面是 Ingress 的 yaml 檔基本格式。
1 | apiVersion: networking.k8s.io/v1beta1 |
paths :
-
host
host 可以指定自訂的 Domain Name,也可以不設定就像上方 yaml 檔基本格式中 rules 的第二個沒有指定。沒有指定的情況就是使用主機的 IP。 -
path
path 可以定義特定的路徑會導向到指定的 Service,也可以不指定。 -
pathType
在 networking.k8s.io/v1beta1 這版是沒有支援 pathType,但是官方文件使用 networking.k8s.io/v1 這版有介紹,就順便紀錄下來。pathType 可以指定符合的路徑格式,例如,指定
Prefix
就代表是前面的 IP 會是主機的 IP,或是 Domain 會是指定的 Host,若沒有指定 pathType 則Prefix
為預設值。也可以指定Exact
,就代表整個 URL 都要完全符合。pathType 更多用法可以參考 Kubernetes 官網。 -
backend
backend 可以指定後續要進行處理的 Service。
範例
在建立 Ingress 之前,我們要先建立一組 Deployment 和 Service,最後再建立 Ingress。
首先先建立 Deployment 讓 Pod 建立起來,這裡因為 portainer 在後續要指定路徑啟動時會遇到畫面呈現有問題也不太確定原因,所以改用 另一篇教學 所提供的藍色和紫色小鯨魚的 Image。
kubernetes-deployment.yml
1 | apiVersion: apps/v1 |
完成 Deployment 的 yaml 檔後就可以將 Deployment 建立起來。
1 | $ kubectl create -f kubernetes-deployment.yml |
接下來我們要建立這些 Pod 所對應的 Service,可以看到 Service 的 Port 統一都設定為 80。所以 target port 都會對應到 Service 的 80 port。而這裡也可以發現沒有設定 NodePort,因為使用 Ingress 後外部就不會再直接透過 NodePort 連進來了。
kubernetes-service.yml
1 | apiVersion: v1 |
完成 Service 的 yaml 檔後就可以將 Service 建立起來。
1 | $ kubectl create -f kubernetes-service.yml |
最後我們就要來建立 Ingress,分別對兩個應用指定不同的 Host。
kubernetes-ingress.yml
1 | apiVersion: networking.k8s.io/v1beta1 |
完成 Ingress 的 yaml 檔後,要先使用 minikube addons enable ingress
的指令啟用 Ingress,接著就可以將 Ingress 建立起來。
1 | $ kubectl create -f kubernetes-ingress.yml |
建立好 Ingress 後可以使用 kubectl get ingress
來查看剛建立的 ingress。
1 | $ kubectl get ingress |
建立完成後別急著輸入 Host Name 就想連到 blue.demo.com 或 purple.demo.com,因為 DNS 會先在本機上的 /etc/hosts
尋找網址跟 IP 的對應,找不到才會去其他的 DNS Server 找。所以既然這是我們自訂的網域名稱,可以直接將這些網域名稱指向 minikube ip 並寫進 /etc/hosts
,這樣就可以連到 minikube 的 Kubernetes 了。如果忘記了 Kubernetes 的 IP,可以使用 minikube ip
指令來取得。建立 DNS 對應作法如下 :
1 | $ echo 172.17.0.2 blue.demo.com >> /etc/hosts |
以上都完成後就先來看看指定 Host 的結果 :
接著再來看指定路徑的結果 :
Summary
本篇介紹了一些 Pod 的進階應用,實務上若能使用 Service + Deployment + Ingress 來部署應用才會較好管理和維護,也能發揮 Kubernetes 最大的效益。
參考
[1] Kubernetes學習筆記2 — pod及service
[2] Kubernetes Service 概念詳解
[3] 建立外部服務與Pods的溝通管道 - Services
[4] Kubernetes Service | 知乎
[5] Day 16 - Kubernetes Label 與 Selector
[6] Port, TargetPort, and NodePort in Kubernetes
[7] 在 Kubernetes 中實現負載平衡 - Ingress Controller
[8] Ingress | Kubernetes
[9] Deployment | Google Clooud
[10] Deployments | Kubernetes
[11] K8s ApiVersion
[12] Kubernetes 那些事 — Ingress 篇(一)
[13] Kubernetes 那些事 — Deployment 與 ReplicaSet(二)
[14] Kubernetes 30天學習筆記
[15] 名詞解釋:ReplicaSets
[16] Kubernetes 基礎教學(二)實作範例:Pod、Service、Deployment、Ingress