為什麼Kubernetes這麼難? • Buttondown

banq發表於2022-01-27

Kubernetes 比我使用過的其他一些系統感覺更大、更可怕、更難處理。在我學習並使用它的過程中,我試圖理解為什麼它看起來像現在這樣,以及哪些設計決策和權衡導致它看起來像現在這樣。我並不聲稱擁有完整的答案,但這篇文章試圖將我所擁有的兩個具體想法或正規化提交給我,因為我試圖理解為什麼使用 Kubernetes 有時會讓人感覺如此毛骨悚然。

 

Kubernetes 是一個叢集作業系統

很容易將 Kubernetes 視為一個用於部署容器化應用程式的系統,或者一些類似的功能描述。雖然這可能是一個有用的觀點,但我認為將 Kubernetes 視為通用叢集作業系統核心會更有成效。

傳統作業系統的工作是使用單臺計算機及其所有附屬硬體,並公開程式可以用來訪問該硬體的介面。雖然具體細節各不相同,但總的來說,有一些以下目標:

  • 資源共享——我們希望使用一臺物理計算機並將其資源細分到多個程式中,以使它們在一定程度上相互隔離。
  • 可移植性——我們希望在一定程度上抽象出底層硬體的精確細節,這樣同一個程式就可以在不同的硬體上執行而無需修改,或者只需進行少量修改。
  • 通用性——當我們提出新型別的硬體,或者將新硬體插入我們的計算機時,我們希望能夠以增量的方式將它們融入我們的抽象和介面,理想情況下不(a)徹底改變任何介面或(b ) 破壞任何不使用該硬體的現有軟體。
  • 整體性——與一般性相關,我們希望作業系統調解對硬體的所有訪問:軟體完全繞過作業系統核心應該很少或不可能。軟體可以使用作業系統核心建立與硬體的直接連線,以便將來直接發生互動(例如,設定記憶體對映命令管道),但初始分配和配置仍然在作業系統的支援下。
  • 效能——與“直接編寫一個直接在硬體上執行並具有對硬體的獨佔直接訪問許可權的專用軟體”(ala unikernel)相比,我們希望為這種抽象支付可接受的小效能成本。在某些情況下,我們希望通過提供 I/O 排程程式或快取層等優化,在實踐中實現比此類系統更高的效能。

雖然 "易於程式設計 "通常是一個額外的目標,但在實踐中,它往往輸給了上述關注。作業系統核心通常是圍繞上述目標設計的,然後編寫使用者空間庫,將低階的、通用的、高效能的介面包裝成更容易使用的抽象概念。作業系統開發者往往更關心 "如何使nginx在我的作業系統上快速執行",而不是 "nginx移植到我的作業系統上能縮短多少行程式碼?"

我認為Kubernetes是在一個非常類似的設計空間中執行的;然而,它的目標不是抽象出一臺計算機,而是抽象出整個資料中心或雲,或其中的一大片。

我認為這種觀點有幫助的原因是,這個問題比 "使HTTP應用程式在容器中部署成為可能 "要難得多,也更普遍,而且它指出了Kubernetes如此靈活的具體原因。Kubernetes希望它足夠普遍和強大,能夠在任何型別的硬體(或虛擬機器例項)上部署任何型別的應用程式。

我認為,這個觀點所解釋的最大的設計選擇也許是Kubernetes的可插拔和可配置性。一般來說,不可能做出對所有人都適用的選擇,特別是如果你渴望在不付出奢侈的效能成本的情況下做到這一點。

特別是在現代雲環境中,應用程式的型別和部署的硬體型別有很大的不同,而且是非常快速的移動目標。因此,如果你想成為所有人的一切,你最終需要極大的可配置性,這最終創造了一個強大的系統,但它可能難以理解,或使 "簡單 "的任務變得複雜。

我感覺到,許多使用者認為Kubernetes本質上是(或者,也許,希望它是)"一個Heroku",即作為一個部署應用程式的平臺,抽象出大多數傳統的底層作業系統和分散式系統的細節。

我的論點是,Kubernetes認為自己解決的問題宣告更接近於 "CloudFormation"--在這個意義上,它希望足以定義你的整個基礎設施--除了它還試圖以一種在底層雲提供商或硬體上通用的方式做到這一點。

 

Kubernetes中的一切是一個控制迴圈

我們可以想象一個非常必要的 "叢集作業系統",就像上面所說的那樣,它暴露了 "分配5個CPU的計算量 "或 "建立一個新的虛擬網路 "這樣的基元,這些基元反過來又支援系統內部抽象中的配置變化或對EC2 API(或其他基礎雲提供商)的呼叫。

Kubernetes,作為一個核心的設計決定,並不像那樣工作。相反,Kubernetes的核心設計決定是,所有的配置都是宣告性的,所有的配置都是通過作為控制迴圈的 "操作者 "的方式實現。他們不斷地將期望的配置與現實的狀態進行比較,然後試圖採取行動,使現實與期望的狀態相一致。

這是一個非常慎重的設計選擇,也是一個有充分理由的選擇。一般來說,任何沒有被設計成控制迴圈的系統都將不可避免地偏離期望的配置,因此,在規模上,需要有人來編寫控制迴圈。通過內部化,Kubernetes希望能讓大多數核心控制環路只寫一次,而且是由領域專家來寫,從而使在其上構建可靠的系統變得更加容易。這也是一個系統的自然選擇,因為它的本質是分散式的,而且是為構建分散式系統而設計的。分散式系統的決定性性質是部分故障的可能性,這就要求超過一定規模的系統能夠自我修復,並在不考慮區域性故障的情況下收斂到正確狀態。

然而,這種設計選擇也帶來了大量的複雜性和混亂的機會。

挑兩個具體的來說。

  • 錯誤被延遲

在Kubernetes中建立一個物件(例如一個pod),一般來說,只是在配置儲存中建立一個物件,斷言該物件的預期存在。如果由於資源限制(叢集的容量),或者由於物件在某些方面內部不一致(你引用的容器映象不存在),實際上不可能滿足該請求,一般來說,你不會在建立時看到該錯誤。配置建立將通過,然後,當相關的操作者醒來並試圖實施改變時,才會產生一個錯誤。

這種間接性使得一切都更難除錯和推理,因為你不能用 "建立成功 "作為 "結果物件存在 "的一個好的速記方法。這也意味著與失敗有關的日誌資訊或除錯輸出不會出現在建立物件的程式中。一個寫得好的控制器會發出Kubernetes事件,解釋正在發生的事情,或以其他方式註釋有問題的物件;但對於一個測試較差的控制器或更罕見的失敗,你可能只是在控制器自己的日誌中得到日誌垃圾。有些變化可能涉及到多個控制器,獨立行動,甚至聯合行動,這使得追蹤哪段該死的程式碼真正失敗變得更加困難。

  • 運維者可能是錯誤的

宣告性的控制-迴圈模式提供了一個隱含的承諾,即你,使用者,不需要擔心如何從狀態A到狀態B;你只需要把狀態B寫進配置資料庫,然後等待。當它執行良好時,這實際上是一個巨大的簡化。

然而,有時不可能從狀態A到狀態B,即使狀態B本身就可以實現。也可能是可能的,但需要停機時間。也可能是可能的,但這是一個罕見的用例,所以控制器的作者忘記了實現它。對於Kubernetes中的核心內建基元,你有一個很好的保證,那就是它們經過了很好的測試和使用,希望能工作得很好。但是當你開始新增第三方資源,以管理TLS證書或雲負載均衡器或託管資料庫或外部DNS名稱(Kubernetes的設計傾向於將你推向這個方向,因為當它可以成為你整個堆疊的真理之源時,它更高興),你會偏離軌道,它變得更不清楚所有的路徑是如何經過測試的。而且,與之前關於延遲錯誤的觀點一致,故障模式是微妙的,而且發生在遠處;而且很難區分 "這個變化還沒有被接受 "和 "這個變化永遠不會被接受"。

 

相關文章