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

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

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

而若是你能把 Pod 當作傳統環境裏的「機器」、把容器看做是運行在這個「機器」裏的「用戶程序」,那麼不少關於 Pod 對象的設計就很是容易理解了。docker

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

這些屬性的共同特徵是,它們描述的是「機器」這個總體,而不是裏面運行的「程序」。好比,配置這個「機器」的網卡(即:Pod 的網絡定義),配置這個「機器」的磁盤(即:Pod 的存儲定義),配置這個「機器」的防火牆(即:Pod 的安全定義)。更不用說,這臺「機器」運行在哪一個服務器之上(即:Pod 的調度)。小程序

NodeSelector

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

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 文件的內容將以下所示:post

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」狀態後,你必定要能迅速作出反應,根據它所表明的異常狀況開始跟蹤和定位,而不是去手忙腳亂地查閱文檔。

相關文章
相關標籤/搜索