這兩個(gè)原因,使Kubernetes變得如此復(fù)雜
1、為什么Kubernetes這么難?
Anthropic在Kubernetes內(nèi)運(yùn)行我們的大部分系統(tǒng),因此我對(duì)該工具積累了更多的經(jīng)驗(yàn),對(duì)其也更加熟悉。雖然在它真的很棒,但我也確實(shí)經(jīng)歷了(大家會(huì)普遍經(jīng)歷的)其復(fù)雜性和調(diào)試的超高難度。
雖然在學(xué)習(xí)新系統(tǒng)時(shí),這些感覺(jué)相當(dāng)普遍,但Kubernetes確實(shí)比我使用過(guò)的其他一些系統(tǒng)感覺(jué)更大、更可怕、更棘手。在學(xué)習(xí)并使用它的過(guò)程中,我試圖理解為什么它看起來(lái)是這樣的,以及哪些設(shè)計(jì)決策和權(quán)衡導(dǎo)致它成為現(xiàn)在這樣。這篇文章嘗試寫出兩種特定的想法,并會(huì)解釋為什么與Kubernetes一起工作有時(shí)會(huì)感到棘手。
2、Kubernetes是一個(gè)集群操作系統(tǒng)
大家很容易將Kubernetes視為部署容器化應(yīng)用程序或一些類似功能描述的系統(tǒng)。雖然這可能是一個(gè)有用的視角,但我認(rèn)為將Kubernetes視為通用集群操作系統(tǒng)內(nèi)核會(huì)更合理。這兩者之間有何區(qū)別?
傳統(tǒng)操作系統(tǒng)的工作是使用一臺(tái)計(jì)算機(jī)及其所有附屬硬件,并公開(kāi)程序可用于訪問(wèn)該硬件的接口。雖然確切的細(xì)節(jié)各不相同,但通常這個(gè)界面有以下幾個(gè)目標(biāo):
1)資源共享——我們希望將一臺(tái)物理計(jì)算機(jī)的資源細(xì)分到多個(gè)程序中,以便在某種程度上相互隔離。
2)可移植性——我們希望在某種程度上抽象底層硬件的精確細(xì)節(jié),以便同一程序可以在不同的硬件上運(yùn)行,而無(wú)需修改或僅進(jìn)行小幅修改。
3)通用性——當(dāng)我們想出新型硬件或?qū)⑿掠布迦胗?jì)算機(jī)時(shí),我們希望能夠以增量的方式將這些硬件放入我們的抽象和接口中,建議不要大幅更改任何接口或破壞任何不使用該硬件的現(xiàn)有軟件。
4)整體性——與通用性相關(guān),我們希望操作系統(tǒng)調(diào)解對(duì)硬件的所有訪問(wèn):軟件幾乎不可能完全繞過(guò)操作系統(tǒng)內(nèi)核。軟件可以使用操作系統(tǒng)內(nèi)核設(shè)置與硬件的直接連接,以便未來(lái)的交互直接發(fā)生(例如設(shè)置內(nèi)存映射的命令管道),但初始分配和配置仍在操作系統(tǒng)的保護(hù)下。
5)性能——與“直接編寫一個(gè)專用軟件,它直接運(yùn)行在硬件上,并且對(duì)硬件有獨(dú)占的直接訪問(wèn)權(quán)”相比,我們希望為擁有這種抽象支付可接受的小性能成本。在某些情況下,我們希望通過(guò)提供I/O調(diào)度器或緩存層等優(yōu)化,在實(shí)踐中實(shí)現(xiàn)比此類系統(tǒng)更高的性能。
雖然“編程的便捷性”通常是一個(gè)額外的目標(biāo),但在實(shí)踐中,它往往因?yàn)樯鲜鰮?dān)憂而被忽視。操作系統(tǒng)內(nèi)核通常圍繞上述目標(biāo)進(jìn)行設(shè)計(jì),然后編寫用戶空間庫(kù),將低級(jí)、通用、高性能的接口封裝到更易于使用的抽象中。操作系統(tǒng)開(kāi)發(fā)人員更關(guān)心“在我的操作系統(tǒng)上運(yùn)行nginx有多快”,而不是“nginx端口到我的操作系統(tǒng)的代碼能短多少行?”
我認(rèn)為Kubernetes在一個(gè)非常相似的設(shè)計(jì)空間中運(yùn)行;然而,它的目標(biāo)不是抽象單個(gè)計(jì)算機(jī),而是抽象整個(gè)數(shù)據(jù)中心或云,或其中的很大一部分。
這種觀點(diǎn)有幫助的原因是,這個(gè)問(wèn)題比“使在容器中部署HTTP應(yīng)用程序成為可能”更困難,也更普遍,它指出了Kubernetes如此靈活的具體原因。Kubernetes渴望足夠通用和強(qiáng)大,可以在任何類型的硬件(或虛擬機(jī)實(shí)例)上部署任何類型的應(yīng)用程序,而無(wú)需“繞過(guò)”或“跳開(kāi)”Kubernetes接口。
我不會(huì)試圖在這里就它是否實(shí)現(xiàn)了這個(gè)目標(biāo)(或者,它在實(shí)踐中是否實(shí)現(xiàn)了這個(gè)目標(biāo))發(fā)表意見(jiàn);只需將它視為一個(gè)要解決的問(wèn)題,就能理解所遇到的許多設(shè)計(jì)決策,這樣的視角是可行的。
從這個(gè)角度來(lái)看,Kubernetes的可插拔性和可配置性可能是比較大的設(shè)計(jì)選擇。一般來(lái)說(shuō),不可能做出對(duì)所有人都適用的選擇,特別是你希望在沒(méi)有高昂的性能成本的情況下做出選擇。特別是在現(xiàn)代云環(huán)境中,部署的應(yīng)用程序類型和硬件類型差異很大,是變化速度非??斓哪繕?biāo)。因此,想成為所有人的萬(wàn)能工具,你最終需要高度可配置性,這最終會(huì)創(chuàng)建一個(gè)強(qiáng)大的系統(tǒng),但這個(gè)系統(tǒng)可能很難理解,甚至使“簡(jiǎn)單”任務(wù)變得復(fù)雜。
當(dāng)然,還有另一個(gè)視角:
許多用戶認(rèn)為Kubernetes本質(zhì)上是“Heroku”,也就是說(shuō),Kubernetes本質(zhì)上是一個(gè)部署應(yīng)用程序的平臺(tái),抽象了大多數(shù)傳統(tǒng)的底層操作系統(tǒng)和分布式系統(tǒng)的細(xì)節(jié)。
Kubernetes認(rèn)為自己解決了更接近“CloudFormation”的問(wèn)題——在某種意義上,它希望足以定義您的整個(gè)基礎(chǔ)設(shè)施—它也試圖以比底層云提供商或硬件通用的方式做到這一點(diǎn)。
3、Kubernetes中的所有內(nèi)容都是一個(gè)控制循環(huán)
大家可以想象一個(gè)十分必要的“集群操作系統(tǒng)”,上文中,它暴露了“分5個(gè)CPU的計(jì)算能力”或“創(chuàng)建新的虛擬網(wǎng)絡(luò)”等原語(yǔ),這些原語(yǔ)反過(guò)來(lái)又支持系統(tǒng)內(nèi)部抽象中的配置更改或?qū)C2 API(或其他底層云提供商)的調(diào)用。
Kubernetes作為核心設(shè)計(jì)決策,并不是這樣工作的。相反,Kubernetes做出了核心設(shè)計(jì)決策,即所有配置都是聲明性的,所有配置都是通過(guò)作為控制回路的“操作員”實(shí)現(xiàn)的:他們不斷將所需的配置與現(xiàn)實(shí)狀態(tài)進(jìn)行比較,然后試圖采取行動(dòng)使現(xiàn)實(shí)與所需的狀態(tài)保持一致。
這是一個(gè)非常深思熟慮的設(shè)計(jì)選擇,而且是有充分理由的。一般來(lái)說(shuō),任何不是設(shè)計(jì)為控制回路的系統(tǒng)都會(huì)不可避免地偏離預(yù)期的配置,因此,在規(guī)模上,需要有人編寫控制回路。通過(guò)內(nèi)部化它們,Kubernetes希望允許大多數(shù)核心控制循環(huán)只編寫一次,并由領(lǐng)域?qū)<揖帉懀瑥亩菀自谒鼈冎蠘?gòu)建可靠的系統(tǒng)。
對(duì)于一個(gè)本質(zhì)上是分布式、為構(gòu)建分布式系統(tǒng)而設(shè)計(jì)的系統(tǒng)而言,這也是一個(gè)自然的選擇。分布式系統(tǒng)的定義本質(zhì)是局部故障的可能性,這要求超過(guò)一定規(guī)模的系統(tǒng)能夠自我修復(fù),并在不考慮局部故障的情況下收斂到正確的狀態(tài)。
然而,這種設(shè)計(jì)選擇也帶來(lái)了產(chǎn)生巨大的復(fù)雜性和混亂的可能性。以下為兩個(gè)具體的例子:
1)錯(cuò)誤被延遲。在Kubernetes中創(chuàng)建對(duì)象(例如pod),通常只需在配置存儲(chǔ)中創(chuàng)建一個(gè)對(duì)象,斷言該對(duì)象的預(yù)期存在。如果事實(shí)證明無(wú)法實(shí)際滿足該請(qǐng)求,要么是因?yàn)橘Y源限制,要么是因?yàn)閷?duì)象在某些方面內(nèi)部不一致(引用的容器映像不存在),通常不會(huì)在創(chuàng)建時(shí)發(fā)現(xiàn)該錯(cuò)誤。配置創(chuàng)建將會(huì)進(jìn)行,然后,當(dāng)相關(guān)操作符喚醒并嘗試實(shí)現(xiàn)更改時(shí),才會(huì)創(chuàng)建錯(cuò)誤。
這種間接性使調(diào)試和推理變得更加困難,因?yàn)槟悴荒苡谩皠?chuàng)建成功”作為“生成的對(duì)象存在”的良好標(biāo)記。這也意味著與失敗相關(guān)的日志消息或調(diào)試輸出不會(huì)出現(xiàn)在創(chuàng)建對(duì)象的流程的上下文中。
編寫良好的控制器會(huì)發(fā)出Kubernetes事件來(lái)解釋正在發(fā)生的事情,或以其他方式注釋有問(wèn)題的對(duì)象;但對(duì)于測(cè)試較差的控制器或更罕見(jiàn)的故障,您可能只會(huì)在控制器自己的日志中獲取日志垃圾郵件。一些更改可能涉及多個(gè)控制器,會(huì)獨(dú)立或聯(lián)合行動(dòng),這使得跟蹤某一段代碼變得更加困難。
2)運(yùn)算符可能有漏洞。聲明性控制環(huán)模式提供了隱含的承諾,即您作為用戶無(wú)需擔(dān)心如何從狀態(tài)A到狀態(tài)B;您只需將狀態(tài)B寫入配置數(shù)據(jù)庫(kù),然后等待。當(dāng)它運(yùn)行良好時(shí),這實(shí)際上是一個(gè)巨大的簡(jiǎn)化。
然而,有時(shí)候從狀態(tài)A到狀態(tài)B是不可能的,即使?fàn)顟B(tài)B可以自己實(shí)現(xiàn)?;蛟S這是可能的,但需要停機(jī)時(shí)間。雖然這是可能的,但這是一個(gè)罕見(jiàn)的用例,所以控制器的作者忘了實(shí)現(xiàn)它。
對(duì)于Kubernetes中的核心內(nèi)置原語(yǔ),您可以保證它們經(jīng)過(guò)良好的測(cè)試和使用,并希望它們能很好地工作。但當(dāng)您開(kāi)始添加第三方資源、管理TLS證書或云負(fù)載平衡器或托管數(shù)據(jù)庫(kù)或外部DNS名稱時(shí),您會(huì)偏離常規(guī),所有路徑的測(cè)試效果會(huì)變得不那么清楚。
而且,與之前關(guān)于延遲錯(cuò)誤的要點(diǎn)一致,故障模式很微妙,而且發(fā)生在較遠(yuǎn)的位置;很難區(qū)分“更改尚未被接收”和“更改永遠(yuǎn)不會(huì)被接收”之間的區(qū)別。
4、結(jié)論
本文一直試圖避免就這些設(shè)計(jì)決策的好壞做出價(jià)值判斷。因?yàn)殛P(guān)于Kubernetes何時(shí)成為什么樣的有價(jià)值的系統(tǒng)才是有意義的。
我發(fā)現(xiàn)以自己的方式對(duì)Kubernetes有很好的理解,并更好地理解其復(fù)雜性來(lái)自哪里,以及它正在服務(wù)的目標(biāo),這是非常有價(jià)值的。
這種分析可以應(yīng)用于現(xiàn)在使用的任何系統(tǒng)。即使一個(gè)系統(tǒng)的設(shè)計(jì)方式在當(dāng)前的環(huán)境中并不理想,但出于某種原因,它總是以這種方式出現(xiàn)。只要這是一個(gè)你必須與之互動(dòng)、推理和決策的系統(tǒng),如果你能理解這些原因、動(dòng)機(jī)和將系統(tǒng)推向這一點(diǎn)的內(nèi)部邏輯,而不是立即將其忽視,則會(huì)有更好的使用體驗(yàn)。
希望這篇文章能幫助其他對(duì)在生產(chǎn)中使用Kubernetes不熟悉、或正在考慮采用Kubernetes的人,幫助大家提供一些有用的框架來(lái)解釋為什么(我相信)它看起來(lái)的樣子,以及對(duì)它有什么合理的期望。
如果我們想更細(xì)致入微,我們可以認(rèn)為它預(yù)先加載復(fù)雜性,而不是添加復(fù)雜性。這種設(shè)計(jì)讓你預(yù)先處理可能長(zhǎng)期忽視的實(shí)際問(wèn)題。這是否是一個(gè)理想的選擇取決于您的目標(biāo)、規(guī)模、時(shí)間范圍和相關(guān)因素。