前言
簡單整理一下pod的相關知識。
正文
爲什麼我們需要pod。
前面我們知道了k8s一個最重要的作用是解決容器的編排功能,那麼爲什麼有一個pod的東西。
這就是實際中遇到的問題。
那就是容器和容器之間,那就是如何表達容器和容器之間的關係。
就是有些場景下,容器與容器之間是存在關係的。
如果把k8s 比作操作系統,容器比作進程,那麼進程組就是pod。
之所以有這個pod,就是因爲一些容器他們之間需要在公共的namespace、cgroup 下面運行。
也可以理解他們原本就應該在一臺虛擬機下面執行。
像這樣容器間的緊密協作,我們可以稱爲“超親密關係”。
這些具有“超親密關係”容器的典型特徵包括但不限於:互相之間會發生直接的文件交換、使用 localhost 或者 Socket 文件進行本地通信、
會發生非常頻繁的遠程調用、需要共享某些 Linux Namespace(比如,一個容器要加入另一個容器的 Network Namespace)等等。
不過,Pod 在 Kubernetes 項目裏還有更重要的意義,那就是:容器設計模式。
首先,關於 Pod 最重要的一個事實是:它只是一個邏輯概念。
也就是說,Kubernetes 真正處理的,還是宿主機操作系統上 Linux 容器的 Namespace
和 Cgroups,而並不存在一個所謂的 Pod 的邊界或者隔離環境。
那麼,Pod 又是怎麼被“創建”出來的呢?
答案是:Pod,其實是一組共享了某些資源的容器。
具體的說:Pod 裏的所有容器,共享的是同一個 Network Namespace,並且可以聲明
共享同一個 Volume。
所以,在 Kubernetes 項目裏,Pod 的實現需要使用一箇中間容器,這個容器叫作 Infra 容器。
在這個 Pod 中,Infra 容器永遠都是第一個被創建的容器,而其他用戶定義的容器,則通過 Join Network Namespace 的方式,與 Infra 容器關聯在一起。
這樣的組織關係,可以用下面這樣一個示意圖來表達:
這個 Pod 裏有兩個用戶容器 A 和 B,還有一個 Infra 容器。很容易理解,在Kubernetes 項目裏,Infra 容器一定要佔用極少的資源,所以它使用的是一個非常特殊的鏡像,叫作:k8s.gcr.io/pause。
這個鏡像是一個用匯編語言編寫的、永遠處於“暫停”狀態的容器,解壓後的大小也只有 100~200 KB 左右。
而在 Infra 容器“Hold 住”Network Namespace 後,用戶容器就可以加入到 Infra 容器的 Network Namespace 當中了。
所以,如果你查看這些容器在宿主機上的 Namespace文件(這個 Namespace 文件的路徑,我已經在前面的內容中介紹過),它們指向的值一定是完全一樣的。
當然,其他的所有網絡資源,都是一個 Pod 一份,並且被該 Pod 中的所有容器共享;Pod 的生命週期只跟 Infra 容器一致,而與容器 A 和 B 無關。
比如:
apiVersion: "v1"
kind: "Pod"
metadata:
name: two-contrains
namespace: name01
spec:
restartPolicy: "Always"
volumes:
- name: shared-data
hostPath:
path: /data
containers:
- name: nginx-controller
image: nginx
volumeMounts:
- name: shared-data
mountPath: /usr/share/nginx/html
- name: debian-container
image: debian
volumeMounts:
- name: shared-data
mountPath: /pod-data
command: ["/bin/sh"]
args: ["-c","echo Hello from the debian container ! > /pod-data/index.html"]
pod 聲明瞭一個volume,然後容器nginx-controller、debian-container 使用。
來看下怎麼實現的。
來看下docker 現象。
對於volumeMounts 而言,其實就是綁定/pod-data 到 /data 中。
對於網絡而言:
其實就是pod 內的幾個容器共享網絡。
對上面而言用的是infra的網絡。
再舉一個例子, 介紹一個initContainers:
k8s中的initContainers和Containers都是用於定義Pod中容器的部分,但是它們的主要區別在於:
-
生命週期不同:initContainers是在Pod中所有容器之前啓動的,並且只有在initContainers完成後纔會啓動其他容器。而Containers則是同時啓動的。
-
用途不同:initContainers主要用於在啓動Pod之前完成一些初始化操作,例如配置環境變量、檢查依賴等。而Containers則是用於運行應用程序或服務。
-
狀態不同:initContainers完成後會退出,而Containers會繼續運行。
總之,initContainers主要用於在Pod啓動時完成一些初始化工作,而Containers則是用於運行應用程序或服務。
是的,initContainers是按照它們在Pod中的順序依次運行的,每個initContainer必須在前一個initContainer完成後才能開始運行。只有所有的initContainers都成功完成後,Pod中的其他容器纔會啓動。
在定義initContainers時,可以使用spec.initContainers
字段來指定它們的順序。例如,下面的示例定義了兩個initContainers,分別用於執行初始化操作:
spec:
initContainers:
- name: init-container-1
image: busybox
command: ['sh', '-c', 'echo "init container 1"']
- name: init-container-2
image: busybox
command: ['sh', '-c', 'echo "init container 2"']
containers:
- name: my-app
image: my-image
command: ['sh', '-c', 'echo "my app"']
在這個示例中,init-container-1將在init-container-2之前執行。當Pod啓動時,先運行init-container-1,然後等待它完成後再運行init-container-2,最後才啓動my-app容器。
那麼舉一個實際中用到的例子,現在這個例子沒什麼用了,因爲現在java 打包就集成了tomcat,而不是在外面包裹一層:
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: {}
上面大體意思是將sample.war拷貝到app下面,然後將掛載出來,然後geektime/tomcat:7.0 就可以使用這個war 包了。
實際上,這個所謂的“組合”操作,正是容器設計模式裏最常用的一種模式,它的名字叫:sidecar
顧名思義,sidecar 指的就是我們可以在一個 Pod 中,啓動一個輔助容器,來完成一些獨立於主進程(主容器)之外的工作。
比如,在我們的這個應用 Pod 中,Tomcat 容器是我們要使用的主容器,而 WAR 包容器的存在,只是爲了給它提供一個 WAR 包而已。
所以,我們用 Init Container 的方式優先運行 WAR 包容器,扮演了一個 sidecar 的角色。
這樣做就有用給好處,就是每次都更新的是war包,而不需要關注tomcat運行環境,減少包的大小。
比如,我現在有一個應用,需要不斷地把日誌文件輸出到容器的 /var/log 目錄中。
這時,我就可以把一個 Pod 裏的 Volume 掛載到應用容器的 /var/log 目錄上。
然後,我在這個 Pod 裏同時運行一個 sidecar 容器,它也聲明掛載同一個 Volume 到自己的 /var/log 目錄上。
這樣,接下來 sidecar 容器就只需要做一件事兒,那就是不斷地從自己的 /var/log 目錄裏讀取日誌文件,轉發到 MongoDB 或者 Elasticsearch 中存儲起來。這樣,一個最基本的日誌收集工作就完成了。
跟第一個例子一樣,這個例子中的 sidecar 的主要工作也是使用共享的 Volume 來完成對文件的操作。
但不要忘記,Pod 的另一個重要特性是,它的所有容器都共享同一個 NetworkNamespace。
這就使得很多與 Pod 網絡相關的配置和管理,也都可以交給 sidecar 完成,而完全無須干涉用戶容器。
這裏最典型的例子莫過於 Istio 這個微服務治理項目了。
Istio 項目使用 sidecar 容器完成微服務治理的原理,我在後面很快會講解到。
結
下面一點需要區分:
在 Kubernetes 中,Infra Container 和 Init Container 都是容器,但它們有不同的用途和生命週期。
Infra Container 是一個在 Pod 中運行的輔助容器,用於提供一些共享資源或服務,例如網絡命名空間、存儲卷、日誌收集、監控等。Infra Container 在 Pod 啓動後一直運行直到 Pod 終止。
Init Container 是一種特殊類型的容器,它是在 Pod 中其他容器啓動之前運行的,用於初始化或準備一些資源。例如,可以使用 Init Container 下載應用程序代碼、初始化數據庫、生成配置文件等。Init Container 在它的工作完成後立即退出,然後 Pod 中的其他容器纔開始啓動。
因此,Infra Container 和 Init Container 的主要區別在於它們的用途和生命週期。Infra Container 是一個持久的輔助容器,爲 Pod 提供一些共享資源或服務;而 Init Container 是一個短暫的容器,用於在其他容器啓動之前初始化或準備一些資源。
然後:
如果在 Kubernetes 中未定義 Infra Container,則 Kubernetes 會自動添加一個名爲 `pause` 的 Infra Container。這是一個非常輕量級的容器,它的作用是爲 Pod 中的其他容器創建 Linux 命名空間和網絡 namespace,併爲網絡 namespace 分配 IP 地址。
在 Pod 中自動添加的 `pause` 容器是一個 Infra Container,它會在其他容器之前啓動,並在其他容器退出之後保持運行狀態。因此,即使在 Pod 定義文件中未定義 Infra Container,Kubernetes 仍然會確保 Infra Container 在 Pod 啓動時運行。
需要注意的是,如果在 Pod 定義文件中顯式定義 Infra Container,則 Kubernetes 不會自動添加 `pause` 容器。在這種情況下,Infra Container 的定義順序決定了它的啓動順序。
一般情況下,我們是不填這個Infra Container。