Pod 是在 Kubernetes 中創建和管理的、最小的可部署的計算單元,是最重要的對象之一。一個 Pod 中包含一個或多個容器,這些容器在 Pod 中能夠共享網絡、存儲等環境。
學習 Kubernetes,Pod 是最重要最基本的知識,本章將介紹什么是 Pod、Pod 的結構等,並練習創建 Pod。
Pod 基礎知識
創建 Pod
創建一個 Pod 很簡單,示例命令如下:
kubectl run nginxtest --image=nginx:latest --port=80
nginxtest
為 Pod 名稱,並且為其映射端口為 80。
但是一般不會直接創建 Pod,因為手動創建的 Pod 不被 “托管” 起來,如果 Pod 故障或者其他原因消失了,不會自動恢復,而且 kubectl run
提供的參數有限。
Kubernetes Pod 中的所有容器共享一個相同的 Linux 命名空間(network、UTS、IPC),而不是每個容器一個命名空間,因此 Pod 中的容器,網絡、主機名稱相同、IP 地址相同。據說在新版本的 Kubernetes 和 Docker 中, PID 命名空間也可以設置為相同的。由於 Mount、User 命名空間不共享,因此在容器中,文件系統和用戶是隔離的。
另外我們也可以使用 yaml 方式定義 Pod,然后創建它。使用 YAML 文件定義需要的 Pod 格式示例如下:
apiVersion: v1 kind: Pod metadata: name: nginxtest spec: containers: - image: nginx:latest name: nginxtest ports: - containerPort: 80 protocol: TCP
但是描述 Pod 的 YAML 文件,包含哪些內容?格式如何?每個字段是什么意思?我們不可能記住所有 Kubernetes 對象的 YAML 文件的每個字段吧?
還好,Kuberentes 提供了 kubectl explain
命令,可以幫助我們快速了解創建一個對象的 YAML 需要哪幾部分內容。例如創建 Pod 的 YAML 定義如下:
oot@master:~# kubectl explain pod KIND: Pod VERSION: v1 DESCRIPTION: Pod is a collection of containers that can run on a host. This resource is created by clients and scheduled onto hosts. FIELDS: apiVersion <string> APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources kind <string> Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds metadata <Object> Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata spec <Object> Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status status <Object> Most recently observed status of the pod. This data may not be up to date. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
如果要查更深一層的字段,則可以:
root@master:~# kubectl explain pod.kind KIND: Pod VERSION: v1 FIELD: kind <string> DESCRIPTION: Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
[Info] 提示
此方式查詢到的文檔,其列舉的字段不一定都是 Pod YAML 所需要的,還需要讀者多練習多學習,從文檔中補充需要的信息。
了解 Pod
Pod 是 Kubernetes 中調度資源的最小單位,一個 Pod 中可以包含多個容器,Pod 中的容器被打包在一起作為一個整體, Pod 中的容器不會被分配到不同節點中,它們一定被部署到同一個節點中。
每個 Pod 有且只有一個唯一的 IP 地址,通過 kubectl get pod {pod名稱} -o wide
可以查詢到。
root@master:~# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginxtest 1/1 Running 0 12m 10.32.0.2 slave1 <none> <none>
[Info] 提示
-o wide
可以查看更多對象的信息,不只是 Pod,其他對象也可以加上去。
Pod 之間可以通過 IP 訪問,這個 IP 可以 Ping 通。
Pod 共享網絡和存儲
我們可以把一個 Pod 形容為一個虛擬主機。在 Pod 中,所有容器(進程)都在一個主機上,我們知道,同一個主機上的所有進程共享着主機網絡,多個進程自然不能同時占用一個端口,否則會沖突。容器共享 Pod 中的網絡,所以 Pod 中的容器也能夠通過 localhost 相互訪問,因此 Pod 中的 容器(進程)不能暴露/使用相同的端口。
通過 Pod 和 locahost
,解決了 高度耦合的容器間通信問題。
Docker 有一個 Contaner 模式,能夠讓多個容器共享一個 Network Namespace,可以讓一個容器和另一個容器共享 IP、端口范圍。
假如容器 A 已經創建起來,那么容器 A 會創建一個虛擬網卡;然后指定 容器 B 與容器 A 共享網絡,那么兩者就可以直接通過 localhost 進行通訊。
在 Kubernetes 中,當創建 Pod 時,會先啟動一個 pause 容器,然后 Pod 中我們定義的容器會以 Container 模式共享 pause 中的網絡。
docker ps | grep pause
當然,pause 不只是實現容器的網絡互通,還有其它功能。
在第一章的 Docker 介紹中,談到過此類容器,其目的是讓其他容器聯通起來, pause 在 Pod 的網絡中相當於交換機。
Pod 中的容器是部分隔離的,每個容器都有自己的文件系統,各自的文件被隔離,容器不能訪問或修改其它容器的文件。
為了讓多個 容器之間能夠共享文件,可以使用卷,把同一個卷映射到容器中。
划分 Pod 和容器
容器中應只包含一個進程,或進程和創建的子進程。如果在同一個容器中包含多個進程,那么需要同時管理進程的啟動、日志等,一個進程崩潰時,容易影響到另一個進程。由於多個進程都會記錄信息到標准輸出中(如控制台輸出),容器日志會合在一起,可能會導致出現問題難以排查。
一個容器只應該運行一個進程,但是他們放到一個 Pod 中就行了嗎?例如程序和數據庫,在設計時應該放到同一個 Pod,還是單獨不同的 Pod?接下來我們簡單討論一下這個問題,限於經驗和技術水平,筆者的論點可能不到位,讀者可以多參考一下別的文章,了解如何設計 這些架構。
下面以 Web 程序和數據庫舉例。
耦合
使用 Pod/容器 的原因,是為了讓不同服務能夠降低耦合,能夠隔離環境,如果程序跟數據庫放在一起,是否能夠有足夠的隔離程度?如果 Web 跟 數據庫放在同一個 Pod,此時 web 跟數據庫的實例(容器)數量是 1:1。對於 Kubernests 來說,Pod 是最小單位,Kbernetes 不能橫向擴容單個容器,因此擴容的最小單位是 Pod,多個容器必須捆綁在一起。同時 Pod 中的所有容器都使用同一機器的資源。在同一個 Pod 中的容器,在生命周期、計算機資源(內存、CPU)、實例數量、網絡等都會耦合在一起。
請參考 https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/
訪問壓力
一般來說,Web 是要被外界訪問的,但是數據庫為了安全,應當避免能夠公網訪問,只有處於集群中的程序或客戶端才能訪問數據庫。同時Web的訪問是直接面向用戶的,訪問量肯定比數據庫的訪問量大得多,而且數據庫需要的存儲空間比web大得多,那么兩者使用的計算資源並不相近。
Pod 可以使用服務器資源,當服務器壓力過大時,當太多用戶訪問 Web 時,Web就要考慮擴容實例,可以在其它節點上部署相同的 Pod(擴容),降低單節點訪問壓力。而一個數據庫實例能夠支持多個 Web 程序同時訪問,那么數據庫實例有必要跟 Web 放在同一個 Pod 中,保持 1:1的實例數量?
故障恢復
在 Kubernetes 中,容器應當是無狀態的,也就是說容器或容器中的進程掛了,Kubernetes 可以快速在其它地方再創建一個 Pod ,啟動容器,維持一定數量的 Pod 實例。對於 Web 來說,只要配置文件和數據庫數據在,再啟動一個 Web 容器,結果是一樣的,流水的程序鐵打的數據,只要數據在,可以隨時啟動 Web 程序,很容易恢復服務。但是數據庫卻不一定,數據庫的運維比 Web 程序復雜得多,我們要考慮數據的安全性和可用性,當容器甚至節點服務器掛了后、磁盤損壞等,如何恢復數據庫。數據庫的維護不覺得。
兩者的維護難度不在同一水平上,此時我們要考慮兩者放在不同的 Pod 中。(實際上很少將數據庫放在容器中,一般都是裸機部署)。
其中負載均衡是通過 Ingress 和 Service 實現的,后面的章節會學習到。
何時使用多個容器
前面提到 Web 跟數據庫,應當划分在不同的 Pod 中,類似地,對於微服務中的不同服務或模塊,也應當放在不同的 Pod 中。微服務架構、容器化,並不是那么容易,例如,對於前后端分離的項目,前后端文件放在同一個 容器中還是同一個 Pod 中還是不同 Pod 中?在設計中我們要考慮很多問題。
對於單體 Web 來說,一個程序中包含了所有服務,那么 Web 完全可以托管前端靜態文件,前端文件跟后端程序打包在一起即可。例如 PHP、ASP.NET Core 等使用 wwwroot、www 等 目錄存儲靜態文件。
如果是一個較大的網站,網站使用了多個微服務,則前端更可能放到一個 Pod 中,用戶訪問前端頁面,然后前端根據訪問的模塊,自動訪問不同的服務。
如果前端和后端文件需要頻繁發布,兩者的發布版本分開工作,則為了避免一方等待另一方發布,或者從 Devops 角度,前端和和后端文件可以放在不同容器中,然后通過存儲卷,兩個容器共享文件。
如果一個 Pod 中,包含一個主進程和多個輔助進程,則可以使用一個 Pod 部署多個 容器,多個容器之間緊密聯系。
具體怎么設計,需要根據實際情況考慮。
Pod 生命周期
當 Pod 被分配到某個節點時, Pod 會一直在該節點運行,直到停止或被終止,Pod 在整個生命周期中只會被調度一次。
Pod 的整個生命周期可能有四種狀態:
- Pending,嘗試啟動容器,如果容器正常啟動,則進入下一個階段;
- Running,處於運行狀態;
- Succeeded、Failed,正常結束或故障等導致容器結束;
- Unknown,因為某些原因無法取得 Pod 的狀態。
我們可以創建一個 Deployment,查看當前正在發生的事件:
kubectl create deployment nginx --image=nginx:latest --replicas=3
kubectl get events
通過 kubectl describe pod {pod名稱}
查看一個 Pod 的事件:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m22s default-scheduler Successfully assigned default/nginx-* to instance-2
Normal Pulling 3m19s kubelet Pulling image "nginx:latest"
Normal Pulled 3m17s kubelet Successfully pulled image "nginx:latest" in 2.570763819s
Normal Created 3m17s kubelet Created container nginx
Normal Started 3m16s kubelet Started container nginx
上面查詢到的事件均發生在 Pod 的 Pending
狀態,我們可以看到在這個階段中,Pod 被調度,然后拉取鏡像、啟動容器,如果容器啟動成功,Pod 便會進入 Running
狀態。
事件記錄保存在 etcd 中。
在 Kubernetes 中,Pod 被認為是相對的臨時性實體,而不是長期存在的。由於 Pod 本身不具有治愈能力,如果 節點故障或者節點資源耗盡、節點被維護、Pod 被驅逐等,那么 Pod 無法在節點上繼續存活。無論 Pod 因為何種原因被刪除,在 Pod 中的網絡、存儲卷等,也會被銷毀,新的 Pod 被創建時,相關的網絡、存儲卷也會被重建。
【圖來源:https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/】
[Info] 提示
由於 Pod 是臨時性的,為了保障服務能夠在 Pod 掛了后自動重建,可以使用 Deployement、Daemon 等對象管理 Pod,這些對象被稱為 控制器。
在刪除 Pod 時,Kubernetes 會終止 Pod 中的所有容器,會向容器中的進程發生 SIGTERM 信號,等待進程的正常關閉,所以 Pod 可能不會被馬上刪除,當然如果進程不能正常關閉,Kubernetes 最多等待 30s,不然會使用 SIGKILL 殺死進程。
容器重啟策略
這是一個簡單的 Pod 的 YAML 定義:
apiVersion: v1 kind: Pod metadata: labels: app: nginx spec: containers: - image: nginx:latest imagePullPolicy: Always name: nginx ... ... restartPolicy: Always
在這個 YAML 中,有兩個*Policy
,取值有 Always、OnFailure 和 Never 三種,它們代表了某種生命周期策略。
對於每個容器,都可以設置 imagePullPolicy
,指示在拉取鏡像時如果失敗,是否進行重試。
在 spec
中,有個 restartPolicy
字段,其值默認為 Always
,指示 Pod 中所有容器的重啟動作,但是在 Deployment
、StatefulSet
、DaemonSet
等控制器中,restartPolicy
只支持 Always ,不支持 OnFailure 和 Never 。
[Info] 提示
如果容器啟動失敗,重試間隔會越來越長。 kubelet 會在 10s 后重試第一次,如果還是失敗,第二次 20s 后再重試;按照 10s、20s、40s、80s ... 的間隔重試,但最長不超過 5 分鍾。如果容器被成功運行且運行了 10 分鍾以上,那么計時器會被重置,下次出現故障時,按照 10s、20s 的間隔時間重試。
如果單獨創建 Pod,並且設置了 restartPolicy: Always
,那么 Pod 會一直停留在此節點上,如果 容器故障,Pod 可能會無限重試。
如果我們使用 Deployment
、StatefulSet
、DaemonSet
等控制器管理 Pod,這些控制器能夠處理 Pod/副本 的管理、上線,並且在 Pod 失效時提供治愈能力,當,當然,這些東西后面再提。
節點上的 Pod 停止工作時,可以創建替代性的 Pod, Pod 被調度到一個健康的節點執行。
[Info] 提示
為什么要使用控制器管理 Pod 呢?
舉個例子,筆者有個朋友,寫了個 A 程序,這個程序能夠在執行后,關閉計算機(關機)。本來是需要的時候執行一次,但是后面配置了一個守護進程,這個守護進程會自動啟動 A 程序。這樣出現了無限循環,開機 -> 啟動守護進程 -> A 出現 -> 關機,結果這個朋友的電腦無法正常開機,一開機就被關機。
這種情況跟單獨的 Pod 部署類似,很容易因為某些原因無限重試,並且一直駐留在節點上。因為重試是在原來的基礎上進行重試,使用原來的文件、數據、網絡等。控制器則可以將其重置,恢復
出廠設置
。如果一個節點上的 CPU、內存不夠用了,那么容器有可能因為資源不足,無法啟動,導致無限重試;如果使用控制器,控制器會在有充足資源的、健康的節點上重建 Pod。
Pod 的部署和管理
但是一般很少直接創建或管理 Pod,一般使用控制器來管理 Pod。下面列出一些控制器,在后面的學習中我們會一步步深入學習。
- Deployment
- StatefulSet
- DaemonSet
單獨創建 Pod ,一般用於臨時調試的等。
創建 Pod
在 Kubernetes 中,所有對象都可以使用 YAML 表示。我們可以使用 YAML 來定義 Pod 對象,一個 Pod 的基本模板:
apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx:latest
如果要映射網絡端口,則 YAML 文件為:
apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80 protocol: TCP
由於 Pod 中的容器共享網絡,技術不加上
ports
,照樣能夠訪問。在 YAML 中多寫一些,有助於其他人快速了解此 Pod 的定義信息。
將上面的 YAML 內容復制到 pod.yaml 中,然后執行命令應用 YAML :
kubectl apply -f pod.yaml
# 或 kubectl create -f pod.yaml
使用
kubectl run
命令也可以創建 pod,命令示例:kubectl run nginx-pod --image=nginx:latest
覆蓋容器命令
在 Pod 中可以配置容器的一些信息,也可以替換容器的啟動命令,其配置格式如下:
spec: containers: - name: nginx image: nginx:latest command: ["/bin/command"] args: ["arg1","arg2","arg3"]
Pod 管理
輸入 kubectl get pods
可以查看名為 default 命名空間的 Pod。輸入 kubectl get pods -o wide
可以查看多幾個字段的 Pod 信息,輸入 kubectl describe pods
可以查看每個 Pod 的所有詳細信息。
root@instance-2:~# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx 1/1 Running 0 11s 192.168.56.3 instance-2 <none> <none>
可以看到 Pod 在第二個節點上部署,其 IP 為 192.168.56.3
。Pod 的 IP 只能在被部署服務的節點上訪問,不同節點不能訪問其的 Pod。
nginx 默認使用了 80 端口,因此通過 192.168.56.3:80
,可以訪問到容器中的 nginx 服務。
[Error] 注意
只能在 Pod 部署的節點上連接到此 IP。如果要在不同的節點訪問 Pod,需要安裝 CNI 網絡插件,如 flannel、calico、weave 等,在 2.2、2.3 中有介紹。
查看日志
在 Docker 中,我們可以通過 docker logs {容器id}
來查看容器中的日志,這些日志是進程打印到控制器的標准輸出,例如 C# 的 Console.Write
、C 語言的 printf
、Go 語言的 fmt.Print
,Docker 的 本地日志驅動會捕獲容器的 stdout/stderr 輸出記錄驅動器。
Docker 日志默認限制日志大小為 10 MB ,每天會輪替一個日志文件,並使用自動壓縮來減少磁盤文件大小。
Docker 日志驅動程序使用基於文件的存儲。文件格式和存儲機制被設計為由 Docker 守護進程獨占訪問,不應被外部工具使用,因為在未來的版本中實現可能會發生變化。
筆者沒有明確查找到 Docker 的日志具體限制情況,以上內容來自 https://docs.docker.com/config/containers/logging/local/ 的參考資料。
在 Kubernetes 中,也可以通過命令快速查看 Pod 中的容器的日志。
如果 Pod 中只有一個容器,則直接使用類似命令即可:
kubectl logs {pod名稱}
如果 Pod 中有多個容器,則需要指定容器名稱:
kubectl logs {pod名稱} -c {容器名稱}
kubectl logs
只能獲取當前正在運行的 Pod 的日志,如果 Pod 被刪除,所有日志記錄都會被刪除。
查看、維護 Pod 狀態,比較常用的命令有:
- kubectl get - 列出對象資源,如
kubectl get pods
;- kubectl describe - 顯示有關資源的詳細信息,如
kubectl describe pod nginxtest
;- kubectl logs - 打印 pod 和其中容器的日志;
- kubectl exec - 在 pod 中的容器上執行命令,格式為
kubectl exec {pod名稱} -c {容器名稱} -- {要執行的命令}
,--
用於分隔命令,其后面的參數均表示要傳遞進容器的命令。