**1、爲何須要 Pod
容器的基本概念**web
咱們知道 Pod 是 Kubernetes 項目裏面一個很是重要的概念,也是很是重要的一個原子調度單位,可是爲何咱們會須要這樣一個概念呢?在使用容器 Docker 的時候,也沒有這個說法。其實,若是想要理解 Pod,首先要理解容器,因此來回顧一下容器的概念:後端
容器的本質其實是一個進程,是一個視圖被隔離,資源受限的進程。設計模式
容器裏面 PID=1 的進程就是應用自己,這意味着管理虛擬機等於管理基礎設施,由於咱們是在管理機器,但管理容器卻等於直接管理應用自己。這也是以前說過的不可變基礎設施的一個最佳體現,這個時候,你的應用就等於你的基礎設施,它必定是不可變的。api
在以上面的例子爲前提的狀況下,Kubernetes 又是什麼呢?不少人都說 Kubernetes 是雲時代的操做系統,這個很是有意思,由於若是以此類推,容器鏡像就是這個操做系統的軟件安裝包,它們之間是這樣的一個類比關係。網絡
真實操做系統裏的例子
若是說 Kubernetes 就是操做系統的話,那麼不妨看一下真實的操做系統的例子。ssh
例子裏面有一個程序叫作 Helloworld,這個 Helloworld 程序其實是由一組進程組成的,須要注意一下,這裏說的進程實際上等同於 Linux 中的線程。分佈式
由於 Linux 中的線程是輕量級進程,因此若是從 Linux 系統中去查看 Helloworld 中的 pstree,將會看到這個 Helloworld 其實是由四個線程組成的,分別是 {api、main、log、compute}。也就是說,四個這樣的線程共同協做,共享 Helloworld 程序的資源,組成了 Helloworld 程序的真實工做狀況。ide
這是操做系統裏面進程組或者線程組中一個很是真實的例子,以上就是進程組的一個概念。微服務
那麼你們不妨思考一下,在真實的操做系統裏面,一個程序每每是根據進程組來進行管理的。Kubernetes 把它類比爲一個操做系統,好比說 Linux。針對於容器咱們前面提到能夠類比爲進程,就是前面的 Linux 線程。那麼 Pod 又是什麼呢?實際上 Pod 就是咱們剛剛提到的進程組,也就是 Linux 裏的線程組。性能
進程組概念
說到進程組,首先建議你們至少有個概念上的理解,而後咱們再詳細的解釋一下。
仍是前面那個例子:Helloworld 程序由四個進程組成,這些進程之間會共享一些資源和文件。那麼如今有一個問題:假如說如今把 Helloworld 程序用容器跑起來,你會怎麼去作?
固然,最天然的一個解法就是,我如今就啓動一個 Docker 容器,裏面運行四個進程。但是這樣會有一個問題,這種狀況下容器裏面 PID=1 的進程該是誰? 好比說,它應該是個人 main 進程,那麼問題來了,「誰」又負責去管理剩餘的 3 個進程呢?
這個核心問題在於,容器的設計自己是一種「單進程」模型,不是說容器裏只能起一個進程,因爲容器的應用等於進程,因此只能去管理 PID=1 的這個進程,其餘再起來的進程實際上是一個託管狀態。 因此說服務應用進程自己就具備「進程管理」的能力。
好比說 Helloworld 的程序有 system 的能力,或者直接把容器裏 PID=1 的進程直接改爲 systemd,不然這個應用,或者是容器是沒有辦法去管理不少個進程的。由於 PID=1 進程是應用自己,若是如今把這個 PID=1 的進程給 kill 了,或者它本身運行過程當中死掉了,那麼剩下三個進程的資源就沒有人回收了,這個是很是嚴重的一個問題。
反過來,若是真的把這個應用自己改爲了 systemd,或者在容器裏面運行了一個 systemd,將會致使另一個問題:使得管理容器再也不是管理應用自己了,而等因而管理 systemd,這裏的問題就很是明顯了。好比說我這個容器裏面 run 的程序或者進程是 systemd,那麼接下來,這個應用是否是退出了?是否是 fail 了?是否是出現異常失敗了?其實是沒辦法直接知道的,由於容器管理的是 systemd。這就是爲何在容器裏面運行一個複雜程序每每比較困難的一個緣由。
這裏再幫你們梳理一下:因爲容器其實是一個「單進程」模型,因此若是你在容器裏啓動多個進程,只有一個能夠做爲 PID=1 的進程,而這時候,若是這個 PID=1 的進程掛了,或者說失敗退出了,那麼其餘三個進程就會天然而然的成爲孤兒,沒有人可以管理它們,沒有人可以回收它們的資源,這是一個很是很差的狀況。
注意:Linux 容器的「單進程」模型,指的是容器的生命週期等同於 PID=1 的進程(容器應用進程)的生命週期,而不是說容器裏不能建立多進程。固然,通常狀況下,容器應用進程並不具有進程管理能力,因此你經過 exec 或者 ssh 在容器裏建立的其餘進程,一旦異常退出(好比 ssh 終止)是很容易變成孤兒進程的。
反過來,其實能夠在容器裏面 run 一個 systemd,用它來管理其餘全部的進程。這樣會產生第二個問題:實際上沒辦法直接管理個人應用了,由於個人應用被 systemd 給接管了,那麼這個時候應用狀態的生命週期就不等於容器生命週期。這個管理模型其實是很是很是複雜的。
Pod = 「進程組」
在 Kubernetes 裏面,Pod 實際上正是 Kubernetes 項目爲你抽象出來的一個能夠類比爲進程組的概念。
前面提到的,由四個進程共同組成的一個應用 Helloworld,在 Kubernetes 裏面,實際上會被定義爲一個擁有四個容器的 Pod,這個概念你們必定要很是仔細的理解。
就是說如今有四個職責不一樣、相互協做的進程,須要放在容器裏去運行,在 Kubernetes 裏面並不會把它們放到一個容器裏,由於這裏會遇到兩個問題。那麼在 Kubernetes 裏會怎麼去作呢?它會把四個獨立的進程分別用四個獨立的容器啓動起來,而後把它們定義在一個 Pod 裏面。
因此當 Kubernetes 把 Helloworld 給拉起來的時候,你實際上會看到四個容器,它們共享了某些資源,這些資源都屬於 Pod,因此咱們說 Pod 在 Kubernetes 裏面只有一個邏輯單位,沒有一個真實的東西對應說這個就是 Pod,不會有的。真正起來在物理上存在的東西,就是四個容器,這四個容器,或者說是多個容器的組合就叫作 Pod。而且還有一個概念必定要很是明確,Pod 是 Kubernetes 分配資源的一個單位,由於裏面的容器要共享某些資源,因此 Pod 也是 Kubernetes 的原子調度單位。
上面提到的 Pod 設計,也不是 Kubernetes 項目本身想出來的, 而是早在 Google 研發 Borg 的時候,就已經發現了這樣一個問題。這個在 Borg paper 裏面有很是很是明確的描述。簡單來講 Google 工程師發如今 Borg 下面部署應用時,不少場景下都存在着相似於「進程與進程組」的關係。更具體的是,這些應用以前每每有着密切的協做關係,使得它們必須部署在同一臺機器上而且共享某些信息。
以上就是進程組的概念,也是 Pod 的用法。
爲何 Pod 必須是原子調度單位?
可能到這裏你們會有一些問題:雖然瞭解這個東西是一個進程組,可是爲何要把 Pod 自己做爲一個概念抽象出來呢?或者說能不能經過調度把 Pod 這個事情給解決掉呢?爲何 Pod 必須是 Kubernetes 裏面的原子調度單位?
下面咱們經過一個例子來解釋。
假如如今有兩個容器,它們是緊密協做的,因此它們應該被部署在一個 Pod 裏面。具體來講,第一個容器叫作 App,就是業務容器,它會寫日誌文件;第二個容器叫作 LogCollector,它會把剛剛 App 容器寫的日誌文件轉發到後端的 ElasticSearch 中。
兩個容器的資源需求是這樣的:App 容器須要 1G 內存,LogCollector 須要 0.5G 內存,而當前集羣環境的可用內存是這樣一個狀況:Node_A:1.25G 內存,Node_B:2G 內存。
假如說如今沒有 Pod 概念,就只有兩個容器,這兩個容器要緊密協做、運行在一臺機器上。但是,若是調度器先把 App 調度到了 Node_A 上面,接下來會怎麼樣呢?這時你會發現:LogCollector 其實是沒辦法調度到 Node_A 上的,由於資源不夠。其實此時整個應用自己就已經出問題了,調度已經失敗了,必須去從新調度。
][6]
以上就是一個很是典型的成組調度失敗的例子。英文叫作:Task co-scheduling 問題,這個問題不是說不能解,在不少項目裏面,這樣的問題都有解法。
好比說在 Mesos 裏面,它會作一個事情,叫作資源囤積(resource hoarding):即當全部設置了 Affinity 約束的任務都達到時,纔開始統一調度,這是一個很是典型的成組調度的解法。
因此上面提到的「App」和「LogCollector」這兩個容器,在 Mesos 裏面,他們不會說馬上調度,而是等兩個容器都提交完成,纔開始統一調度。這樣也會帶來新的問題,首先調度效率會損失,由於須要等待。因爲須要等,還會有外一個狀況會出現,就是產生死鎖,即互相等待的一個狀況。這些機制在 Mesos 裏都是須要解決的,也帶來了額外的複雜度。
另外一種解法是 Google 的解法。它在 Omega 系統(就是 Borg 下一代)裏面,作了一個很是複雜且很是厲害的解法,叫作樂觀調度。好比說:無論這些衝突的異常狀況,先調度,同時設置一個很是精妙的回滾機制,這樣通過沖突後,經過回滾來解決問題。這個方式相對來講要更加優雅,也更加高效,可是它的實現機制是很是複雜的。這個有不少人也能理解,就是悲觀鎖的設置必定比樂觀鎖要簡單。
而像這樣的一個 Task co-scheduling 問題,在 Kubernetes 裏,就直接經過 Pod 這樣一個概念去解決了。由於在 Kubernetes 裏,這樣的一個 App 容器和 LogCollector 容器必定是屬於一個 Pod 的,它們在調度時必然是以一個 Pod 爲單位進行調度,因此這個問題是根本不存在的。
再次理解 Pod
在講了前面這些知識點以後,咱們來再次理解一下 Pod,首先 Pod 裏面的容器是「超親密關係」。
這裏有個「超」字須要你們理解,正常來講,有一種關係叫作親密關係,這個親密關係是必定能夠經過調度來解決的。
好比說如今有兩個 Pod,它們須要運行在同一臺宿主機上,那這樣就屬於親密關係,調度器必定是能夠幫助去作的。可是對於超親密關係來講,有一個問題,即它必須經過 Pod 來解決。由於若是超親密關係賦予不了,那麼整個 Pod 或者說是整個應用都沒法啓動。
什麼叫作超親密關係呢?大概分爲如下幾類:
好比說兩個進程之間會發生文件交換,前面提到的例子就是這樣,一個寫日誌,一個讀日誌;
兩個進程之間須要經過 localhost 或者說是本地的 Socket 去進行通訊,這種本地通訊也是超親密關係;
這兩個容器或者是微服務之間,須要發生很是頻繁的 RPC 調用,出於性能的考慮,也但願它們是超親密關係;
兩個容器或者是應用,它們須要共享某些 Linux Namespace。最簡單常見的一個例子,就是我有一個容器須要加入另外一個容器的 Network Namespace。這樣我就能看到另外一個容器的網絡設備,和它的網絡信息。
像以上幾種關係都屬於超親密關係,它們都是在 Kubernetes 中會經過 Pod 的概念去解決的。
如今咱們理解了 Pod 這樣的概念設計,理解了爲何須要 Pod。它解決了兩個問題:
咱們怎麼去描述超親密關係;
咱們怎麼去對超親密關係的容器或者說是業務去作統一調度,這是 Pod 最主要的一個訴求。
2、Pod 的實現機制
Pod 要解決的問題
像 Pod 這樣一個東西,自己是一個邏輯概念。那在機器上,它到底是怎麼實現的呢?這就是咱們要解釋的第二個問題。
既然說 Pod 要解決這個問題,核心就在於如何讓一個 Pod 裏的多個容器之間最高效的共享某些資源和數據。
由於容器之間本來是被 Linux Namespace 和 cgroups 隔開的,因此如今實際要解決的是怎麼去打破這個隔離,而後共享某些事情和某些信息。這就是 Pod 的設計要解決的核心問題所在。
因此說具體的解法分爲兩個部分:網絡和存儲。
1.共享網絡
第一個問題是 Pod 裏的多個容器怎麼去共享網絡?下面是個例子:
好比說如今有一個 Pod,其中包含了一個容器 A 和一個容器 B,它們兩個就要共享 Network Namespace。在 Kubernetes 裏的解法是這樣的:它會在每一個 Pod 裏,額外起一個 Infra container 小容器來共享整個 Pod 的 Network Namespace。
Infra container 是一個很是小的鏡像,大概 100~200KB 左右,是一個彙編語言寫的、永遠處於「暫停」狀態的容器。因爲有了這樣一個 Infra container 以後,其餘全部容器都會經過 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。
因此說一個 Pod 裏面的全部容器,它們看到的網絡視圖是徹底同樣的。即:它們看到的網絡設備、IP地址、Mac地址等等,跟網絡相關的信息,其實全是一份,這一份都來自於 Pod 第一次建立的這個 Infra container。這就是 Pod 解決網絡共享的一個解法。
在 Pod 裏面,必定有一個 IP 地址,是這個 Pod 的 Network Namespace 對應的地址,也是這個 Infra container 的 IP 地址。因此你們看到的都是一份,而其餘全部網絡資源,都是一個 Pod 一份,而且被 Pod 中的全部容器共享。這就是 Pod 的網絡實現方式。
因爲須要有一個至關於說中間的容器存在,因此整個 Pod 裏面,必然是 Infra container 第一個啓動。而且整個 Pod 的生命週期是等同於 Infra container 的生命週期的,與容器 A 和 B 是無關的。這也是爲何在 Kubernetes 裏面,它是容許去單獨更新 Pod 裏的某一個鏡像的,即:作這個操做,整個 Pod 不會重建,也不會重啓,這是很是重要的一個設計。
2.共享存儲
第二問題:Pod 怎麼去共享存儲?Pod 共享存儲就相對比較簡單。
好比說如今有兩個容器,一個是 Nginx,另一個是很是普通的容器,在 Nginx 裏放一些文件,讓我能經過 Nginx 訪問到。因此它須要去 share 這個目錄。我 share 文件或者是 share 目錄在 Pod 裏面是很是簡單的,實際上就是把 volume 變成了 Pod level。而後全部容器,就是全部同屬於一個 Pod 的容器,他們共享全部的 volume。
好比說上圖的例子,這個 volume 叫作 shared-data,它是屬於 Pod level 的,因此在每個容器裏能夠直接聲明:要掛載 shared-data 這個 volume,只要你聲明瞭你掛載這個 volume,你在容器裏去看這個目錄,實際上你們看到的就是同一份。這個就是 Kubernetes 經過 Pod 來給容器共享存儲的一個作法。
因此在以前的例子中,應用容器 App 寫了日誌,只要這個日誌是寫在一個 volume 中,只要聲明掛載了一樣的 volume,這個 volume 就能夠馬上被另一個 LogCollector 容器給看到。以上就是 Pod 實現存儲的方式。
3、詳解容器設計模式
如今咱們知道了爲何須要 Pod,也瞭解了 Pod 這個東西究竟是怎麼實現的。最後,以此爲基礎,詳細介紹一下 Kubernetes 很是提倡的一個概念,叫作容器設計模式。
舉例
接下來將會用一個例子來給你們進行講解。
好比我如今有一個很是常見的一個訴求:我如今要發佈一個應用,這個應用是 JAVA 寫的,有一個 WAR 包須要把它放到 Tomcat 的 web APP 目錄下面,這樣就能夠把它啓動起來了。但是像這樣一個 WAR 包或 Tomcat 這樣一個容器的話,怎麼去作,怎麼去發佈?這裏面有幾種作法。
第一種方式:能夠把 WAR 包和 Tomcat 打包放進一個鏡像裏面。可是這樣帶來一個問題,就是如今這個鏡像實際上揉進了兩個東西。那麼接下來,不管是我要更新 WAR 包仍是說我要更新 Tomcat,都要從新作一個新的鏡像,這是比較麻煩的;
第二種方式:就是鏡像裏面只打包 Tomcat。它就是一個 Tomcat,可是須要使用數據卷的方式,好比說 hostPath,從宿主機上把 WAR 包掛載進咱們 Tomcat 容器中,掛到個人 web APP 目錄下面,這樣把這個容器啓用起來以後,裏面就能用了。
可是這時會發現一個問題:這種作法必定須要維護一套分佈式存儲系統。由於這個容器可能第一次啓動是在宿主機 A 上面,第二次從新啓動就可能跑到 B 上去了,容器它是一個可遷移的東西,它的狀態是不保持的。因此必須維護一套分佈式存儲系統,使容器無論是在 A 仍是在 B 上,均可以找到這個 WAR 包,找到這個數據。
注意,即便有了分佈式存儲系統作 Volume,你還須要負責維護 Volume 裏的 WAR 包。好比:你須要單獨寫一套 Kubernetes Volume 插件,用來在每次 Pod 啓動以前,把應用啓動所需的 WAR 包下載到這個 Volume 裏,而後才能被應用掛載使用到。
這樣操做帶來的複雜程度仍是比較高的,且這個容器自己必須依賴於一套持久化的存儲插件(用來管理 Volume 裏的 WAR 包內容)。
InitContainer
因此你們有沒有考慮過,像這樣的組合方式,有沒有更加通用的方法?哪怕在本地 Kubernetes 上,沒有分佈式存儲的狀況下也能用、能玩、能發佈。
實際上方法是有的,在 Kubernetes 裏面,像這樣的組合方式,叫作 Init Container。
仍是一樣一個例子:在上圖的 yaml 裏,首先定義一個 Init Container,它只作一件事情,就是把 WAR 包從鏡像裏拷貝到一個 Volume 裏面,它作完這個操做就退出了,因此 Init Container 會比用戶容器先啓動,而且嚴格按照定義順序來依次執行。
而後,這個關鍵在於剛剛拷貝到的這樣一個目的目錄:APP 目錄,其實是一個 Volume。而咱們前面提到,一個 Pod 裏面的多個容器,它們是能夠共享 Volume 的,因此如今這個 Tomcat 容器,只是打包了一個 Tomcat 鏡像。但在啓動的時候,要聲明使用 APP 目錄做爲個人 Volume,而且要把它們掛載在 Web APP 目錄下面。
而這個時候,因爲前面運行過了一個 Init Container,已經執行完拷貝操做了,因此這個 Volume 裏面已經存在了應用的 WAR 包:就是 sample.war,絕對已經存在這個 Volume 裏面了。等到第二步執行啓動這個 Tomcat 容器的時候,去掛這個 Volume,必定能在裏面找到前面拷貝來的 sample.war。
因此能夠這樣去描述:這個 Pod 就是一個自包含的,能夠把這一個 Pod 在全世界任何一個 Kubernetes 上面都順利啓用起來。不用擔憂沒有分佈式存儲、Volume 不是持久化的,它必定是能夠公佈的。
因此這是一個經過組合兩個不一樣角色的容器,而且按照一些像 Init Container 的編排方式,統一去打包這樣一個應用,把它用 Pod 來去作的很是典型的一個例子。像這樣的一個概念,在 Kubernetes 裏面就是一個很是經典的容器設計模式,叫作:「Sidecar」。
容器設計模式:Sidecar
什麼是 Sidecar?就是說其實在 Pod 裏面,能夠定義一些專門的容器,來執行主業務容器所須要的一些輔助工做,好比咱們前面舉的例子,其實就幹了一個事兒,這個 Init Container,它就是一個 Sidecar,它只負責把鏡像裏的 WAR 包拷貝到共享目錄裏面,以便被 Tomcat 可以用起來。
其它有哪些操做呢?好比說:
本來須要在容器裏面執行 SSH 須要乾的一些事情,能夠寫腳本、一些前置的條件,其實均可以經過像 Init Container 或者另外像 Sidecar 的方式去解決;
固然還有一個典型例子就是個人日誌收集,日誌收集自己是一個進程,是一個小容器,那麼就能夠把它打包進 Pod 裏面去作這個收集工做;
還有一個很是重要的東西就是 Debug 應用,實際上如今 Debug 整個應用均可以在應用 Pod 裏面再次定義一個額外的小的 Container,它能夠去 exec 應用 pod 的 namespace;
查看其餘容器的工做狀態,這也是它能夠作的事情。再也不須要去 SSH 登錄到容器裏去看,只要把監控組件裝到額外的小容器裏面就能夠了,而後把它做爲一個 Sidecar 啓動起來,跟主業務容器進行協做,因此一樣業務監控也均可以經過 Sidecar 方式來去作。
這種作法一個很是明顯的優點就是在於其實將輔助功能從個人業務容器解耦了,因此我就可以獨立發佈 Sidecar 容器,而且更重要的是這個能力是能夠重用的,即一樣的一個監控 Sidecar 或者日誌 Sidecar,能夠被全公司的人共用的。這就是設計模式的一個威力。
Sidecar:應用與日誌收集
接下來,咱們再詳細細化一下 Sidecar 這樣一個模式,它還有一些其餘的場景。
好比說前面提到的應用日誌收集,業務容器將日誌寫在一個 Volume 裏面,而因爲 Volume 在 Pod 裏面是被共享的,因此日誌容器 —— 即 Sidecar 容器必定能夠經過共享該 Volume,直接把日誌文件讀出來,而後存到遠程存儲裏面,或者轉發到另一個例子。如今業界經常使用的 Fluentd 日誌進程或日誌組件,基本上都是這樣的工做方式。
Sidecar:代理容器
Sidecar 的第二個用法,能夠稱做爲代理容器 Proxy。什麼叫作代理容器呢?
假如如今有個 Pod 須要訪問一個外部系統,或者一些外部服務,可是這些外部系統是一個集羣,那麼這個時候如何經過一個統一的、簡單的方式,用一個 IP 地址,就把這些集羣都訪問到?有一種方法就是:修改代碼。由於代碼裏記錄了這些集羣的地址;另外還有一種解耦的方法,即經過 Sidecar 代理容器。
簡單說,單獨寫一個這麼小的 Proxy,用來處理對接外部的服務集羣,它對外暴露出來只有一個 IP 地址就能夠了。因此接下來,業務容器主要訪問 Proxy,而後由 Proxy 去鏈接這些服務集羣,這裏的關鍵在於 Pod 裏面多個容器是經過 localhost 直接通訊的,由於它們同屬於一個 network Namespace,網絡視圖都同樣,因此它們倆通訊 localhost,並無性能損耗。
因此說代理容器除了作了解耦以外,並不會下降性能,更重要的是,像這樣一個代理容器的代碼就又能夠被全公司重用了。
Sidecar:適配器容器
Sidecar 的第三個設計模式 —— 適配器容器 Adapter,什麼叫 Adapter 呢?
如今業務暴露出來的 API,好比說有個 API 的一個格式是 A,可是如今有一個外部系統要去訪問個人業務容器,它只知道的一種格式是 API B ,因此要作一個工做,就是把業務容器怎麼想辦法改掉,要去改業務代碼。但實際上,你能夠經過一個 Adapter 幫你來作這層轉換。
有個例子:如今業務容器暴露出來的監控接口是 /metrics,訪問這個容器的 metrics 的 URL 就能夠拿到了。但是如今,這個監控系統升級了,它訪問的 URL 是 /health,我只認得暴露出 health 健康檢查的 URL,才能去作監控,metrics 不認識。那這個怎麼辦?那就須要改代碼了,但能夠不去改代碼,額外寫一個 Adapter,用來把全部對 health 的這個請求轉發給 metrics 就能夠了,因此這個 Adapter 對外暴露的是 health 這樣一個監控的 URL,這就能夠了,你的業務就又能夠工做了。
這樣的關鍵,還在於 Pod 之中的容器是經過 localhost 直接通訊的,因此沒有性能損耗,而且這樣一個 Adapter 容器能夠被全公司重用起來,這些都是設計模式給咱們帶來的好處。
本文總結
Pod 是 Kubernetes 項目裏實現「容器設計模式」的核心機制;
「容器設計模式」是 Google Borg 的大規模容器集羣管理最佳實踐之一,也是 Kubernetes 進行復雜應用編排的基礎依賴之一;
全部「設計模式」的本質都是:解耦和重用。
本文做者:張磊 阿里雲容器平臺高級技術專家,CNCF 官方大使
原文連接:https://yq.aliyun.com/article...本文爲雲棲社區原創內容,未經容許不得轉載。