Kubernetes 深入理解 Pod
一、為什么需要 Pod
容器的基本概念
現在來看第一個問題:為什么需要 Pod?我們知道 Pod 是 Kubernetes 項目里面一個非常重要的概念,也是非常重要的一個原子調度單位,但是為什么我們會需要這樣一個概念呢?我們在使用容器 Docker 的時候,也沒有這個說法。其實如果要理解 Pod,我們首先要理解容器,所以首先來回顧一下容器的概念:
容器的本質實際上是一個進程,是一個視圖被隔離,資源受限的進程。
容器里面 PID=1 的進程就是應用本身,這意味著管理虛擬機等于管理基礎設施,因為我們是在管理機器,但管理容器卻等于直接管理應用本身。這也是之前說過的不可變基礎設施的一個最佳體現,這個時候,你的應用就等于你的基礎設施,它一定是不可變的。
在以上面的例子為前提的情況下,Kubernetes 又是什么呢?我們知道,很多人都說 Kubernetes 是云時代的操作系統,這個非常有意思,因為如果以此類推,容器鏡像就是這個操作系統的軟件安裝包,它們之間是這樣的一個類比關系。
真實操作系統里的例子
如果說 Kubernetes 就是操作系統的話,那么我們不妨看一下真實的操作系統的例子。
例子里面有一個程序叫做 Helloworld,這個 Helloworld 程序實際上是由一組進程組成的,需要注意一下,這里說的進程實際上等同于 Linux 中的線程。
因為 Linux 中的線程是輕量級進程,所以如果從 Linux 系統中去查看 Helloworld 中的 pstree,將會看到這個 Helloworld 實際上是由四個線程組成的,分別是?{api、main、log、compute}。也就是說,四個這樣的線程共同協作,共享 Helloworld 程序的資源,組成了 Helloworld 程序的真實工作情況。
這是操作系統里面進程組或者線程組中一個非常真實的例子,以上就是進程組的一個概念。
?那么大家不妨思考一下,在真實的操作系統里面,一個程序往往是根據進程組來進行管理的。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 上的,因為資源不夠。其實此時整個應用本身就已經出問題了,調度已經失敗了,必須去重新調度。
以上就是一個非常典型的成組調度失敗的例子。英文叫做: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。這樣我就能看到另一個容器的網絡設備,和它的網絡信息。
?二、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 實現存儲的方式。?
總結
以上是生活随笔為你收集整理的Kubernetes 深入理解 Pod的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: IC芯片制造过程简介
- 下一篇: 批处理文件——BAT学习
