溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

發(fā)布時(shí)間:2020-06-28 16:18:22 來源:網(wǎng)絡(luò) 閱讀:880 作者:阿里系統(tǒng)軟件技術(shù) 欄目:云計(jì)算

作者|張磊 阿里云容器平臺高級技術(shù)專家,CNCF 官方大使

一、為什么需要 Pod

容器的基本概念

我們知道 Pod 是 Kubernetes 項(xiàng)目里面一個(gè)非常重要的概念,也是非常重要的一個(gè)原子調(diào)度單位,但是為什么我們會需要這樣一個(gè)概念呢?在使用容器 Docker 的時(shí)候,也沒有這個(gè)說法。其實(shí),如果想要理解 Pod,首先要理解容器,所以來回顧一下容器的概念:

容器的本質(zhì)實(shí)際上是一個(gè)進(jìn)程,是一個(gè)視圖被隔離,資源受限的進(jìn)程。

容器里面 PID=1 的進(jìn)程就是應(yīng)用本身,這意味著管理虛擬機(jī)等于管理基礎(chǔ)設(shè)施,因?yàn)槲覀兪窃诠芾頇C(jī)器,但管理容器卻等于直接管理應(yīng)用本身。這也是之前說過的不可變基礎(chǔ)設(shè)施的一個(gè)最佳體現(xiàn),這個(gè)時(shí)候,你的應(yīng)用就等于你的基礎(chǔ)設(shè)施,它一定是不可變的。

在以上面的例子為前提的情況下,Kubernetes 又是什么呢?很多人都說 Kubernetes 是云時(shí)代的操作系統(tǒng),這個(gè)非常有意思,因?yàn)槿绻源祟愅?,容器鏡像就是這個(gè)操作系統(tǒng)的軟件安裝包,它們之間是這樣的一個(gè)類比關(guān)系。
從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

真實(shí)操作系統(tǒng)里的例子

如果說 Kubernetes 就是操作系統(tǒng)的話,那么不妨看一下真實(shí)的操作系統(tǒng)的例子。
例子里面有一個(gè)程序叫做 Helloworld,這個(gè) Helloworld 程序?qū)嶋H上是由一組進(jìn)程組成的,需要注意一下,這里說的進(jìn)程實(shí)際上等同于 Linux 中的線程。
因?yàn)?Linux 中的線程是輕量級進(jìn)程,所以如果從 Linux 系統(tǒng)中去查看 Helloworld 中的 pstree,將會看到這個(gè) Helloworld 實(shí)際上是由四個(gè)線程組成的,分別是?{api、main、log、compute}。也就是說,四個(gè)這樣的線程共同協(xié)作,共享 Helloworld 程序的資源,組成了 Helloworld 程序的真實(shí)工作情況。
這是操作系統(tǒng)里面進(jìn)程組或者線程組中一個(gè)非常真實(shí)的例子,以上就是進(jìn)程組的一個(gè)概念。

從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

那么大家不妨思考一下,在真實(shí)的操作系統(tǒng)里面,一個(gè)程序往往是根據(jù)進(jìn)程組來進(jìn)行管理的。Kubernetes 把它類比為一個(gè)操作系統(tǒng),比如說 Linux。針對于容器我們前面提到可以類比為進(jìn)程,就是前面的 Linux 線程。那么 Pod 又是什么呢?實(shí)際上 Pod 就是我們剛剛提到的進(jìn)程組,也就是 Linux 里的線程組。

進(jìn)程組概念

說到進(jìn)程組,首先建議大家至少有個(gè)概念上的理解,然后我們再詳細(xì)的解釋一下。
還是前面那個(gè)例子:Helloworld 程序由四個(gè)進(jìn)程組成,這些進(jìn)程之間會共享一些資源和文件。那么現(xiàn)在有一個(gè)問題:假如說現(xiàn)在把 Helloworld 程序用容器跑起來,你會怎么去做?
當(dāng)然,最自然的一個(gè)解法就是,我現(xiàn)在就啟動(dòng)一個(gè) Docker 容器,里面運(yùn)行四個(gè)進(jìn)程。可是這樣會有一個(gè)問題,這種情況下容器里面 PID=1 的進(jìn)程該是誰? 比如說,它應(yīng)該是我的 main 進(jìn)程,那么問題來了,“誰”又負(fù)責(zé)去管理剩余的 3 個(gè)進(jìn)程呢?
這個(gè)核心問題在于,容器的設(shè)計(jì)本身是一種“單進(jìn)程”模型,不是說容器里只能起一個(gè)進(jìn)程,由于容器的應(yīng)用等于進(jìn)程,所以只能去管理 PID=1 的這個(gè)進(jìn)程,其他再起來的進(jìn)程其實(shí)是一個(gè)托管狀態(tài)。 所以說服務(wù)應(yīng)用進(jìn)程本身就具有“進(jìn)程管理”的能力。
比如說 Helloworld 的程序有 system 的能力,或者直接把容器里 PID=1 的進(jìn)程直接改成 systemd,否則這個(gè)應(yīng)用,或者是容器是沒有辦法去管理很多個(gè)進(jìn)程的。因?yàn)?PID=1 進(jìn)程是應(yīng)用本身,如果現(xiàn)在把這個(gè) PID=1 的進(jìn)程給 kill 了,或者它自己運(yùn)行過程中死掉了,那么剩下三個(gè)進(jìn)程的資源就沒有人回收了,這個(gè)是非常嚴(yán)重的一個(gè)問題。
反過來,如果真的把這個(gè)應(yīng)用本身改成了 systemd,或者在容器里面運(yùn)行了一個(gè) systemd,將會導(dǎo)致另外一個(gè)問題:使得管理容器不再是管理應(yīng)用本身了,而等于是管理 systemd,這里的問題就非常明顯了。比如說我這個(gè)容器里面 run 的程序或者進(jìn)程是 systemd,那么接下來,這個(gè)應(yīng)用是不是退出了?是不是 fail 了?是不是出現(xiàn)異常失敗了?實(shí)際上是沒辦法直接知道的,因?yàn)槿萜鞴芾淼氖?systemd。這就是為什么在容器里面運(yùn)行一個(gè)復(fù)雜程序往往比較困難的一個(gè)原因。
這里再幫大家梳理一下:由于容器實(shí)際上是一個(gè)“單進(jìn)程”模型,所以如果你在容器里啟動(dòng)多個(gè)進(jìn)程,只有一個(gè)可以作為 PID=1 的進(jìn)程,而這時(shí)候,如果這個(gè) PID=1 的進(jìn)程掛了,或者說失敗退出了,那么其他三個(gè)進(jìn)程就會自然而然的成為孤兒,沒有人能夠管理它們,沒有人能夠回收它們的資源,這是一個(gè)非常不好的情況。

注意:Linux 容器的“單進(jìn)程”模型,指的是容器的生命周期等同于 PID=1 的進(jìn)程(容器應(yīng)用進(jìn)程)的生命周期,而不是說容器里不能創(chuàng)建多進(jìn)程。當(dāng)然,一般情況下,容器應(yīng)用進(jìn)程并不具備進(jìn)程管理能力,所以你通過 exec 或者 ssh 在容器里創(chuàng)建的其他進(jìn)程,一旦異常退出(比如 ssh 終止)是很容易變成孤兒進(jìn)程的。

反過來,其實(shí)可以在容器里面 run 一個(gè) systemd,用它來管理其他所有的進(jìn)程。這樣會產(chǎn)生第二個(gè)問題:實(shí)際上沒辦法直接管理我的應(yīng)用了,因?yàn)槲业膽?yīng)用被 systemd 給接管了,那么這個(gè)時(shí)候應(yīng)用狀態(tài)的生命周期就不等于容器生命周期。這個(gè)管理模型實(shí)際上是非常非常復(fù)雜的。
從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

Pod = “進(jìn)程組”

在 Kubernetes 里面,Pod 實(shí)際上正是 Kubernetes 項(xiàng)目為你抽象出來的一個(gè)可以類比為進(jìn)程組的概念。
前面提到的,由四個(gè)進(jìn)程共同組成的一個(gè)應(yīng)用 Helloworld,在 Kubernetes 里面,實(shí)際上會被定義為一個(gè)擁有四個(gè)容器的 Pod,這個(gè)概念大家一定要非常仔細(xì)的理解。
就是說現(xiàn)在有四個(gè)職責(zé)不同、相互協(xié)作的進(jìn)程,需要放在容器里去運(yùn)行,在 Kubernetes 里面并不會把它們放到一個(gè)容器里,因?yàn)檫@里會遇到兩個(gè)問題。那么在 Kubernetes 里會怎么去做呢?它會把四個(gè)獨(dú)立的進(jìn)程分別用四個(gè)獨(dú)立的容器啟動(dòng)起來,然后把它們定義在一個(gè) Pod 里面。
所以當(dāng) Kubernetes 把 Helloworld 給拉起來的時(shí)候,你實(shí)際上會看到四個(gè)容器,它們共享了某些資源,這些資源都屬于 Pod,所以我們說 Pod 在 Kubernetes 里面只有一個(gè)邏輯單位,沒有一個(gè)真實(shí)的東西對應(yīng)說這個(gè)就是 Pod,不會有的。真正起來在物理上存在的東西,就是四個(gè)容器,這四個(gè)容器,或者說是多個(gè)容器的組合就叫做 Pod。并且還有一個(gè)概念一定要非常明確,Pod 是 Kubernetes 分配資源的一個(gè)單位,因?yàn)槔锩娴娜萜饕蚕砟承┵Y源,所以 Pod 也是 Kubernetes 的原子調(diào)度單位。
從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

上面提到的 Pod 設(shè)計(jì),也不是 Kubernetes 項(xiàng)目自己想出來的, 而是早在 Google 研發(fā) Borg 的時(shí)候,就已經(jīng)發(fā)現(xiàn)了這樣一個(gè)問題。這個(gè)在 Borg paper 里面有非常非常明確的描述。簡單來說 Google 工程師發(fā)現(xiàn)在 Borg 下面部署應(yīng)用時(shí),很多場景下都存在著類似于“進(jìn)程與進(jìn)程組”的關(guān)系。更具體的是,這些應(yīng)用之前往往有著密切的協(xié)作關(guān)系,使得它們必須部署在同一臺機(jī)器上并且共享某些信息。
以上就是進(jìn)程組的概念,也是 Pod 的用法。

為什么 Pod 必須是原子調(diào)度單位?

可能到這里大家會有一些問題:雖然了解這個(gè)東西是一個(gè)進(jìn)程組,但是為什么要把 Pod 本身作為一個(gè)概念抽象出來呢?或者說能不能通過調(diào)度把 Pod 這個(gè)事情給解決掉呢?為什么 Pod 必須是 Kubernetes 里面的原子調(diào)度單位?
下面我們通過一個(gè)例子來解釋。
假如現(xiàn)在有兩個(gè)容器,它們是緊密協(xié)作的,所以它們應(yīng)該被部署在一個(gè) Pod 里面。具體來說,第一個(gè)容器叫做 App,就是業(yè)務(wù)容器,它會寫日志文件;第二個(gè)容器叫做 LogCollector,它會把剛剛 App 容器寫的日志文件轉(zhuǎn)發(fā)到后端的 ElasticSearch 中。
兩個(gè)容器的資源需求是這樣的:App 容器需要 1G 內(nèi)存,LogCollector 需要 0.5G 內(nèi)存,而當(dāng)前集群環(huán)境的可用內(nèi)存是這樣一個(gè)情況:Node_A:1.25G 內(nèi)存,Node_B:2G 內(nèi)存。
假如說現(xiàn)在沒有 Pod 概念,就只有兩個(gè)容器,這兩個(gè)容器要緊密協(xié)作、運(yùn)行在一臺機(jī)器上??墒牵绻{(diào)度器先把 App 調(diào)度到了 Node_A 上面,接下來會怎么樣呢?這時(shí)你會發(fā)現(xiàn):LogCollector 實(shí)際上是沒辦法調(diào)度到 Node_A 上的,因?yàn)橘Y源不夠。其實(shí)此時(shí)整個(gè)應(yīng)用本身就已經(jīng)出問題了,調(diào)度已經(jīng)失敗了,必須去重新調(diào)度。

從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

以上就是一個(gè)非常典型的成組調(diào)度失敗的例子。英文叫做:Task co-scheduling 問題,這個(gè)問題不是說不能解,在很多項(xiàng)目里面,這樣的問題都有解法。
比如說在 Mesos 里面,它會做一個(gè)事情,叫做資源囤積(resource hoarding):即當(dāng)所有設(shè)置了 Affinity 約束的任務(wù)都達(dá)到時(shí),才開始統(tǒng)一調(diào)度,這是一個(gè)非常典型的成組調(diào)度的解法。
所以上面提到的“App”和“LogCollector”這兩個(gè)容器,在 Mesos 里面,他們不會說立刻調(diào)度,而是等兩個(gè)容器都提交完成,才開始統(tǒng)一調(diào)度。這樣也會帶來新的問題,首先調(diào)度效率會損失,因?yàn)樾枰却?。由于需要等,還會有外一個(gè)情況會出現(xiàn),就是產(chǎn)生死鎖,即互相等待的一個(gè)情況。這些機(jī)制在 Mesos 里都是需要解決的,也帶來了額外的復(fù)雜度。
另一種解法是 Google 的解法。它在 Omega 系統(tǒng)(就是 Borg 下一代)里面,做了一個(gè)非常復(fù)雜且非常厲害的解法,叫做樂觀調(diào)度。比如說:不管這些沖突的異常情況,先調(diào)度,同時(shí)設(shè)置一個(gè)非常精妙的回滾機(jī)制,這樣經(jīng)過沖突后,通過回滾來解決問題。這個(gè)方式相對來說要更加優(yōu)雅,也更加高效,但是它的實(shí)現(xiàn)機(jī)制是非常復(fù)雜的。這個(gè)有很多人也能理解,就是悲觀鎖的設(shè)置一定比樂觀鎖要簡單。
而像這樣的一個(gè) Task co-scheduling 問題,在 Kubernetes 里,就直接通過 Pod 這樣一個(gè)概念去解決了。因?yàn)樵?Kubernetes 里,這樣的一個(gè) App 容器和 LogCollector 容器一定是屬于一個(gè) Pod 的,它們在調(diào)度時(shí)必然是以一個(gè) Pod 為單位進(jìn)行調(diào)度,所以這個(gè)問題是根本不存在的。

再次理解 Pod

在講了前面這些知識點(diǎn)之后,我們來再次理解一下 Pod,首先 Pod 里面的容器是“超親密關(guān)系”。
這里有個(gè)“超”字需要大家理解,正常來說,有一種關(guān)系叫做親密關(guān)系,這個(gè)親密關(guān)系是一定可以通過調(diào)度來解決的。

從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

比如說現(xiàn)在有兩個(gè) Pod,它們需要運(yùn)行在同一臺宿主機(jī)上,那這樣就屬于親密關(guān)系,調(diào)度器一定是可以幫助去做的。但是對于超親密關(guān)系來說,有一個(gè)問題,即它必須通過 Pod 來解決。因?yàn)槿绻H密關(guān)系賦予不了,那么整個(gè) Pod 或者說是整個(gè)應(yīng)用都無法啟動(dòng)。
什么叫做超親密關(guān)系呢?大概分為以下幾類:

  • 比如說兩個(gè)進(jìn)程之間會發(fā)生文件交換,前面提到的例子就是這樣,一個(gè)寫日志,一個(gè)讀日志;
  • 兩個(gè)進(jìn)程之間需要通過 localhost 或者說是本地的 Socket 去進(jìn)行通信,這種本地通信也是超親密關(guān)系;
  • 這兩個(gè)容器或者是微服務(wù)之間,需要發(fā)生非常頻繁的 RPC 調(diào)用,出于性能的考慮,也希望它們是超親密關(guān)系;
  • 兩個(gè)容器或者是應(yīng)用,它們需要共享某些 Linux Namespace。最簡單常見的一個(gè)例子,就是我有一個(gè)容器需要加入另一個(gè)容器的 Network Namespace。這樣我就能看到另一個(gè)容器的網(wǎng)絡(luò)設(shè)備,和它的網(wǎng)絡(luò)信息。

像以上幾種關(guān)系都屬于超親密關(guān)系,它們都是在 Kubernetes 中會通過 Pod 的概念去解決的。
現(xiàn)在我們理解了 Pod 這樣的概念設(shè)計(jì),理解了為什么需要 Pod。它解決了兩個(gè)問題:

  1. 我們怎么去描述超親密關(guān)系;
  2. 我們怎么去對超親密關(guān)系的容器或者說是業(yè)務(wù)去做統(tǒng)一調(diào)度,這是 Pod 最主要的一個(gè)訴求。

二、Pod 的實(shí)現(xiàn)機(jī)制

Pod 要解決的問題

像 Pod 這樣一個(gè)東西,本身是一個(gè)邏輯概念。那在機(jī)器上,它究竟是怎么實(shí)現(xiàn)的呢?這就是我們要解釋的第二個(gè)問題。既然說 Pod 要解決這個(gè)問題,核心就在于如何讓一個(gè) Pod 里的多個(gè)容器之間最高效的共享某些資源和數(shù)據(jù)。
因?yàn)槿萜髦g原本是被 Linux Namespace 和 cgroups 隔開的,所以現(xiàn)在實(shí)際要解決的是怎么去打破這個(gè)隔離,然后共享某些事情和某些信息。這就是 Pod 的設(shè)計(jì)要解決的核心問題所在。

所以說具體的解法分為兩個(gè)部分:網(wǎng)絡(luò)和存儲。

1.共享網(wǎng)絡(luò)

第一個(gè)問題是 Pod 里的多個(gè)容器怎么去共享網(wǎng)絡(luò)?下面是個(gè)例子:

比如說現(xiàn)在有一個(gè) Pod,其中包含了一個(gè)容器 A 和一個(gè)容器 B,它們兩個(gè)就要共享 Network Namespace。在 Kubernetes 里的解法是這樣的:它會在每個(gè) Pod 里,額外起一個(gè) Infra container 小容器來共享整個(gè) Pod 的? Network Namespace。
Infra container 是一個(gè)非常小的鏡像,大概 100~200KB 左右,是一個(gè)匯編語言寫的、永遠(yuǎn)處于“暫?!睜顟B(tài)的容器。由于有了這樣一個(gè) Infra container 之后,其他所有容器都會通過 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。
所以說一個(gè) Pod 里面的所有容器,它們看到的網(wǎng)絡(luò)視圖是完全一樣的。即:它們看到的網(wǎng)絡(luò)設(shè)備、IP地址、Mac地址等等,跟網(wǎng)絡(luò)相關(guān)的信息,其實(shí)全是一份,這一份都來自于 Pod 第一次創(chuàng)建的這個(gè) Infra container。這就是 Pod 解決網(wǎng)絡(luò)共享的一個(gè)解法。
在 Pod 里面,一定有一個(gè) IP 地址,是這個(gè) Pod 的 Network Namespace 對應(yīng)的地址,也是這個(gè) Infra container 的 IP 地址。所以大家看到的都是一份,而其他所有網(wǎng)絡(luò)資源,都是一個(gè) Pod 一份,并且被 Pod 中的所有容器共享。這就是 Pod 的網(wǎng)絡(luò)實(shí)現(xiàn)方式。
由于需要有一個(gè)相當(dāng)于說中間的容器存在,所以整個(gè) Pod 里面,必然是 Infra container 第一個(gè)啟動(dòng)。并且整個(gè) Pod 的生命周期是等同于 Infra container 的生命周期的,與容器 A 和 B 是無關(guān)的。這也是為什么在 Kubernetes 里面,它是允許去單獨(dú)更新 Pod 里的某一個(gè)鏡像的,即:做這個(gè)操作,整個(gè) Pod 不會重建,也不會重啟,這是非常重要的一個(gè)設(shè)計(jì)。
從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

2.共享存儲

第二問題:Pod 怎么去共享存儲?Pod 共享存儲就相對比較簡單。
比如說現(xiàn)在有兩個(gè)容器,一個(gè)是 Nginx,另外一個(gè)是非常普通的容器,在 Nginx 里放一些文件,讓我能通過 Nginx 訪問到。所以它需要去 share 這個(gè)目錄。我 share 文件或者是 share 目錄在 Pod 里面是非常簡單的,實(shí)際上就是把 volume 變成了 Pod level。然后所有容器,就是所有同屬于一個(gè) Pod 的容器,他們共享所有的 volume。

從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

比如說上圖的例子,這個(gè) volume 叫做 shared-data,它是屬于 Pod level 的,所以在每一個(gè)容器里可以直接聲明:要掛載 shared-data 這個(gè) volume,只要你聲明了你掛載這個(gè) volume,你在容器里去看這個(gè)目錄,實(shí)際上大家看到的就是同一份。這個(gè)就是 Kubernetes 通過 Pod 來給容器共享存儲的一個(gè)做法。
所以在之前的例子中,應(yīng)用容器 App 寫了日志,只要這個(gè)日志是寫在一個(gè) volume 中,只要聲明掛載了同樣的 volume,這個(gè) volume 就可以立刻被另外一個(gè) LogCollector 容器給看到。以上就是 Pod 實(shí)現(xiàn)存儲的方式。

三、詳解容器設(shè)計(jì)模式

現(xiàn)在我們知道了為什么需要 Pod,也了解了 Pod 這個(gè)東西到底是怎么實(shí)現(xiàn)的。最后,以此為基礎(chǔ),詳細(xì)介紹一下 Kubernetes 非常提倡的一個(gè)概念,叫做容器設(shè)計(jì)模式。

舉例

接下來將會用一個(gè)例子來給大家進(jìn)行講解。
比如我現(xiàn)在有一個(gè)非常常見的一個(gè)訴求:我現(xiàn)在要發(fā)布一個(gè)應(yīng)用,這個(gè)應(yīng)用是 JAVA 寫的,有一個(gè) WAR 包需要把它放到 Tomcat 的 web APP 目錄下面,這樣就可以把它啟動(dòng)起來了。可是像這樣一個(gè) WAR 包或 Tomcat 這樣一個(gè)容器的話,怎么去做,怎么去發(fā)布?這里面有幾種做法。

從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

  • 第一種方式:可以把 WAR 包和 Tomcat 打包放進(jìn)一個(gè)鏡像里面。但是這樣帶來一個(gè)問題,就是現(xiàn)在這個(gè)鏡像實(shí)際上揉進(jìn)了兩個(gè)東西。那么接下來,無論是我要更新 WAR 包還是說我要更新 Tomcat,都要重新做一個(gè)新的鏡像,這是比較麻煩的;

  • 第二種方式:就是鏡像里面只打包 Tomcat。它就是一個(gè) Tomcat,但是需要使用數(shù)據(jù)卷的方式,比如說 hostPath,從宿主機(jī)上把 WAR 包掛載進(jìn)我們 Tomcat 容器中,掛到我的 web APP 目錄下面,這樣把這個(gè)容器啟用起來之后,里面就能用了。

但是這時(shí)會發(fā)現(xiàn)一個(gè)問題:這種做法一定需要維護(hù)一套分布式存儲系統(tǒng)。因?yàn)檫@個(gè)容器可能第一次啟動(dòng)是在宿主機(jī) A 上面,第二次重新啟動(dòng)就可能跑到 B 上去了,容器它是一個(gè)可遷移的東西,它的狀態(tài)是不保持的。所以必須維護(hù)一套分布式存儲系統(tǒng),使容器不管是在 A 還是在 B 上,都可以找到這個(gè) WAR 包,找到這個(gè)數(shù)據(jù)。

注意,即使有了分布式存儲系統(tǒng)做 Volume,你還需要負(fù)責(zé)維護(hù) Volume 里的 WAR 包。比如:你需要單獨(dú)寫一套 Kubernetes Volume 插件,用來在每次 Pod 啟動(dòng)之前,把應(yīng)用啟動(dòng)所需的 WAR 包下載到這個(gè) Volume 里,然后才能被應(yīng)用掛載使用到。

這樣操作帶來的復(fù)雜程度還是比較高的,且這個(gè)容器本身必須依賴于一套持久化的存儲插件(用來管理 Volume 里的 WAR 包內(nèi)容)。

InitContainer

所以大家有沒有考慮過,像這樣的組合方式,有沒有更加通用的方法?哪怕在本地 Kubernetes 上,沒有分布式存儲的情況下也能用、能玩、能發(fā)布。

實(shí)際上方法是有的,在 Kubernetes 里面,像這樣的組合方式,叫做 Init Container。

從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

還是同樣一個(gè)例子:在上圖的 yaml 里,首先定義一個(gè) Init Container,它只做一件事情,就是把 WAR 包從鏡像里拷貝到一個(gè) Volume 里面,它做完這個(gè)操作就退出了,所以 Init Container 會比用戶容器先啟動(dòng),并且嚴(yán)格按照定義順序來依次執(zhí)行。
然后,這個(gè)關(guān)鍵在于剛剛拷貝到的這樣一個(gè)目的目錄:APP 目錄,實(shí)際上是一個(gè) Volume。而我們前面提到,一個(gè) Pod 里面的多個(gè)容器,它們是可以共享 Volume 的,所以現(xiàn)在這個(gè) Tomcat 容器,只是打包了一個(gè) Tomcat 鏡像。但在啟動(dòng)的時(shí)候,要聲明使用 APP 目錄作為我的 Volume,并且要把它們掛載在 Web APP 目錄下面。
而這個(gè)時(shí)候,由于前面運(yùn)行過了一個(gè) Init Container,已經(jīng)執(zhí)行完拷貝操作了,所以這個(gè) Volume 里面已經(jīng)存在了應(yīng)用的 WAR 包:就是 sample.war,絕對已經(jīng)存在這個(gè) Volume 里面了。等到第二步執(zhí)行啟動(dòng)這個(gè) Tomcat 容器的時(shí)候,去掛這個(gè) Volume,一定能在里面找到前面拷貝來的 sample.war。

所以可以這樣去描述:這個(gè) Pod 就是一個(gè)自包含的,可以把這一個(gè) Pod 在全世界任何一個(gè) Kubernetes 上面都順利啟用起來。不用擔(dān)心沒有分布式存儲、Volume 不是持久化的,它一定是可以公布的。

所以這是一個(gè)通過組合兩個(gè)不同角色的容器,并且按照一些像 Init Container 的編排方式,統(tǒng)一去打包這樣一個(gè)應(yīng)用,把它用 Pod 來去做的非常典型的一個(gè)例子。像這樣的一個(gè)概念,在 Kubernetes 里面就是一個(gè)非常經(jīng)典的容器設(shè)計(jì)模式,叫做:“Sidecar”。

容器設(shè)計(jì)模式:Sidecar

什么是 Sidecar?就是說其實(shí)在 Pod 里面,可以定義一些專門的容器,來執(zhí)行主業(yè)務(wù)容器所需要的一些輔助工作,比如我們前面舉的例子,其實(shí)就干了一個(gè)事兒,這個(gè) Init Container,它就是一個(gè) Sidecar,它只負(fù)責(zé)把鏡像里的 WAR 包拷貝到共享目錄里面,以便被 Tomcat 能夠用起來。

其它有哪些操作呢?比如說:

  • 原本需要在容器里面執(zhí)行 SSH 需要干的一些事情,可以寫腳本、一些前置的條件,其實(shí)都可以通過像 Init Container 或者另外像 Sidecar 的方式去解決;

  • 當(dāng)然還有一個(gè)典型例子就是我的日志收集,日志收集本身是一個(gè)進(jìn)程,是一個(gè)小容器,那么就可以把它打包進(jìn) Pod 里面去做這個(gè)收集工作;

  • 還有一個(gè)非常重要的東西就是 Debug 應(yīng)用,實(shí)際上現(xiàn)在 Debug 整個(gè)應(yīng)用都可以在應(yīng)用 Pod 里面再次定義一個(gè)額外的小的 Container,它可以去 exec 應(yīng)用 pod 的 namespace;

  • 查看其他容器的工作狀態(tài),這也是它可以做的事情。不再需要去 SSH 登陸到容器里去看,只要把監(jiān)控組件裝到額外的小容器里面就可以了,然后把它作為一個(gè) Sidecar 啟動(dòng)起來,跟主業(yè)務(wù)容器進(jìn)行協(xié)作,所以同樣業(yè)務(wù)監(jiān)控也都可以通過 Sidecar 方式來去做。

這種做法一個(gè)非常明顯的優(yōu)勢就是在于其實(shí)將輔助功能從我的業(yè)務(wù)容器解耦了,所以我就能夠獨(dú)立發(fā)布 Sidecar 容器,并且更重要的是這個(gè)能力是可以重用的,即同樣的一個(gè)監(jiān)控 Sidecar 或者日志 Sidecar,可以被全公司的人共用的。這就是設(shè)計(jì)模式的一個(gè)威力。

從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

Sidecar:應(yīng)用與日志收集

接下來,我們再詳細(xì)細(xì)化一下 Sidecar 這樣一個(gè)模式,它還有一些其他的場景。

比如說前面提到的應(yīng)用日志收集,業(yè)務(wù)容器將日志寫在一個(gè) Volume 里面,而由于 Volume 在 Pod 里面是被共享的,所以日志容器 —— 即 Sidecar 容器一定可以通過共享該 Volume,直接把日志文件讀出來,然后存到遠(yuǎn)程存儲里面,或者轉(zhuǎn)發(fā)到另外一個(gè)例子?,F(xiàn)在業(yè)界常用的 Fluentd 日志進(jìn)程或日志組件,基本上都是這樣的工作方式。

從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

Sidecar:代理容器

Sidecar 的第二個(gè)用法,可以稱作為代理容器 Proxy。什么叫做代理容器呢?
假如現(xiàn)在有個(gè) Pod 需要訪問一個(gè)外部系統(tǒng),或者一些外部服務(wù),但是這些外部系統(tǒng)是一個(gè)集群,那么這個(gè)時(shí)候如何通過一個(gè)統(tǒng)一的、簡單的方式,用一個(gè) IP 地址,就把這些集群都訪問到?有一種方法就是:修改代碼。因?yàn)榇a里記錄了這些集群的地址;另外還有一種解耦的方法,即通過 Sidecar 代理容器。
簡單說,單獨(dú)寫一個(gè)這么小的 Proxy,用來處理對接外部的服務(wù)集群,它對外暴露出來只有一個(gè) IP 地址就可以了。所以接下來,業(yè)務(wù)容器主要訪問 Proxy,然后由 Proxy 去連接這些服務(wù)集群,這里的關(guān)鍵在于 Pod 里面多個(gè)容器是通過 localhost 直接通信的,因?yàn)樗鼈兺瑢儆谝粋€(gè) network Namespace,網(wǎng)絡(luò)視圖都一樣,所以它們倆通信 localhost,并沒有性能損耗。
所以說代理容器除了做了解耦之外,并不會降低性能,更重要的是,像這樣一個(gè)代理容器的代碼就又可以被全公司重用了。

從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

Sidecar:適配器容器

Sidecar 的第三個(gè)設(shè)計(jì)模式 —— 適配器容器 Adapter,什么叫 Adapter 呢?

現(xiàn)在業(yè)務(wù)暴露出來的 API,比如說有個(gè) API 的一個(gè)格式是 A,但是現(xiàn)在有一個(gè)外部系統(tǒng)要去訪問我的業(yè)務(wù)容器,它只知道的一種格式是 API B ,所以要做一個(gè)工作,就是把業(yè)務(wù)容器怎么想辦法改掉,要去改業(yè)務(wù)代碼。但實(shí)際上,你可以通過一個(gè) Adapter 幫你來做這層轉(zhuǎn)換。
從零開始入門 K8s| 詳解 Pod 及容器設(shè)計(jì)模式

有個(gè)例子:現(xiàn)在業(yè)務(wù)容器暴露出來的監(jiān)控接口是 /metrics,訪問這個(gè)容器的 metrics 的 URL 就可以拿到了??墒乾F(xiàn)在,這個(gè)監(jiān)控系統(tǒng)升級了,它訪問的 URL 是 /health,我只認(rèn)得暴露出 health 健康檢查的 URL,才能去做監(jiān)控,metrics 不認(rèn)識。那這個(gè)怎么辦?那就需要改代碼了,但可以不去改代碼,額外寫一個(gè) Adapter,用來把所有對 health 的這個(gè)請求轉(zhuǎn)發(fā)給 metrics 就可以了,所以這個(gè) Adapter 對外暴露的是 health 這樣一個(gè)監(jiān)控的 URL,這就可以了,你的業(yè)務(wù)就又可以工作了。
這樣的關(guān)鍵,還在于 Pod 之中的容器是通過 localhost 直接通信的,所以沒有性能損耗,并且這樣一個(gè) Adapter 容器可以被全公司重用起來,這些都是設(shè)計(jì)模式給我們帶來的好處。

本文總結(jié)

  • Pod 是 Kubernetes 項(xiàng)目里實(shí)現(xiàn)“容器設(shè)計(jì)模式”的核心機(jī)制;
  • “容器設(shè)計(jì)模式”是 Google Borg 的大規(guī)模容器集群管理最佳實(shí)踐之一,也是 Kubernetes 進(jìn)行復(fù)雜應(yīng)用編排的基礎(chǔ)依賴之一;
  • 所有“設(shè)計(jì)模式”的本質(zhì)都是:解耦和重用。

最后

Pod 與容器設(shè)計(jì)模式是 Kubernetes 體系里面最重要的一個(gè)基礎(chǔ)知識點(diǎn),希望讀者能夠仔細(xì)揣摩和掌握。在這里,我建議你去重新審視一下之前自己公司或者團(tuán)隊(duì)里使用 Pod 方式,是不是或多或少采用了所謂“富容器”這種設(shè)計(jì)呢?這種設(shè)計(jì),只是一種過渡形態(tài),會培養(yǎng)出很多非常不好的運(yùn)維習(xí)慣。我強(qiáng)烈建議你逐漸采用容器設(shè)計(jì)模式的思想,對富容器進(jìn)行解耦,將它們拆分成多個(gè)容器組成一個(gè) Pod。這也正是當(dāng)前阿里巴巴“全面上云”戰(zhàn)役中正在全力推進(jìn)的一項(xiàng)重要的工作內(nèi)容。

“ 阿里巴巴云原生微信公眾號(ID:Alicloudnative)關(guān)注微服務(wù)、Serverless、容器、Service Mesh 等技術(shù)領(lǐng)域、聚焦云原生流行技術(shù)趨勢、云原生大規(guī)模的落地實(shí)踐,做最懂云原生開發(fā)者的技術(shù)公眾號?!?/strong>

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI