k8s四 | 深入理解Pod資源對象

這篇文章我們來深入了解Pod的基本概念及相關使用

一. Pod的設計思路

首先Pod是 Kubernetes 項目中最小的 API 對象,而Pod也是由容器組組成的。Pod 里的所有容器,共享的是同一個 Network Namespace,并且可以聲明共享同一個 Volume。凡是調度、網絡、存儲,以及安全相關的屬性,基本上是 Pod 級別的,而容器就成為Pod屬性中一個普通的字段定義。

我們可以這樣理解:容器是相當于未來云計算的進程,鏡像是安裝包,Pod則是傳統環境的機器,k8s是操作系統。pod是一個小家庭,它把密不可分的家庭成員(container)聚在一起,Infra container則是家長,掌管家中共通資源,家庭成員通過sidecar方式互幫互助,其樂融融~。

二. Pod的網絡通信Infra容器

在 Kubernetes 項目里,Pod 的實現需要使用一個中間容器,這個容器叫作 Infra 容器。在這個 Pod 中,Infra 容器永遠都是第一個被創建的容器,而其他用戶定義的容器,則通過 Join Network Namespace 的方式,與 Infra 容器關聯在一起。這樣的組織關系,可以用下面這樣一個示意圖來表達:


image.png

如上圖所示,這個 Pod 里有兩個用戶容器 A 和 B,還有一個 Infra 容器。很容易理解,在 Kubernetes 項目里,Infra 容器一定要占用極少的資源,所以它使用的是一個非常特殊的鏡像,叫作:k8s.gcr.io/pause。這個鏡像是一個用匯編語言編寫的、永遠處于“暫停”狀態的容器,解壓后的大小也只有 100~200 KB 左右。而在 Infra 容器“Hold 住”Network Namespace 后,用戶容器就可以加入到 Infra 容器的 Network Namespace 當中了

  • 這也就意味著,對于 Pod 里的容器 A 和容器 B 來說:
  • 它們可以直接使用 localhost 進行通信;
  • 它們看到的網絡設備跟 Infra 容器看到的完全一樣;
  • 一個 Pod 只有一個 IP 地址,也就是這個 Pod 的 Network Namespace 對應的 IP 地址;
  • 當然,其他的所有網絡資源,都是一個 Pod 一份,并且被該 Pod 中的所有容器共享;
  • Pod 的生命周期只跟 Infra 容器一致,而與容器 A 和 B 無關。

而對于同一個 Pod 里面的所有用戶容器來說,它們的進出流量,也可以認為都是通過 Infra 容器完成的。這一點很重要,因為將來如果你要為 Kubernetes 開發一個網絡插件時,應該重點考慮的是如何配置這個 Pod 的 Network Namespace,而不是每一個用戶容器如何使用你的網絡配置,這是沒有意義的。

這就意味著,如果你的網絡插件需要在容器里安裝某些包或者配置才能完成的話,是不可取的:Infra 容器鏡像的 rootfs 里幾乎什么都沒有,沒有你隨意發揮的空間。當然,這同時也意味著你的網絡插件完全不必關心用戶容器的啟動與否,而只需要關注如何配置 Pod,也就是 Infra 容器的 Network Namespace 即可。

三. 伴生容器與容器初始化

  • sidecar: 伴生容器,指的就是我們可以在一個 Pod 中,啟動一個輔助容器,來完成一些獨立于主進程(主容器)之外的工作。
  • Init Container:初始化容器 就是用來做初始化工作的容器,可以是一個或者多個,如果有多個的話,這些容器會按定義的順序依次執行,只有所有的Init Container執行完后,主容器才會被啟動。我們知道一個Pod里面的所有容器是共享數據卷和網絡命名空間的,所以Init Container里面產生的數據可以被主容器使用到的。

例如:我們現在有一個 Java Web 應用的 WAR 包,它需要被放在 Tomcat 的 webapps 目錄下運行起來。假如,你現在只能用 Docker 來做這件事情,那該如何處理這個組合關系呢?

  • 一種方法是,把 WAR 包直接放在 Tomcat 鏡像的 webapps 目錄下,做成一個新的鏡像運行起來??墒牵@時候,如果你要更新 WAR 包的內容,或者要升級 Tomcat 鏡像,就要重新制作一個新的發布鏡像,非常麻煩。
  • 另一種方法是,你壓根兒不管 WAR 包,永遠只發布一個 Tomcat 容器。不過,這個容器的 webapps 目錄,就必須聲明一個 hostPath 類型的 Volume,從而把宿主機上的 WAR 包掛載進 Tomcat 容器當中運行起來。不過,這樣你就必須要解決一個問題,即:如何讓每一臺宿主機,都預先準備好這個存儲有 WAR 包的目錄呢?這樣來看,你只能獨立維護一套分布式存儲系統了。

實際上,有了 Pod 之后,這樣的問題就很容易解決了。我們可以把 WAR 包和 Tomcat 分別做成鏡像,然后把它們作為一個 Pod 里的兩個容器“組合”在一起。這個 Pod 的配置文件如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: javaweb-2
spec:
  initContainers:
  - image: geektime/sample:v2
    name: war
    command: ["cp", "/sample.war", "/app"]
    volumeMounts:
    - mountPath: /app
      name: app-volume
  containers:
  - image: geektime/tomcat:7.0
    name: tomcat
    command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
    volumeMounts:
    - mountPath: /root/apache-tomcat-7.0.42-v2/webapps
      name: app-volume
    ports:
    - containerPort: 8080
      hostPort: 8001 
  volumes:
  - name: app-volume
    emptyDir: {}

在這個 Pod 中,我們定義了兩個容器,第一個容器使用的鏡像是geektime/sample:v2,這個鏡像里只有一個 WAR 包(sample.war)放在根目錄下。而第二個容器則使用的是一個標準的 Tomcat 鏡像。

不過,你可能已經注意到,WAR 包容器的類型不再是一個普通容器,而是一個 Init Container 類型的容器。在 Pod 中,所有 Init Container 定義的容器,都會比 spec.containers 定義的用戶容器先啟動。并且,Init Container 容器會按順序逐一啟動,而直到它們都啟動并且退出了,用戶容器才會啟動。

所以,這個 Init Container 類型的 WAR 包容器啟動后,我執行了一句"cp /sample.war /app",把應用的 WAR 包拷貝到 /app 目錄下,然后退出。而后這個 /app 目錄,就掛載了一個名叫 app-volume 的 Volume。接下來就很關鍵了。Tomcat 容器,同樣聲明了掛載 app-volume 到自己的 webapps 目錄下。所以,等 Tomcat 容器啟動時,它的 webapps 目錄下就一定會存在 sample.war 文件:這個文件正是 WAR 包容器啟動時拷貝到這個 Volume 里面的,而這個 Volume 是被這兩個容器共享的。

像這樣,我們就用一種“組合”方式,解決了 WAR 包與 Tomcat 容器之間耦合關系的問題。實際上,這個所謂的“組合”操作,正是容器設計模式里最常用的一種模式,它的名字叫:sidecar。

在我們的這個應用 Pod 中,Tomcat 容器是我們要使用的主容器,而 WAR 包容器的存在,只是為了給它提供一個 WAR 包而已。所以,我們用 Init Container初始化容器的方式優先運行 WAR 包容器,扮演了一個 sidecar 的角色。

四. Pod的生命周期狀態

  • Pending。這個狀態意味著,Pod 的 YAML 文件已經提交給了 Kubernetes,API 對象已經被創建并保存在 Etcd 當中。但是,這個 Pod 里有些容器因為某種原因而不能被順利創建。比如,調度不成功。
  • Running。這個狀態下,Pod已經調度成功,跟一個具體的節點綁定。它包含的容器都已經創建成功,并且至少有一個正在運行中。
  • Succeeded。這個狀態意味著,Pod 里的所有容器都正常運行完畢,并且已經退出了。這種情況在運行一次性任務時最為常見。
  • Failed。這個狀態下,Pod 里至少有一個容器以不正常的狀態(非 0 的返回碼)退出。這個狀態的出現,意味著你得想辦法 Debug 這個容器的應用,比如查看 Pod 的 Events 和日志。
  • Unknown。這是一個異常狀態,意味著 Pod 的狀態不能持續地被 kubelet 匯報給 kube-apiserver,這很有可能是主從節點(Master 和 Kubelet)間的通信出現了問題。

五. Pod的鉤子Hook

Kubernetes 為我們的容器提供了生命周期鉤子的,就是我們說的Pod Hook,Pod Hook 是由 kubelet 發起的,當容器中的進程啟動前或者容器中的進程終止之前運行,這是包含在容器的生命周期之中。我們可以同時為 Pod 中的所有容器都配置 hook。

Kubernetes 為我們提供了兩種鉤子函數:

  • PostStart:這個鉤子在容器創建后立即執行。但是,并不能保證鉤子將在容器ENTRYPOINT之前運行,因為沒有參數傳遞給處理程序。主要用于資源部署、環境準備等。不過需要注意的是如果鉤子花費太長時間以至于不能運行或者掛起, 容器將不能達到running狀態。
  • PreStop:這個鉤子在容器終止之前立即被調用。它是阻塞的,意味著它是同步的, 所以它必須在刪除容器的調用發出之前完成。主要用于優雅關閉應用程序、通知其他系統等。如果鉤子在執行期間掛起, Pod階段將停留在running狀態并且永不會達到failed狀態。
    如果PostStart或者PreStop鉤子失敗, 它會殺死容器。所以我們應該讓鉤子函數盡可能的輕量。當然有些情況下,長時間運行命令是合理的, 比如在停止容器之前預先保存狀態。

另外我們有兩種方式來實現上面的鉤子函數:

  • Exec - 用于執行一段特定的命令,不過要注意的是該命令消耗的資源會被計入容器。
  • HTTP - 對容器上的特定的端點執行HTTP請求
    示例
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"]

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

六. Pod的健康檢查

在 Kubernetes 中,你可以為 Pod 里的容器定義一個健康檢查“探針”(Probe)。這樣,kubelet 就會根據這個 Probe 的返回值決定這個容器的狀態,而不是直接以容器進行是否運行(來自 Docker 返回的信息)作為依據。這種機制,是生產環境中保證應用健康存活的重要手段。

  • liveness probe(存活探針):確定你的應用程序是否正在運行,通俗點將就是是否還活著。一般來說,如果你的程序一旦崩潰了, Kubernetes 就會立刻知道這個程序已經終止了,然后就會重啟這個程序。而我們的 liveness probe 的目的就是來捕獲到當前應用程序還沒有終止,還沒有崩潰,如果出現了這些情況,那么就重啟處于該狀態下的容器,使應用程序在存在 bug 的情況下依然能夠繼續運行下去。
  • readiness probe(可讀性探針):確定容器是否已經就緒可以接收流量過來了。這個探針通俗點講就是說是否準備好了,現在可以開始工作了。只有當 Pod 中的容器都處于就緒狀態的時候 kubelet 才會認定該 Pod 處于就緒狀態,因為一個 Pod 下面可能會有多個容器。當然 Pod 如果處于非就緒狀態,那么我們就會將他從我們的工作隊列(實際上就是我們后面需要重點學習的 Service)中移除出來,這樣我們的流量就不會被路由到這個 Pod 里面來了,決定的這個 Pod 是不是能被通過 Service 的方式訪問到,而并不影響 Pod 的生命周期。。

探針支持的配置方式:

  • exec:執行一段命令
  • http:檢測某個 http 請求
  • tcpSocket:檢查端口, kubelet 將嘗試在指定端口上打開容器的套接字。如果可以建立連接,容器被認為是健康的,如果不能就認為是失敗的。

示例

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: test-liveness-exec
spec:
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

在這個 Pod 中,我們定義了一個容器。它在啟動之后做的第一件事,就是在 /tmp 目錄下創建了一個 healthy 文件,以此作為自己已經正常運行的標志。而 30 s 過后,它會把這個文件刪除掉。與此同時,我們定義了一個這樣的 livenessProbe(健康檢查)。它的類型是 exec,這意味著,它會在容器啟動后,在容器里面執行一句我們指定的命令,比如:“cat /tmp/healthy”。這時,如果這個文件存在,這條命令的返回值就是 0,Pod 就會認為這個容器不僅已經啟動,而且是健康的,如果返回值為非0,那么kubelet將會重啟這個容器。initialDelaySeconds:5,在容器啟動 5 s 后開始執行健康檢查,periodSeconds: 5每 5 s 執行一次。

創建這個Pod查看過程

$ kubectl  get pods 
test-liveness-exec                                                1/1     Running            0          64s

30s后我們查看Pod的Event

$ kubectl  describe pod test-liveness-exec
Events:
  Type     Reason     Age                 From                 Message
  ----     ------     ----                ----                 -------
  Normal   Scheduled  2m5s                default-scheduler    Successfully assigned default/test-liveness-exec to k8s-node01
  Warning  Unhealthy  66s (x3 over 76s)   kubelet, k8s-node01  Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory

通過Event發現健康檢查探查到 /tmp/healthy 已經不存在了,所以它報告容器是不健康的,我們在看一下Pod的當前狀態是否正常?

$ kubectl   get pods 
NAME                                                              READY   STATUS             RESTARTS   AGE
test-liveness-exec                                                1/1     Running            1          1m1s

我們可以看到Pod的狀態仍然是Running正常的,但是RESTARTS字段已經由0已經變成1了,這是因為Pod已經被kubelet重啟了。

livenessProbe 也可以定義為發起 HTTP 或者 TCP 請求的方式,定義格式如下:

livenessProbe:
     httpGet:
       path: /healthz
       port: 8080
       httpHeaders:
       - name: X-Custom-Header
         value: Awesome
       initialDelaySeconds: 3
       periodSeconds: 3
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

Pod 其實可以暴露一個健康檢查 URL(比如 /healthz),或者直接讓健康檢查去檢測應用的監聽端口。這兩種配置方法,在 Web 服務類的應用中非常常用。

七. Pod的故障恢復機制

上面例子中健康檢查如果沒有通過,kubelet則會重啟這個容器,這是因為默認Pod的恢復機制為Always,其實它是 Pod 的 Spec 部分的一個標準字段(pod.spec.restartPolicy),我們可以根據自己的需求來定通過設置 restartPolicy改變 Pod 的恢復策略:

  • Always:在任何情況下,只要容器不在運行狀態,就自動重啟容器;
  • OnFailure: 只在容器 異常時才自動重啟容器;
  • Never: 從來不重啟容器。

基本的設計原理:

  1. 只要 Pod 的 restartPolicy 指定的策略允許重啟異常的容器(比如:Always),那么這個 Pod 就會保持 Running 狀態,并進行容器重啟。否則,Pod 就會進入 Failed 狀態 。

  2. 對于包含多個容器的 Pod,只有它里面所有的容器都進入異常狀態后,Pod 才會進入 Failed 狀態。在此之前,Pod 都是 Running 狀態。此時,Pod 的 READY 字段會顯示正常容器的個數

八. Pod的常用屬性定義

除了上文定義的一些屬性字段,我們常用的屬性還有以下字段定義:

  • NodeSelector:是一個供用戶將 Pod 與 Node 進行綁定的字段
  • ImagePullPolicy:[Always | Never | IfNotPresent] # 【String】 每次都嘗試重新拉取鏡像 | 僅使用本地鏡像 | 如果本地有鏡像則使用,沒有則拉取
  • Volume:volume是Pod中多個容器訪問的共享目錄。volume被定義在pod上,被這個pod的多個容器掛載到相同或不同的路徑下。一般volume被用于持久化pod產生的數據。Kubernetes提供了眾多的volume類型,包括emptyDir、hostPath、nfs、glusterfs、cephfs、ceph rbd等。
  • NodeName:一旦 Pod 的這個字段被賦值,Kubernetes 項目就會被認為這個 Pod 已經經過了調度,調度的結果就是賦值的節點名字。所以,這個字段一般由調度器負責設置,但用戶也可以設置它來“騙過”調度器,當然這個做法一般是在測試或者調試的時候才會用到。
  • HostAliases:定義了 Pod 的 hosts 文件(比如 /etc/hosts)里的內容。

網上資料:


image.png

image.png

image.png

image.png

image.png

image.png

總結:在這篇文章中我們深度了解了Pod的設計模式和字段屬性定義,其實Pod 這個看似復雜的 API 對象,實際上就是對容器的進一步抽象和封裝而已,Pod 對象就是容器的升級版。它對容器進行了組合,添加了更多的屬性和字段。


上篇文章:k8s三 | 使用YAML文件定義資源對象
系列文章:深入理解Kuerneters
參考資料:深入剖析Kubernetes-張磊、從Docker到Kubernetes進階-陽明

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容

  • Pod 為什么需要Pod Pod,是Kubernetes項目中的原子調度單位。容器就是未來云計算系統中的進程;容器...
    12點前睡覺hhh閱讀 1,317評論 0 0
  • 現在,你已經非常清楚:Kubernetes 項目中的最小編排單位是 Pod,而不是容器。將這個設計落實到 API ...
    YY_1閱讀 838評論 0 1
  • 現在,你已經非常清楚:Kubernetes 項目中的最小編排單位是 Pod,而不是容器。將這個設計落實到 API ...
  • 填坑,整理下Java的常用異常。正確使用異常在實際編碼中非常重要,但面試中的意義相對較小,因為對異常的理解和應用很...
    猴子007閱讀 1,170評論 7 24
  • 姓名:林雄杰 公司:寧波輝旺機械有限公司 2018年12月20日 【日精進打卡第59天】 【知~學習】誦持佛教經卷...
    木木似水閱讀 69評論 0 0