作者:admin 日期:2023-09-06 瀏覽: 次
這兩個設計決策,讓 Kubernetes 變得可怕
1前言
大家好。最近我的團隊發表了我們的第一篇論文(https://transformer-circuits.pub/2021/framework/index.html),這讓我感到非常興奮。這篇文章的內容很零碎,所以我沒指望 ML 領域以外有多少人會看,但我確實認為它在理解 GPT-3 之類的模型內部到底發生了什么事情這個主題上取得了一些非常好的(當然也是比較初步的!)進展。除此之外,我還發表了一篇關于 Gar?on 的文章(https://transformer-circuits.pub/2021/garcon/index.html),這是我在 Anthropic 的第一批項目之一,它是為我們大部分可解釋性工作提供動力的基礎設施工具。
說完這些,這篇文章里我想與大家分享一些閑暇時的想法。2為什么 Kubernetes 這么難?
Anthropic 在 Kubernetes 中運行我們的大部分系統,因此我對這個工具有了更多經驗,相當熟悉它。雖然它真的很棒,但我當然也經歷過(我認為誰都有這種經歷)“天吶為什么這件事如此復雜”的感受,以及“為什么調試任何東西都這么難”的情況
雖然其中一些感受在學習任何新系統時都是很常見的,但 Kubernetes 確實比我使用過的其他一些系統感覺更大、更可怕、更難處理。當我學習并使用它時,我試著去理解為什么它長成這么一個樣子,以及哪些設計決策和權衡導致它長成了這個樣子。我并不是說自己已經得到了完整的答案,但這篇文章會試著把我總結出來的兩個想法或范式落到紙面上,這是我在試圖理解為什么使用 Kubernetes 有時會讓人感到如此毛骨悚然時總結出來的經驗。3Kubernetes 是一個集群操作系統
人們很容易將 Kubernetes 視為一個用于部署容器化應用程序的系統,或者一些類似的功能描述。雖然這可能是一個有意義的觀點,但我認為將 Kubernetes 視為通用集群操作系統內核會更合理一些。那么我到底是什么意思呢?這兩種定義有什么區別?
傳統操作系統的工作是使用單臺計算機及其所有附屬硬件,并公開一個程序可以用來訪問該硬件的接口。雖然具體細節各不相同,但總的來說,這個界面有一些以下目標:
資源共享——我們希望使用一臺物理計算機并將其資源細分到多個程序中,以使它們在一定程度上相互隔離。
可移植性——我們希望在一定程度上抽象出底層硬件的精確細節,這樣同一個程序就可以無需修改,或只需少量修改就運行在不同的硬件上。
通用性——當我們得到新類型的硬件,或者將新硬件插入我們的計算機時,我們希望能夠以增量的方式將它們融入我們的抽象和接口中,理想情況下不會(a)徹底改變任何接口或(b)破壞任何不使用該硬件的現有軟件。
整體性——與通用性相關,我們希望操作系統作為中介來管理對硬件的所有訪問:軟件完全繞過操作系統內核的情況應該很少或不可能出現。軟件可以使用操作系統內核建立與硬件的直接連接,以便未來的交互可以直接發生(例如,設置一個內存映射命令管道),但初始分配和配置仍然在操作系統的管理之下。
性能——與“直接編寫一個直接運行在硬件上,并具有對硬件的獨占直接訪問權限的專用軟件”(也就是 unikernel)相比,我們希望為這種抽象支付可接受的最小性能成本。在某些情況下,我們希望通過提供 I/O 調度程序或緩存層等優化,在實踐中實現比此類系統更高的性能。
雖然“易于編程”通常是一個額外的目標,但在實踐中它的優先級往往會輸給上述目標。操作系統內核通常圍繞上述目標設計,然后編寫一些用戶空間庫以將底層、通用、高性能的各種接口包裝成更易于使用的抽象。操作系統開發人員往往更關心“讓 nginx 在我的操作系統上運行有多快”,而不是“我的操作系統的 nginx 端口縮短了多少行代碼?”
我開始認為 Kubernetes 是位于一個非常相似的設計空間中的。然而,它不是在一臺計算機上進行抽象,而是旨在抽象整個數據中心或云,或者其中的一大部分。
我覺得這個觀點很有意義的原因在于,這個問題比“使人們可以在容器中部署 HTTP 應用程序”更難也更通用,它指出了 Kubernetes 如此靈活的具體原因。Kubernetes 渴望變得足夠通用和強大,可以在任何類型的硬件(或 VM 實例)上部署任何類型的應用程序,而無需你“繞過”或“走出”Kubernetes 界面。我不會在這里嘗試就它是否實現了該目標(或者它在實踐中何時實現或沒有實現該目標)發表意見;只需將它視為一個要解決的問題,我就能理解所遇到的許多設計決策,這樣的視角應該是可行的。
我認為這個觀點所解釋的最大設計選項可能就是 Kubernetes 的可插拔性和可配置性。一般來說,我們不可能做出對每個人都適用的選項,特別是你還希望不要付出奢侈的性能成本。在現代云環境中尤其如此,因為其中部署的應用程序類型和硬件類型差異很大,并且都是變化非常快的目標。因此,如果你想解決所有人的所有煩惱,你最終需要具有極大的可配置性,這最終會創建一個強大的系統,但它可能就會難以理解,或者甚至會讓“簡單”的任務變得非常復雜。
另一種觀點:在與我的搭檔 Kate 討論這篇文章時,我想出了另一個關于這個主題的視角:
我的感覺是,許多用戶認為 Kubernetes 本質上是(或者可能希望它是)“一個 Heroku”,即作為部署應用程序的平臺,抽象了大多數傳統的底層操作系統和分布式系統細節。
我的論點是 Kubernetes 認為自己解決了一個更接近于“CloudFormation”的問題——從某種意義上說,它希望自己足以定義你的整個基礎設施——它還試圖以一種對底層云供應商或硬件通用的方式來做到這一點。4Kubernetes 中的一切都是一個控制回路
你可以想象一個非常必要的“集群操作系統”,就像前文所述那樣,它暴露了諸如“分配 5 個 CPU 的算力”或“創建一個新的虛擬網絡”之類的原語,這反過來又能支持系統內部抽象中的配置更改或調用 EC2 API(或其他底層云提供商)。
Kubernetes 作為一個核心設計決策并不是這樣工作的。相反,Kubernetes 做出了的核心設計決策是所有配置都是聲明性的,并且所有配置都通過充當控制回路 的“Operator”來實現:它們不斷對比所需配置與現實狀態,然后嘗試采取行動來讓現實符合理想狀態。
這是一個非常深思熟慮的設計選擇,并且有著充足的理由。一般來說,任何 不 設計為控制回路的系統都將不可避免地偏離所需的配置,因此,在更大規模層面,需要 有人 編寫控制回路。將它們內部化后,Kubernetes 希望大多數核心控制回路只需要領域專家編寫一次即可,這樣就更容易在它們之上構建可靠的系統。本質而言,Kubernetes 是分布式的,并且是為構建分布式系統而設計的,所以這也是這樣的一個系統的自然選擇。分布式系統的定義天然允許出現 部分故障,這需要超過一定規模的系統能夠自我修復并收斂到正確的狀態,而不管本地故障究竟是什么情況。
然而,這種設計選擇也帶來了大量的復雜性和讓人陷入混淆的可能(注)。舉兩個具體的例子:錯誤被延遲
在 Kubernetes 中創建對象(例如 pod)時,通常只是在配置存儲中創建一個對象,斷言該對象的期望存在。如果由于資源限制(集群已滿負荷)或由于對象在某些方面內部不一致(你引用的容器映像不存在)而無法真正滿足該請求,那么一般來說你在創建時不會看到該錯誤。配置創建過程會正常完成,然后當相關 Operator 醒來并嘗試實施更改時才會創建錯誤。
這種間接性讓一切事物都更難調試和推理,因為你不能使用“創建成功”作為“結果對象存在”的良好標志。這也意味著與失敗相關的日志消息或調試輸出不會出現在創建對象的進程的上下文中。一個編寫良好的控制器將發出一些 Kubernetes 事件來解釋正在發生的事情,或者以其他方式注釋有問題的對象;但是對于測試不太完善的控制器或很少發生的故障,你可能只會在控制器自己的日志中獲得 logspam。并且某些更改可能涉及多個控制器,它們會獨立甚至聯合執行,這使得我們更難追蹤到底是哪一段該死的代碼失敗了。Operator 可能有問題
聲明式控制回路模式提供了隱含的承諾,即你(用戶)不需要擔心系統 如何 從狀態 A 到狀態 B;你只需將狀態 B 寫入配置數據庫,然后等待即可。當它運行良好時,這實際上大大簡化了工作。
然而,有時系統不可能從狀態 A 到達狀態 B,即使狀態 B 可以自行實現。或者也許這是可能的,但需要停機時間才行。或者它雖然是可能的,但卻是一個罕見的用例,因此控制器的作者忘了實現它。對于 Kubernetes 中的核心內置原語,你可以很好地保證它們經過良好的測試和實踐檢驗,并且應該能夠很好地工作。但是當你開始添加第三方資源、管理 TLS 證書或云負載均衡器或托管數據庫或外部 DNS 名稱時(Kubernetes 的設計傾向于將你推向這個方向,因為它更希望成為你整個堆棧的真相來源),你會在人跡罕至的道路上徘徊不前,并且很難搞清楚所有路徑是不是都經過了良好的測試。并且,與前面關于延遲錯誤的觀點一樣的是,故障模式都是很微妙的,并且出現在很遠的位置;并且很難區分“尚未收到更改”和“永遠不會收到更改”之間的區別。5結論
在這篇文章中,我試圖避免作出價值判斷,評價這些設計決策到底是好是壞。我認為關于 Kubernetes 應該何時成為什么樣的系統才是有意義、有價值的,以及哪些情況下更簡單的系統可能就足夠了之類的話題已經爭論夠多了。然而,知道它做出了這些決策后,我發現我對 Kubernetes 本身有了很好的理解,并且能更好地理解它的復雜性來自哪里,以及它服務的目標是什么,這是非常有價值的。
我嘗試將這種分析方法應用于我使用的所有系統上。即使一個系統的設計方式在當前環境下看起來——甚至可能就是——次優的,但它之所以會設計成現在這個樣子 總會是有一些原因的。對于一個你必須與之交互、推理和做出決定的系統而言,如果你能理解這些原因、動機和將系統帶到今天這一步的內部邏輯,你會有更好的體驗,而不是輕易放棄它。我希望這篇文章能對其他在生產中使用 Kubernetes 的新手或正在考慮采用它的人們有所幫助,希望本文提供了一些有用的框架來幫大家推理為什么(我認為)它看起來是這個樣子,以及對它有哪些期望是合理的。
注:如果我們想要更細化一些,我們可能會說它 預加載 了復雜性,而非添加了復雜性(或者說在添加的同時也是預加載了復雜性)。這種設計使你可以提前處理一些你可能會長期忽略的實用特性。這是否是一個理想的選擇取決于你的目標、規模、時間范圍和其他相關因素。
- END -
原文:https://tinyurl.com/ycknustc