Kubernetes — 深入解析Pod對象:基本概念(一)


在上一篇文章中,我詳細介紹了 Pod 這個 Kubernetes 項目中最重要的概念。 現在,你已經非常清楚:Pod,而不是容器,才是 Kubernetes 項目中的最小編排單位。將這個設計落實到 API 對象上,容器(Container)就成了 Pod 屬性里的一個普通的字段。那么,一個很自然的問題就是:到底哪些屬性屬於 Pod 對象,而又有哪些屬性屬於 Container 呢?

要徹底理解這個問題,你就一定要牢記我在上一篇文章中提到的一個結論:Pod 扮演的是傳統部署環境里“虛擬機”的角色。這樣的設計,是為了使用戶從傳統環境(虛擬機環境)向 Kubernetes(容器環境)的遷移,更加平滑。

而如果你能把 Pod 看成傳統環境里的“機器”、把容器看作是運行在這個“機器”里的“用戶程序”,那么很多關於 Pod 對象的設計就非常容易理解了。

比如,凡是調度、網絡、存儲,以及安全相關的屬性,基本上是 Pod 級別的。

這些屬性的共同特征是,它們描述的是“機器”這個整體,而不是里面運行的“程序”。比如,配置這個“機器”的網卡(即:Pod 的網絡定義),配置這個“機器”的磁盤(即:Pod 的存儲定義),配置這個“機器”的防火牆(即:Pod 的安全定義)。更不用說,這台“機器”運行在哪個服務器之上(即:Pod 的調度)。

NodeSelector

接下來,我就先為你介紹 Pod 中幾個重要字段的含義和用法。 NodeSelector:是一個供用戶將 Pod 與 Node進行綁定的字段,用法如下所示:

apiVersion: v1
kind: Pod
...
spec:
 nodeSelector:
   disktype: ssd

  

這樣的一個配置,意味着這個 Pod 永遠只能運行在攜帶了“disktype:ssd”標簽(Label)的節點上;否則,它將調度失敗。

HostAliases

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"
...

  

在這個 Pod 的 YAML 文件中,我設置了一組 IP 和 hostname  的數據。這樣,這個 Pod 啟動后,/etc/hosts 文件的內容將如下所示:

cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
...
10.244.135.10 hostaliases-pod
10.1.2.3 foo.remote
10.1.2.3 bar.remote

  

其中,最下面兩行記錄,就是我通過 HostAliases 字段為 Pod 設置的。需要指出的是,在 Kubernetes 項目中,如果要設置 hosts 文件里的內容,一定要通過這種方法。否則,如果直接修改了 hosts 文件的話,在 Pod 被刪除重建之后,kubelet 會自動覆蓋掉被修改的內容。

shareProcessNamespace

除了上述跟“機器”相關的配置外,你可能也會發現,凡是跟容器的 Linux Namespace 相關的屬性,也一定是 Pod 級別的。這個原因也很容易理解:Pod 的設計,就是要讓它里面的容器盡可能多地共享 Linux Namespace,僅保留必要的隔離和限制能力。這樣,Pod 模擬出的效果,就跟虛擬機里程序間的關系非常類似了。

舉個例子,在下面這個 Pod 的 YAML 文件中,我定義了 shareProcessNamespace=true

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  shareProcessNamespace: true
  containers:
  - name: nginx
    image: nginx
  - name: shell
    image: busybox
    stdin: true
    tty: true

  

這就意味着這個 Pod 里的容器要共享 PID Namespace。 而在這個 YAML 文件中,我還定義了兩個容器:一個是 nginx 容器,一個是開啟了 tty 和 stdin 的 shell 容器。

我在前面介紹容器基礎時,曾經講解過什么是 tty 和 stdin。而在 Pod 的 YAML 文件里聲明開啟它們倆,其實等同於設置了 docker run 里的 -it(-i 即 stdin,-t 即 tty)參數。

如果你還是不太理解它們倆的作用的話,可以直接認為 tty 就是 Linux 給用戶提供的一個常駐小程序,用於接收用戶的標准輸入,返回操作系統的標准輸出。當然,為了能夠在 tty 中輸入信息,你還需要同時開啟 stdin(標准輸入流)。

於是,這個 Pod 被創建后,你就可以使用 shell 容器的 tty 跟這個容器進行交互了。我們一起實踐一下:

kubectl create -f nginx.yaml

  

接下來,我們使用 kubectl attach 命令,連接到 shell 容器的 tty 上:

kubectl attach -it nginx -c shell

 

這樣,我們就可以在+shell+容器里執行+ps+指令,查看所有正在運行的進程:

kubectl attach -it nginx -c shell
/ # ps ax
PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    8 root      0:00 nginx: master process nginx -g daemon off;
   14 101       0:00 nginx: worker process
   15 root      0:00 sh
   21 root      0:00 ps ax

  

可以看到,在這個容器里,我們不僅可以看到它本身的 ps ax 指令,還可以看到 nginx 容器的進程,以及 Infra 容器的 /pause 進程。這就意味着,整個 Pod 里的每個容器的進程,對於所有容器來說都是可見的:它們共享了同一個 PID Namespace。

共享宿主機的 Namespace

類似地,凡是 Pod 中的容器要共享宿主機的 Namespace,也一定是 Pod 級別的定義,比如:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  hostNetwork: true
  hostIPC: true
  hostPID: true
  containers:
  - name: nginx
    image: nginx
  - name: shell
    image: busybox
    stdin: true
    tty: true

在這個 Pod 中,我定義了共享宿主機的 Network、IPC 和 PID Namespace。這就意味着,這個 Pod 里的所有容器,會直接使用宿主機的網絡、直接與宿主機進行 IPC 通信、看到宿主機里正在運行的所有進程。

當然,除了這些屬性,Pod 里最重要的字段當屬“Containers”了。而在上一篇文章中,我還介紹過“Init Containers”。其實,這兩個字段都屬於 Pod  對容器的定義,內容也完全相同,只是 Init Containers 的生命周期,會先於所有的 Containers,並且嚴格按照定義的順序執行。

Kubernetes 項目中對 Container 的定義,和 Docker 相比並沒有什么太大區別。我在前面的容器技術概念入門系列文章中,和你分享的 Image(鏡像)、Command(啟動命令)、workingDir(容器的工作目錄)、Ports(容器要開發的端口),以及 volumeMounts(容器要掛載的 Volume)都是構成 Kubernetes 項目中 Container 的主要字段。不過在這里,還有這么幾個屬性值得你額外關注。

首先,是 ImagePullPolicy 字段。它定義了鏡像拉取的策略。而它之所以是一個 Container 級別的屬性,是因為容器鏡像本來就是 Container 定義中的一部分。

ImagePullPolicy 的值默認是 Always,即每次創建 Pod 都重新拉取一次鏡像。另外,當容器的鏡像是類似於 nginx 或者 nginx:latest 這樣的名字時,ImagePullPolicy 也會被認為 Always。

而如果它的值被定義為 Never 或者 IfNotPresent,則意味着 Pod 永遠不會主動拉取這個鏡像,或者只在宿主機上不存在這個鏡像時才拉取。

其次,是 Lifecycle 字段。它定義的是 Container Lifecycle Hooks。顧名思義,Container Lifecycle Hooks 的作用,是在容器狀態發生變化時觸發一系列“鈎子”。我們來看這樣一個例子:

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 操作的執行,是同步的。所以,它會阻塞當前的容器殺死流程,直到這個 Hook 定義操作完成之后,才允許容器被殺死,這跟 postStart 不一樣。

所以,在這個例子中,我們在容器成功啟動之后,在 /usr/share/message 里寫入了一句“歡迎信息”(即 postStart 定義的操作)。而在這個容器被刪除之前,我們則先調用了 nginx 的退出指令(即 preStop 定義的操作),從而實現了容器的“優雅退出”。

在熟悉了 Pod 以及它的 Container 部分的主要字段之后,我再和你分享一下這樣一個的 Pod 對象在 Kubernetes 中的生命周期。 Pod 生命周期的變化,主要體現在 Pod API 對象的Status 部分,這是它除了 Metadata 和 Spec 之外的第三個重要字段。其中,pod.status.phase,就是 Pod 的當前狀態,它有如下幾種可能的情況: 

  • 這個狀態意味着,Pod 的 YAML 文件已經提交給了 Kubernetes,API 對象已經被創建並保存在 Etcd 當中。但是,這個 Pod 里有些容器因為某種原因而不能被順利創建。比如,調度不成功。
  • Running,這個狀態下,Pod 已經調度成功,跟一個具體的節點綁定。它包含的容器都已經創建成功,並且至少有一個正在運行中。
  • Succeeded,這個狀態意味着,Pod 里的所有容器都正常運行完畢,並且已經退出了。這種情況在運行一次性任務時最為常見。
  • Failed,這個狀態下,Pod 里至少有一個容器以不正常的狀態(非 0 的返回碼)退出。這個狀態的出現,意味着你得想辦法 Debug 這個容器的應用,比如查看 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”狀態后,你一定要能迅速做出反應,根據它所代表的異常情況開始跟蹤和定位,而不是去手忙腳亂地查閱文檔。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM