在上一篇文章中,我詳細介紹了 Pod 這個 Kubernetes 項目中最重要的概念。 如今,你已經很是清楚:Pod,而不是容器,纔是 Kubernetes 項目中的最小編排單位。將這個設計落實到 API 對象上,容器(Container)就成了 Pod 屬性裏的一個普通的字段。那麼,一個很天然的問題就是:到底哪些屬性屬於 Pod 對象,而又有哪些屬性屬於 Container 呢?node
要完全理解這個問題,你就必定要牢記我在上一篇文章中提到的一個結論:Pod 扮演的是傳統部署環境裏「虛擬機」的角色。這樣的設計,是爲了使用戶從傳統環境(虛擬機環境)向 Kubernetes(容器環境)的遷移,更加平滑。nginx
而若是你能把 Pod 當作傳統環境裏的「機器」、把容器看做是運行在這個「機器」裏的「用戶程序」,那麼不少關於 Pod 對象的設計就很是容易理解了。docker
好比,凡是調度、網絡、存儲,以及安全相關的屬性,基本上是 Pod 級別的。shell
這些屬性的共同特徵是,它們描述的是「機器」這個總體,而不是裏面運行的「程序」。好比,配置這個「機器」的網卡(即:Pod 的網絡定義),配置這個「機器」的磁盤(即:Pod 的存儲定義),配置這個「機器」的防火牆(即:Pod 的安全定義)。更不用說,這臺「機器」運行在哪一個服務器之上(即:Pod 的調度)。小程序
接下來,我就先爲你介紹 Pod 中幾個重要字段的含義和用法。 NodeSelector:是一個供用戶將 Pod 與 Node進行綁定的字段,用法以下所示:api
apiVersion: v1 kind: Pod ... spec: nodeSelector: disktype: ssd
這樣的一個配置,意味着這個 Pod 永遠只能運行在攜帶了「disktype:ssd」標籤(Label)的節點上;不然,它將調度失敗。安全
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 會自動覆蓋掉被修改的內容。
除了上述跟「機器」相關的配置外,你可能也會發現,凡是跟容器的 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。
相似地,凡是 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 對象的 Status 字段,還能夠再細分出一組 Conditions。這些細分狀態的值包括:PodScheduled、Ready、Initialized,以及 Unschedulable。它們主要用於描述形成當前 Status 的具體緣由是什麼。 好比,Pod 當前的 Status 是 Pending,對應的 Condition 是 Unschedulable,這就意味着它的調度出現了問題。
而其中,Ready 這個細分狀態很是值得咱們關注:它意味着 Pod 不只已經正常啓動(Running 狀態),並且已經能夠對外提供服務了。這二者之間(Running 和 Ready)是有區別的,你不妨仔細思考一下。 Pod 的這些狀態信息,是咱們判斷應用運行狀況的重要標準,尤爲是 Pod 進入了非「Running」狀態後,你必定要能迅速作出反應,根據它所表明的異常狀況開始跟蹤和定位,而不是去手忙腳亂地查閱文檔。