作者:沈亞軍
愛可生研發團隊成員,負責公司 DMP 產品的後端開發,愛好太廣,三天三夜都說不完,低調低調...
本文來源:原創投稿
*愛可生開源社群出品,原創內容未經授權不得隨意使用,轉載請聯絡小編並註明來源。
pod 是什麼
Pod 是一組互相協作的容器,是我們可以在 Kubernetes 中建立和管理的最小可部署單元。同一個 pod 內的容器共享網路和儲存,並且作為一個整體被定址和排程。當我們在 Kubernetes 中建立一個 pod 會建立 pod 內的所有容器,並且將容器的所有資源都被分配到一個節點上。
為什麼需要 pod
思考以下問題,為什麼不直接在 kubernetes 部署容器?為什麼需要把多個容器視作一個整體?為什麼不使用同一個容器內執行多個程式的方案?
當一個應用包含多個程式且通過IPC
方式通訊,需要執行在同一臺主機。如果部署在 kubernetes 環境程式需要執行在容器內,所以可能考慮方案之一是把多個程式執行在同一個容器內以實現類似在同一個主機的部署模式。但是 container 的設計是每個容器執行一個單獨的程式,除非程式本身會建立多個子程式,當然如果你選擇在同一個容器內執行多個沒有聯絡的程式的話,那麼需要自己來管理其他程式,包括每個程式的生命週期(重啟掛掉的程式)、日誌的切割等。如果多個程式都在標準輸出和標準錯誤輸出上輸出日誌,就會導致日誌的混亂,因此 docker 和 kubernetes 希望我們在一個容器內只執行一個程式。
排除在同一個容器內執行多個程式的方案後,我們需要一個更高層級的組織結構實現把多個容器繫結在一起組成一個單元,這就是 pod 概念的由來,Pod 帶來的好處:
- Pod 做為一個可以獨立執行的服務單元,簡化了應用部署的難度,以更高的抽象層次為應用部署管提供了極大的方便。
- Pod 做為最小的應用例項可以獨立執行,因此可以方便的進行部署、水平擴充套件和收縮、方便進行排程管理與資源的分配。
- Pod 中的容器共享相同的資料和網路地址空間,Pod 之間也進行了統一的資源管理與分配。
pause 容器
因為容器之間是使用 Linux Namespace 和 cgroups 隔開的,所以 pod 的實現需要解決怎麼去打破這個隔離。為了實現同 pod 的容器可以共享部分資源,引入了 pause 容器。 pause 容器的映象非常小,執行著一個非常簡單的程式。它幾乎不執行任何功能,啟動後就永遠把自己阻塞住。每個 Kubernetes Pod 都包含一個 pause 容器, pause 容器是 pod 內實現 namespace 共享的基礎。
在 linux 環境下執行一個程式,該程式會繼承父程式所有的namespace
,同時也可以使用unsharing
方式建立新的namespace
。如下使用unshare
方式執行 shell 並建立新的 PID、UTS、IPC 和 mount 名稱空間。
sudo unshare --pid --uts --ipc --mount -f chroot rootfs /bin/sh
其他程式可以使用系統呼叫setns
加入到新的名稱空間,pod
的實現方式也是類似,通過如下命令演示如何手動建立一個簡易的pod
## 首先執行一個 pause 容器
docker run -d --name pause -p 8880:80 --ipc=shareable gcr.io/google_containers/pause-amd64:3.0
## 建立 nginx 容器,並將其加入到 pause 容器 net ipc 和 pid namespace
$ cat <<EOF >> nginx.conf
error_log stderr;
events { worker_connections 1024; }
http {
access_log /dev/stdout combined;
server {
listen 80 default_server;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1:2368;
}
}
}
EOF
docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf --net=container:pause --ipc=container:pause --pid=container:pause nginx
## 執行 ghost 容器 並將其加入到 pause 容器 network ipc 和 pid namespace
docker run -d --name ghost --net=container:pause --ipc=container:pause --pid=container:pause ghost
在 ghost 容器中使用 ps 可以看到 pause 和 nginx 程式,
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1032 4 ? Ss 10:06 0:00 /pause
root 8 0.0 0.1 8864 3120 ? Ss 10:15 0:00 nginx: master process nginx -g daemon off;
101 38 0.0 0.1 9312 3088 ? S 10:15 0:00 nginx: worker process
node 48 0.3 6.9 969996 142296 ? Ssl 10:18 0:09 node current/index.js
通過 localhost:8080 訪問 ghost 頁面,那麼應該能夠看到 ghost 通過 Nginx 代理執行,因為 pause、nginx 和 ghost 容器之間共享 network namespace,如下圖所示:
pod 常用方式
pod 使用方式可以被分為兩種型別:
- pod 內只執行一個容器。這種情況可把 pod 視為容器的包裝器,kubernetes 通過管理 pod 方式管理容器;
- pod 內執行多個需要共享資源緊密協作的容器。如下圖所示,兩個容器通過 Volume 共享檔案,Filer Puller 從遠端更新檔案,Web Server 負責檔案的展示。
是否把兩個容器分配在不同或同一個 pod,通常需要考慮以下幾點:
- 它們是否有必要執行在同一個 kubernetes 節點?
- 它們代表一個整體,還是獨立的組成部分?
- 它們是否有必要整體擴縮容?
Pod 的使用
建立 Pod
通過如下方式kubectl apply -f nginx-pod.yaml
建立 pod,並通過kubectl get pod
檢視 pod 的狀態,如下所示。
apiVersion: v1
kind: Pod
metadata:
name: nginx # pod 名稱
spec:
containers: # 容器列表
- name: nginx # 容器名稱
image: nginx:1.14.2 # 容器使用映象
ports: # 容器埠對映
- containerPort: 80
執行kubectl describe pod nginx
檢視 pod 的狀態,如下所示展示了 pod 部分資訊,Status
欄位是 pod 在其生命週期中的一個摘要介紹,Running 表示 pod 處於正常執行狀態
Name: nginx
Namespace: default
.....
Start Time: Sat, 04 Jun 2022 09:24:36 +0000
Labels: <none>
.....
Status: Running
IP: 10.42.1.139
Containers:
nginx:
Container ID: docker://xxxx
Image: nginx:1.14.2
Image ID: docker-pullable://
.....
pod 的生命週期
Pod 建立完成後,遵循定義的生命週期,從 Pending 階段開始,如果 pod 內至少一個容器啟動正常,則進入 Running,然後根據 Pod 中的任何容器是否因故障終止而進入 Succeeded 或 Failed 階段,pod 在其生命週期可能處於以下幾種狀態
- Pending: Pod 已被 Kubernetes 叢集接受,但一個或多個容器尚未準備好執行。這包括 Pod 等待排程所花費的時間以及通過網路下載容器映象所花費的時間。
- Running: Pod 已繫結到一個節點,並且所有容器都已建立。至少有一個容器仍在執行,或者正在啟動或重新啟動過程中。
- Succeeded: Pod 中的所有容器都已成功終止,不會重新啟動。
- Failed:Pod 中的所有容器都已終止,並且至少有一個容器因故障而終止。也就是說,容器要麼以非零狀態退出,要麼被系統終止。
- Unknown: 由於某種原因,無法獲取 Pod 的狀態。此階段通常是由於與應該執行 Pod 的節點通訊時出錯而發生。
pod 建立流程
所有的 Kubernetes 元件 Controller, Scheduler, Kubelet 都使用 Watch 機制來監聽 API Server,來獲取物件變化的事件,建立 pod 的大致流程如下:
- 使用者通過 Kubectl 提交 Pod`描述檔案到 API Server;
- API Server 將 Pod 物件的資訊存入 Etcd;
- Pod 的建立會生成事件,返回給 API Server;
- Controller 監聽到事件;
- Pod 如果需要要掛載盤,Controller 會檢查是否有滿足條件的 PV;
- 若滿足條件的 PV,Controller 會繫結 Pod 和 PV,將繫結關係告知 API Server;
- API Server 將繫結資訊寫入 Etcd;
- 生成 Pod Update 事件;
- Scheduler 監聽到 Pod Update 事件;
- Scheduler 會為 Pod 選擇 Node;
- 如有滿足條件的 Node,Scheduler 會繫結 Pod 和 Node,並將繫結關係告知 API Server;
- API Server 將繫結資訊寫入 Etcd;
- 生成 Pod Update 事件;
- Kubelet 監聽到 Pod Update 事件,建立 Pod;
- Kubelet 告知 CRI(容器執行時介面) 下載映象;
- Kubelet 告知 CRI 執行容器;
- CRI 呼叫 Docker 執行容器;
- Kubelet 告知 Volume Manager,將盤掛在到 Node 同時掛載到 Pod;
- CRI 呼叫 CNI(容器網路介面) 配置容器網路;