Pod,是 Kubernetes 項目中最小的 API 對象
容器的本質是進程,就是未來雲計算系統中的進程;容器鏡像就是這個系統里的".exe"安裝包
Kubernetes 就是操作系統!
Pod 里的所有容器,共享的是同一個 Network Namespace,並且可以聲明共享同一個 Volum
所以,
在 Kubernetes 項目里,Pod 的實現需要使用一個中間容器,這個容器叫作 Infra 容器。在這個 Pod 中,Infra 容器永遠都是第一個被創建的容器,而其他用戶定義的容器,
則通過 Join Network Namespace 的方式,與 Infra 容器關聯在一起。這樣的組織關系.
這個 Pod 里有兩個用戶容器 A 和 B,還有一個 Infra 容器。很容易理解,在 Kubernetes 項目里,Infra 容器一定要占用極少的資源,所以它使用的是一個非常特殊的鏡像,
這個鏡像是一個用匯編語言編寫的、永遠處於“暫停”狀態的容器,解壓后的大小也只有 100~200 KB 左右。
而對於同一個 Pod 里面的所有用戶容器來說,它們的進出流量,也可以認為都是通過 Infra 容器完成的。這一點很重要,因為<strong>將來如果你要為 Kubernetes 開發一個網絡插件時,
應該重點考慮的是如何配置這個 Pod 的 Network Namespace,而不是每一個用戶容器如何使用你的網絡配置,這是沒有意義的
這就意味着,如果你的網絡插件需要在容器里安裝某些包或者配置才能完成的話,是不可取的:Infra 容器鏡像的 rootfs 里幾乎什么都沒有,沒有你隨意發揮的空間。
當然,這同時也意味着你的網絡插件完全不必關心用戶容器的啟動與否,而只需要關注如何配置 Pod,也就是 Infra 容器的 Network Namespace 即可。
這樣,一個 Volume 對應的宿主機目錄對於 Pod 來說就只有一個,Pod 里的容器只要聲明掛載這個 Volume,就一定可以共享這個 Volume 對應的宿主機目錄。比如下面這個例子:
apiVersion: v1
kind: Pod
metadata:
name: two-containers
spec:
restartPolicy: Never
volumes:
- name: shared-data
hostPath:
path: /data
containers:
- name: nginx-container
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"]
在這個例子中,debian-container 和 nginx-container 都聲明掛載了 shared-data 這個 Volume。而 shared-data 是 hostPath 類型。所以,它對應在宿主機上的目錄就是:/data。而這個目錄,其實就被同時綁定掛載進了上述兩個容器當中
這就是為什么,nginx-container 可以從它的 /usr/share/nginx/html 目錄中,讀取到 debian-container 生成的 index.html 文件的原因。
二.明白了 Pod 的實現原理后,我們再來討論“容器設計模式
我們現在有一個 Java Web 應用的 WAR 包,它需要被放在 Tomcat 的 webapps 目錄下運行起來
一種方法是,把 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。
顧名思義,sidecar 指的就是我們可以在一個 Pod 中,啟動一個輔助容器,來完成一些獨立於主進程(主容器)之外的工作。
比如:
在我們的這個應用 Pod 中,Tomcat 容器是我們要使用的主容器,而 WAR 包容器的存在,只是為了給它提供一個 WAR 包而已。所以,我們用 Init Container 的方式優先運行 WAR 包容器,扮演了一個 sidecar 的角色。
但不要忘記,Pod 的另一個重要特性是,它的所有容器都共享同一個 Network Namespace。這就使得很多與 Pod 網絡相關的配置和管理,也都可以交給 sidecar 完成,而完全無須干涉用戶容器。這里最典型的例子莫過於 Istio 這個微服務治理項目了
事實上,直到現在,仍有很多人把容器跟虛擬機相提並論,他們把容器當做性能更好的虛擬機,喜歡討論如何把應用從虛擬機無縫地遷移到容器中。
但實際上,無論是從具體的實現原理,還是從使用方法、特性、功能等方面,容器與虛擬機幾乎沒有任何相似的地方;也不存在一種普遍的方法,
能夠把虛擬機里的應用無縫遷移到容器中。因為,容器的性能優勢,必然伴隨着相應缺陷,即:它不能像虛擬機那樣,完全模擬本地物理機環境中的部署方法。
所以,這個“上雲”工作的完成,最終還是要靠深入理解容器的本質,即:進程。
實際上,一個運行在虛擬機里的應用,哪怕再簡單,也是被管理在 systemd 或者 supervisord 之下的一組進程,而不是一個進程。這跟本地物理機上應用的運行方式其實是一樣的。這也是為什么,從物理機到虛擬機之間的應用遷移,往往並不困難。
可是對於容器來說,一個容器永遠只能管理一個進程。更確切地說,一個容器,就是一個進程。這是容器技術的“天性”,不可能被修改。所以,將一個原本運行在虛擬機里的應用,“無縫遷移”到容器中的想法,實際上跟容器的本質是相悖的。
所以,你現在可以這么理解 Pod 的本質:
Pod,實際上是在扮演傳統基礎設施里“虛擬機”的角色;而容器,則是這個虛擬機里運行的用戶程序。
所以下一次,當你需要把一個運行在虛擬機里的應用遷移到 Docker 容器中時,一定要仔細分析到底有哪些進程(組件)運行在這個虛擬機里。
然后,你就可以把整個虛擬機想象成為一個 Pod,把這些進程分別做成容器鏡像,把有順序關系的容器,定義為 Init Container。這才是更加合理的、松耦合的容器編排訣竅,也是從傳統應用架構,到“微服務架構”最自然的過渡方式。