Kubernetes 如何重塑虛擬機(jī)
Kubernetes 大規(guī)模使用過的都說簡單,沒有用過清一色的都是使用復(fù)雜、概念晦澀難懂,因此即使是那些具有一定服務(wù)器端知識(shí)的人也可能會(huì)感到困惑。讓我在這里嘗試一些不同的東西。與其解釋一個(gè)不熟悉的問題(如何在 Kubernetes 中運(yùn)行 Web 服務(wù)?)和另一個(gè)(你只需要一個(gè)清單,三個(gè) sidecar 和一堆 gobbledygook),我將嘗試揭示 Kubernetes 技術(shù)發(fā)展趨勢。
如果您已經(jīng)知道如何使用虛擬機(jī)運(yùn)行服務(wù),希望您會(huì)發(fā)現(xiàn)最終并沒有太大區(qū)別。如果您對(duì)大規(guī)模運(yùn)營服務(wù)完全不熟悉,那么跟隨技術(shù)的發(fā)展可能會(huì)幫助您了解當(dāng)代方法。
像往常一樣,這篇文章并不全面。相反,它試圖總結(jié)我的個(gè)人經(jīng)歷以及計(jì)算機(jī)多年來虛擬化是如何形成的。
如何使用虛擬機(jī)部署服務(wù)
早在 2010 年,當(dāng)我剛剛開始我的軟件工程師職業(yè)生涯時(shí),使用虛擬機(jī)(或有時(shí)是裸機(jī))部署應(yīng)用程序非常普遍。
你需要一個(gè)臨時(shí)的 Linux 虛擬機(jī),將 Nginx 或 Apache 反向代理放在它前面,然后在它旁邊運(yùn)行一堆守護(hù)進(jìn)程和 cronjobs。
這樣的機(jī)器將代表服務(wù)的單個(gè)實(shí)例,打個(gè)比方,就類似于一個(gè)盒子,而服務(wù)本身將只是分布在網(wǎng)絡(luò)上的一組命名的相同機(jī)器。根據(jù)您的業(yè)務(wù)規(guī)模,您可能只有幾個(gè)、幾十個(gè)、幾百個(gè)甚至幾千個(gè)盒子分布在為生產(chǎn)流量提供服務(wù)的多個(gè)盒子中。
服務(wù)的抽象將應(yīng)用程序的復(fù)雜性隱藏在單個(gè)入口點(diǎn)之后
使用虛擬機(jī)部署服務(wù)帶來的挑戰(zhàn)
通常,機(jī)器群的大小將定義配置(安裝操作系統(tǒng)和軟件包)、擴(kuò)展(產(chǎn)生相同的盒子)、服務(wù)發(fā)現(xiàn)(將一組盒子隱藏在一個(gè)名稱后面)和部署(運(yùn)送新版本的代碼)的方式到盒子)完成了。
如果你是一個(gè)只有幾個(gè)類似寵物的盒子的公司,您可能會(huì)發(fā)現(xiàn)自己很少半手動(dòng)地配置新盒子。這通常意味著總線系數(shù)低(由于缺乏自動(dòng)化)、安全狀況差(由于缺乏定期補(bǔ)丁更新)以及可能更長的災(zāi)難恢復(fù)。從好的方面來說,管理成本會(huì)非常低,因?yàn)椴恍枰獢U(kuò)展,您的部署會(huì)很簡單(只需幾個(gè)盒子來交付代碼),而且服務(wù)發(fā)現(xiàn)會(huì)很簡單(由于相當(dāng)靜態(tài)地址池)。
對(duì)于擁有大量盒子的公司來說,現(xiàn)實(shí)情況會(huì)有所不同。大量機(jī)器通常會(huì)導(dǎo)致更頻繁地需要配置新盒子(更多的盒子意味著更多的破損)。你會(huì)投資自動(dòng)化(投資回報(bào)率會(huì)很高),最終得到許多牛一樣的盒子。作為不斷重新創(chuàng)建盒子的副產(chǎn)品,您將增加總線因素并改善安全狀況(將自動(dòng)更新和安裝補(bǔ)?。?。在它的反面,會(huì)存在低效的擴(kuò)展(由于每日/每年的流量分布不均勻),過于復(fù)雜的部署(很難將代碼快速交付到許多機(jī)器上),以及脆弱的服務(wù)發(fā)現(xiàn)(您是否嘗試過大規(guī)模運(yùn)行consul或zookeeper?)會(huì)導(dǎo)致更高的運(yùn)營成本。
Amazon Elastic Compute Cloud (EC2) 等早期云產(chǎn)品允許更快地啟動(dòng)(和關(guān)閉)機(jī)器;使用packer制作并使用cloud-init自定義的機(jī)器鏡像,使配置稍微容易一些;puppet和ansible等自動(dòng)化工具支持應(yīng)用基礎(chǔ)架構(gòu)更改并大規(guī)模交付新版本的軟件。但是,仍有很大的改進(jìn)空間。
Docker 容器解決了什么問題
在過去,擁有不同的生產(chǎn)和開發(fā)環(huán)境是很常見的。這將導(dǎo)致應(yīng)用程序可能在您安裝的 Debian 機(jī)器上本地運(yùn)行,但由于缺少依賴項(xiàng)而無法在生產(chǎn)中的 vanilla CentOS 上啟動(dòng)。相反,在本地安裝應(yīng)用程序的依賴項(xiàng)可能會(huì)遇到一些麻煩,但由于資源需求高,為每個(gè)服務(wù)運(yùn)行預(yù)配置的虛擬機(jī)進(jìn)行開發(fā)將是不可行的。
即使在生產(chǎn)中,虛擬機(jī)的龐大也是一個(gè)問題。每個(gè)服務(wù)擁有一個(gè)虛擬機(jī)可能會(huì)導(dǎo)致低于最佳資源利用率和/或相當(dāng)大的存儲(chǔ)和計(jì)算開銷,但是將多個(gè)服務(wù)放在一個(gè)盒子中可能會(huì)使它們發(fā)生資源搶占沖突。
世界顯然需要一個(gè)更輕量級(jí)的盒子。
容器 - 單個(gè)應(yīng)用程序的盒子
這就是容器的用武之地。就像允許將裸機(jī)服務(wù)器分割成幾臺(tái)更?。ǜ阋耍┑臋C(jī)器的虛擬機(jī)一樣,容器將一個(gè) Linux 機(jī)器分割成數(shù)十個(gè)甚至數(shù)百個(gè)獨(dú)立的環(huán)境。
在一個(gè)容器中,您可能會(huì)覺得您擁有自己的虛擬機(jī),以及您最喜歡的 Linux 發(fā)行版。好吧,至少乍一看。從外部看,容器只是在主機(jī)操作系統(tǒng)上運(yùn)行并共享其內(nèi)核的常規(guī)進(jìn)程。
打包應(yīng)用程序及其所有依賴項(xiàng)(包括特定版本的操作系統(tǒng)用戶空間和庫)的能力,將其作為容器鏡像發(fā)送,并在安裝了 Docker(或類似工具)的任何位置的標(biāo)準(zhǔn)化執(zhí)行環(huán)境中運(yùn)行,極大地提高了工作負(fù)載的可重復(fù)性.
由于容器邊界的輕量級(jí)實(shí)現(xiàn),計(jì)算開銷顯著降低,允許單個(gè)生產(chǎn)服務(wù)器運(yùn)行可能屬于多個(gè)(微)服務(wù)的數(shù)十個(gè)不同容器。當(dāng)然,這可能以降低安全性為代價(jià)。
由于不可變和共享的鏡像層,鏡像存儲(chǔ)和分發(fā)也變得更加高效。
在某種程度上,容器也改變了供應(yīng)的方式。使用(粗心編寫的)Dockerfiles 和ko和Jib之類的(神奇的)工具,責(zé)任極大地轉(zhuǎn)移到了開發(fā)人員身上,簡化了生產(chǎn) VM 的要求——從開發(fā)人員的角度來看,你只需要一個(gè) Docker-(或更高版本的 OCI-)兼容應(yīng)用程序的運(yùn)行時(shí),因此您不會(huì)再因?yàn)橐蟀惭b某個(gè)版本的 Linux 或系統(tǒng)包而惹惱您的運(yùn)維朋友。
最重要的是,容器加速了運(yùn)行服務(wù)的替代方式的開發(fā)?,F(xiàn)在有 17 種方法可以在 AWS 上運(yùn)行容器https://www.lastweekinaws.com/blog/the-17-ways-to-run-containers-on-aws/,其中大部分是完全無服務(wù)器的,在足夠簡單的情況下,您可以使用 Lambda 或 Fargate 并從牛一樣的盒子中受益!
容器不能解決什么問題
容器被證明是一個(gè)非常方便的開發(fā)工具。構(gòu)建容器鏡像也比構(gòu)建 VM 更簡單、更快捷。再加上如何有效分離團(tuán)隊(duì)之間職責(zé)的老組織問題,導(dǎo)致典型企業(yè)的平均服務(wù)數(shù)量顯著增加,每個(gè)服務(wù)的盒子數(shù)量也有類似的增加。
Docker 普及的容器形式實(shí)際上具有很強(qiáng)的欺騙性。乍一看,每個(gè)服務(wù)實(shí)例都有一個(gè)便宜的專用 VM。但是,如果這樣的實(shí)例需要sidecar(例如在您的 Web 應(yīng)用程序前面運(yùn)行的本地反向代理來終止 TLS 連接或加載秘密和/或預(yù)熱緩存的守護(hù)程序),您會(huì)立即感覺到疼痛,這就是容器與虛擬機(jī)的本質(zhì)區(qū)別。
Docker 容器被刻意設(shè)計(jì)為只包含一個(gè)應(yīng)用程序。一個(gè)容器——一個(gè) Nginx;一個(gè)容器 - 一個(gè) Python Web 服務(wù)器;一個(gè)容器 - 一個(gè)守護(hù)進(jìn)程。容器的生命周期將綁定到該應(yīng)用程序的生命周期。并且特別不鼓勵(lì)將像systemd這樣的 init 進(jìn)程作為頂級(jí)入口點(diǎn)運(yùn)行。
因此,要從本文開頭的圖表重新創(chuàng)建一個(gè) VM-box,您需要擁有三個(gè)具有共享網(wǎng)絡(luò)堆棧的協(xié)調(diào)容器-box(嗯,至少localhost需要相同)。要運(yùn)行該服務(wù)的兩個(gè)實(shí)例,您需要三個(gè)三個(gè)一組的六個(gè)容器!
從擴(kuò)展的角度來看,這意味著我們需要一起擴(kuò)展(和縮減)一些容器。部署也需要同步進(jìn)行。新版本的 Web 應(yīng)用程序容器可能會(huì)開始使用新的端口號(hào),并與舊版本的反向代理容器不兼容。
我們顯然在這里錯(cuò)過了一個(gè)抽象,它與容器一樣輕量級(jí),但與原始 VM 盒子一樣富有表現(xiàn)力。
此外,容器本身也沒有提供任何將盒子分組為服務(wù)的方法。但他們促成了箱子人數(shù)的增加!Docker 競相用它的 Swarm 產(chǎn)品解決這些問題,但另一個(gè)系統(tǒng)贏了……
Kubernetes 解決了這一切……還是沒有?
Kubernetes 設(shè)計(jì)師顯然沒有發(fā)明新的運(yùn)行容器的方法,而是決定重新創(chuàng)建良好的舊的基于 VM 的服務(wù)架構(gòu),但使用容器作為構(gòu)建塊。好吧,至少這是我的看法。
但對(duì)我來說,作為以前有 VM 經(jīng)驗(yàn)的人,一旦我了解了新術(shù)語并弄清楚了類似的概念,許多最初的 Kubernetes 想法就會(huì)開始看起來很熟悉。
Kubernetes Pod 是新的虛擬機(jī)
讓我們從 Pod 抽象開始。Pod 是您可以在 Kubernetes 中運(yùn)行的最小的東西。最簡單的 Pod 定義如下所示:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.1
ports:
- containerPort: 80
乍一看,上面的清單只是說明要運(yùn)行什么鏡像(以及如何命名)。但是請(qǐng)注意containers屬性是一個(gè)列表!現(xiàn)在,回到那個(gè)nginx + web app例子,在 Kubernetes 中,您可以簡單地將反向代理和應(yīng)用程序本身放在一個(gè)盒子中,而不是為 Web 應(yīng)用程序容器運(yùn)行額外的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: foo-instance-1
spec:
containers:
- name: nginx # <-- sidecar container
image: nginx:1.20.1
ports:
- containerPort: 80
- name: app # <-- main container
image: app:0.3.2
然而,Pod 不僅僅是一組容器。Pod 中容器之間的隔離邊界被削弱。就像在 VM 上運(yùn)行的常規(guī)進(jìn)程一樣,Pod 中的容器可以通過localhost或使用傳統(tǒng)的 IPC 方式自由通信。同時(shí),每個(gè)容器仍然有一個(gè)獨(dú)立的根文件系統(tǒng),以保持打包應(yīng)用程序及其依賴項(xiàng)的好處。對(duì)我來說,這看起來像是在嘗試同時(shí)利用 VM 和容器世界的最佳部分:
擴(kuò)展和部署 Pod 很簡單
現(xiàn)在,當(dāng)我們得到新的盒子時(shí),我們?nèi)绾芜\(yùn)行多個(gè)它們來組成一個(gè)服務(wù)?換句話說,如何在 Kubernetes 中進(jìn)行擴(kuò)展和部署?
事實(shí)證明,它非常簡單,至少在基本場景中是這樣。Kubernetes 引入了一個(gè)方便的抽象,稱為 Deployment。最小的 Deployment 定義由名稱和 Pod 模板組成,但指定所需的 Pod 副本數(shù)量也很常見:
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo-deployment-1
labels:
app: foo
spec:
replicas: 10
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
<Pod definition comes here>
Kubernetes 的偉大之處在于,作為開發(fā)人員,您并不關(guān)心服務(wù)器(或 Kubernetes 術(shù)語中的節(jié)點(diǎn))。您根據(jù) Pod 組進(jìn)行思考和操作,它們會(huì)自動(dòng)調(diào)動(dòng)(和重新分布)到集群節(jié)點(diǎn):
這使得 Kubernetes 更像是一種無服務(wù)器技術(shù)。但同時(shí),Pod 的外觀和行為與過去熟悉的 VM 非常相似(除了您不需要管理它們),因此您可以在熟悉的抽象中設(shè)計(jì)和推理您的應(yīng)用程序:
內(nèi)置服務(wù)發(fā)現(xiàn)
Kubernetes Service - 一組命名的 Pod。
Kubernetes 設(shè)計(jì)人員肯定知道,僅僅創(chuàng)建 N 個(gè)盒子的副本并將其稱為服務(wù)是不夠的??蛻舳藨?yīng)該能夠使用單個(gè)(可能是邏輯的)名稱訪問服務(wù),并且服務(wù)發(fā)現(xiàn)系統(tǒng)應(yīng)該能夠?qū)⒃撁Q轉(zhuǎn)換為特定的 IP 地址(類似于我們理解的負(fù)載均衡器,服務(wù)于特定的實(shí)例) )。
過去,您需要一個(gè)單獨(dú)的(并且要求非常高的)解決方案。但是,Kubernetes 內(nèi)置了這個(gè)功能,而且默認(rèn)實(shí)現(xiàn)還不錯(cuò)!它還可以使用Linkerd或Istio等服務(wù)網(wǎng)格進(jìn)行擴(kuò)展,使其更加強(qiáng)大。
將一組 Pod 轉(zhuǎn)換為服務(wù)唯一需要做的就是創(chuàng)建一個(gè) Service 對(duì)象(不是真正的創(chuàng)建服務(wù),只是一個(gè)網(wǎng)絡(luò)層面的抽象)。
下面是一個(gè)簡單的 Kubernetes Service 定義的樣子:
apiVersion: v1
kind: Service
metadata:
name: foo
spec:
selector:
app: foo
ports:
- protocol: TCP
port: 80
上面的清單允許app=foo使用defaultDNS 名稱(如foo.default.svc.cluster.local. 而且這一切都沒有在集群中安裝任何額外的軟件!
請(qǐng)注意 Service 定義在任何地方都沒有提到 Deployment。就像 Deployment 本身一樣,它根據(jù) Pod 和標(biāo)簽運(yùn)行,這使它非常強(qiáng)大!例如,Kubernetes 中良好的藍(lán)/綠或金絲雀部署可以通過讓兩個(gè) Deployment 對(duì)象在單個(gè) Service 選擇具有公共標(biāo)簽的 Pod 后運(yùn)行不同版本的應(yīng)用程序鏡像來實(shí)現(xiàn):
現(xiàn)在,最有趣的部分 - 你注意到 Kubernetes 服務(wù)與我們舊的基于 VM 的服務(wù)沒有什么區(qū)別了嗎?
Kubernetes 即服務(wù)
那么,Kubernetes 是不是就像 VM 一樣,但更簡單?嗯,是的,但也不是。因?yàn)樗摂M機(jī)存在本質(zhì)上的差別,套用Kelsey Hightower的話,我們應(yīng)該區(qū)分駕駛汽車的復(fù)雜性和修理汽車的復(fù)雜性。我們中的許多人會(huì)開車,但很少有人擅長修理發(fā)動(dòng)機(jī)。幸運(yùn)的是,有專門的商店!這同樣適用于 Kubernetes。
使用 EKS 或 GKE 等托管 Kubernetes 產(chǎn)品運(yùn)行服務(wù)確實(shí)很相似,但比使用 VM 簡單得多。但如果你必須維護(hù) Kubernetes 集群背后的實(shí)際服務(wù)器,那就完全不同了……,所以僅僅使用 Kubernetes 和維護(hù) Kubernetes 是兩碼事。
總結(jié)
為了改善在 VM 上運(yùn)行服務(wù)的體驗(yàn),容器改變了我們打包軟件的方式,大大降低了對(duì)服務(wù)器配置的要求,并啟用了替代方法來部署我們的工作負(fù)載。但就其本身而言,容器并沒有成為大規(guī)模運(yùn)行服務(wù)的解決方案。頂部仍然需要額外的編排層。
Kubernetes 作為容器原生編排系統(tǒng)之一,使用容器作為基本構(gòu)建塊重新創(chuàng)建了過去熟悉的架構(gòu)模式。Kubernetes 還通過提供用于擴(kuò)展、部署和服務(wù)發(fā)現(xiàn)的內(nèi)置方法來解決傳統(tǒng)方案的痛點(diǎn)。