在先前的文章中,我詳細介紹了 Pod 的概念,這是 Kubernetes 專案中最重要的一環。在今天的文章中,我將分享更多關於 Pod 物件的細節。
Pods vs. 虛擬機器
到現在你應該非常清楚,在 Kubernetes 專案中,最小的編排單元是 Pod 而不是容器。這個設計反映在 API 物件中,容器只是 Pod 屬性裡的一個普通欄位。自然會產生一個問題:哪些屬性屬於 Pod 物件,哪些屬於 Container?
Pod 扮演了傳統部署環境中「虛擬機器」的角色。這個設計是為了讓從傳統環境(虛擬機器環境)過渡到 Kubernetes(容器環境)的過程更加順暢。
如果我們把 Pod 當作傳統環境中的「機器」,而容器是運行在這台「機器」上的「使用者程式」,那麼 Pod 物件的許多設計就會變得容易理解。
例如,與排程、網路、儲存和安全相關的屬性,本質上都是 Pod 層級的。這些屬性的共同特點是描述整個「機器」,而不是機器內部運行的「程式」。
比如配置「機器」的網路卡(即 Pod 的網路定義)、配置「機器」的磁碟(即 Pod 的儲存定義)、配置「機器」的防火牆(即 Pod 的安全定義)。更不用說這台「機器」要運行在哪個伺服器上(即 Pod 的排程)。
Kubernetes YAML
在 Kubernetes 中,我們透過宣告式檔案來定義 Pod 資源。
NodeSelector:這是一個允許使用者將 Pod 綁定到特定 Node 的欄位,如下所示:
apiVersion: v1
kind: Pod
...
spec:
nodeSelector:
disktype: ssd
這樣的配置意味著這個 Pod 只能運行在帶有 “disktype: ssd” 標籤的節點上,否則排程會失敗。
NodeName:一旦 Pod 的這個欄位被賦值,Kubernetes 專案就會認為該 Pod 已經被排程,而排程的結果就是指定的節點名稱。因此,這個欄位通常由排程器設定,但使用者也可以設定它來「欺騙」排程器,不過這種做法通常只在測試或除錯時使用。
HostAliases:定義 Pod 的 hosts 檔案內容(例如 /etc/hosts),如下所示:
apiVersion: v1
kind: Pod
...
spec:
hostAliases:
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
...
需要注意的是,在 Kubernetes 專案中,如果你想設定 hosts 檔案的內容,必須透過這個方法。否則,如果你直接修改 hosts 檔案,kubelet 會在 Pod 被刪除並重新建立後自動覆蓋修改的內容。
除了上述「機器」相關的配置外,你可能還會發現,任何與容器 Linux Namespace 相關的屬性也都是 Pod 層級的。這也容易理解:Pod 的設計是為了讓其中的容器盡可能共享更多的 Linux Namespace,只保留必要的隔離和限制能力。這樣一來,Pod 模擬出來的效果與虛擬機器中程式之間的關係非常相似。
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
這是來自 Kubernetes 官方文件的 Pod YAML 檔案。實際上非常簡單,只是定義了一個使用 nginx 映像的容器。然而,在這個 YAML 檔案的 containers 部分中,你會看到容器設定了 postStart 和 preStop 參數。
這是什麼意思呢?
先說 postStart,它指的是容器啟動後立即執行的操作。需要明確的是,postStart 定義的操作是在 Docker 容器的 ENTRYPOINT 之後執行的,但並不嚴格保證執行順序。也就是說,當 postStart 啟動時,ENTRYPOINT 可能還沒有結束。
當然,如果 postStart 執行超時或發生錯誤,Kubernetes 會在那個 Pod 的 Events 中報告錯誤訊息,表示容器啟動失敗,導致 Pod 也會處於失敗狀態。
同樣地,preStop 的執行時機是在容器被殺死之前(例如收到 SIGKILL 訊號時)。需要強調的是,preStop 操作的執行是同步的。因此,它會阻塞當前的容器殺死過程,直到定義的操作完成,這與 postStart 不同。
所以,在這個範例中,容器成功啟動後,它會將一個「問候訊息」寫入 /usr/share/message(即 postStart 定義的操作)。而在這個容器被刪除之前,我們先呼叫 nginx 的退出指令(即 preStop 定義的操作),從而實現容器的「優雅關閉」。
熟悉了 Pod 及其 Container 部分的主要欄位之後,我將分享這類 Pod 物件在 Kubernetes 中的生命週期。
Pod 生命週期的變化主要體現在 Pod API 物件的 Status 部分,這是它除了 Metadata 和 Spec 之外的第三個重要欄位。其中,pod.status.phase 表示 Pod 的當前狀態,可能的情況如下:
-
Pending。此狀態表示 Pod 的 YAML 檔案已提交給 Kubernetes,API 物件已建立並儲存在 Etcd 中。但是,Pod 中的某些容器因故無法順利建立。例如排程失敗。
-
Running。在此狀態下,Pod 已成功排程並綁定到特定節點。其包含的所有容器都已成功建立,且至少有一個正在運行。
-
Succeeded。此狀態表示 Pod 中的所有容器都已運行完成並退出。這種情況最常見於執行一次性任務時。
-
Failed。在此狀態下,Pod 中至少有一個容器以異常狀態退出(非零回傳碼)。出現此狀態表示你需要想辦法除錯容器中的應用程式,例如檢查 Pod 的 Events 和日誌。
-
Unknown。這是一個異常狀態,表示 Pod 的狀態無法由 kubelet 持續回報給 kube-apiserver,很可能是 master 與 Kubelet 之間的通訊出了問題。
此外,Pod 物件的 Status 欄位可以進一步細分為一組 Conditions。這些詳細的狀態值包括:PodScheduled、Ready、Initialized 和 Unschedulable。它們主要用於描述當前 Status 的具體原因。
例如,如果 Pod 的當前 Status 是 Pending,而對應的 Condition 是 Unschedulable,則表示其排程有問題。
其中,Ready 子狀態特別值得我們關注:它意味著 Pod 不僅正常啟動(處於 Running 狀態),而且已經準備好對外提供服務。這兩個狀態(Running 和 Ready)之間存在差異,你可以仔細思考一下。
Pod 的這些狀態訊息是我們判斷應用程式運行狀況的重要依據,尤其是當 Pod 進入非 “Running” 狀態時,你必須能夠快速反應,根據所代表的異常情況開始追蹤和定位,而不是慌亂地查閱文件。
總結
在今天的文章中,我詳細說明了 Pod API 物件,介紹了 Pod 的核心使用方法,並分析了 Pod 和 Container 在欄位上的異同。希望這些說明能幫助你更好地理解和記住 Pod YAML 中的核心欄位及其精確含義。
事實上,Pod API 物件是整個 Kubernetes 系統中最核心的概念,也將在我後續說明的控制器中使用。
