本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
為什麼 Kubernetes 不使用 libnetwork
Kubernetes 早在 1.0 版本釋出之前就有了非常基本的網路外掛形式——大約在 Docker 的 libnetwork 和容器網路模型 (CNM) 引入的同時。與 libnetwork 不同,Kubernetes 外掛系統仍然保留其“alpha”稱號。既然 Docker 的網路外掛支援已經發布並得到支援,我們自然會遇到一個明顯的問題:為什麼 Kubernetes 尚未採用它。畢竟,供應商幾乎肯定會為 Docker 編寫外掛——我們都使用相同的驅動程式會更好,對嗎?
在深入討論之前,重要的是要記住 Kubernetes 是一個支援多種容器執行時的系統,其中 Docker 只是其中之一。配置網路是每個執行時的一個方面,所以當人們問“Kubernetes 會支援 CNM 嗎?”時,他們真正的意思是“Kubernetes 會支援 Docker 執行時中的 CNM 驅動程式嗎?” 如果我們能在不同執行時之間實現通用的網路支援,那將是極好的,但這並不是一個明確的目標。
事實上,Kubernetes 尚未為 Docker 執行時採用 CNM/libnetwork。實際上,我們一直在研究 CoreOS 提出的替代方案容器網路介面 (CNI) 模型,該模型是 App Container (appc) 規範的一部分。為什麼?原因有很多,包括技術性和非技術性。
首先,Docker 網路驅動設計中的一些基本假設給我們帶來了問題。
Docker 有“本地”和“全域性”驅動的概念。本地驅動(如“bridge”)以機器為中心,不進行跨節點協調。全域性驅動(如“overlay”)依賴 libkv(一個鍵值儲存抽象)來協調跨機器。這個鍵值儲存是另一個外掛介面,級別很低(只有鍵和值,沒有語義含義)。要在 Kubernetes 叢集中執行像 Docker 的 overlay 驅動這樣的東西,我們需要叢集管理員執行一個完全不同的 consul、etcd 或 zookeeper 例項(參見 多主機網路),或者我們必須提供自己的由 Kubernetes 支援的 libkv 實現。
後者聽起來很有吸引力,我們也嘗試過實現它,但是 libkv 介面非常底層,而且其模式在 Docker 內部定義。我們必須要麼直接暴露底層的鍵值儲存,要麼提供鍵值語義(在我們的結構化 API 之上,而結構化 API 本身又是基於鍵值系統實現的)。由於效能、可伸縮性和安全原因,這兩種選擇都不是很吸引人。最終結果是,整個系統將變得更加複雜,而使用 Docker 網路的目的是簡化事情。
對於那些願意並能夠執行滿足 Docker 全域性驅動所需的基礎設施並自行配置 Docker 的使用者來說,Docker 網路應該“開箱即用”。Kubernetes 不會阻礙這種設定,無論專案走向何方,這種選項都應該可用。然而,對於預設安裝而言,實際結論是這對使用者來說是不必要的負擔,因此我們無法使用 Docker 的全域性驅動(包括“overlay”),這大大降低了使用 Docker 外掛的價值。
Docker 的網路模型做了很多不適用於 Kubernetes 的假設。在 Docker 1.8 和 1.9 版本中,它包含了一個有根本性缺陷的“發現”實現,導致容器中的 /etc/hosts
檔案損壞(docker #17190)——而且這不容易關閉。在 1.10 版本中,Docker 計劃捆綁一個新的 DNS 伺服器,目前尚不清楚是否可以將其關閉。容器級別的命名不是 Kubernetes 的正確抽象——我們已經有了自己的服務命名、發現和繫結概念,並且我們已經有了自己的 DNS 模式和伺服器(基於成熟的 SkyDNS)。捆綁的解決方案不足以滿足我們的需求,而且無法停用。
除了本地/全域性分離之外,Docker 還有程序內和程序外(“遠端”)外掛。我們研究了是否可以繞過 libnetwork(從而跳過上述問題)並直接驅動 Docker 遠端外掛。不幸的是,這意味著我們無法使用任何 Docker 程序內外掛,特別是“bridge”和“overlay”,這再次消除了 libnetwork 的大部分實用性。
另一方面,CNI 在哲學上與 Kubernetes 更為契合。它比 CNM 簡單得多,不需要守護程序,並且至少在理論上是跨平臺的(CoreOS 的 rkt 容器執行時支援它)。跨平臺意味著有機會實現跨執行時(例如 Docker、Rocket、Hyper)以相同方式工作的網路配置。它遵循了 UNIX 的“做好一件事”的哲學。
此外,封裝 CNI 外掛並生成更定製化的 CNI 外掛非常簡單——只需一個簡單的 shell 指令碼即可完成。CNM 在這方面要複雜得多。這使得 CNI 成為快速開發和迭代的誘人選擇。早期的原型已經證明,可以將 kubelet 中目前硬編碼的網路邏輯幾乎 100% 地提取到外掛中。
我們研究了為 Docker 編寫一個執行 CNI 驅動的“bridge”CNM 驅動。結果證明這非常複雜。首先,CNM 和 CNI 模型截然不同,所以沒有任何“方法”可以對齊。我們仍然面臨上述的全域性與本地以及鍵值問題。假設這個驅動程式宣告自己是本地的,我們必須從 Kubernetes 獲取邏輯網路資訊。
不幸的是,Docker 驅動程式很難對映到像 Kubernetes 這樣的其他控制平面。具體來說,驅動程式不知道容器要連線的網路名稱——只有一個 Docker 內部分配的 ID。這使得驅動程式很難映射回存在於其他系統中的任何網路概念。
網路供應商已向 Docker 開發人員提出了這個問題以及其他問題,但這些問題通常以“按預期工作”為由關閉(libnetwork #139、libnetwork #486、libnetwork #514、libnetwork #865、docker #18864),儘管它們使得非 Docker 第三方系統更難整合。在整個調查過程中,Docker 明確表示他們對偏離當前路線或下放控制權的想法不甚開放。這讓我們非常擔憂,因為 Kubernetes 補充了 Docker 並增加了許多功能,但它存在於 Docker 本身之外。
出於所有這些原因,我們選擇將 CNI 作為 Kubernetes 的外掛模型進行投入。這會帶來一些不幸的副作用。其中大部分相對較小(例如,docker inspect
將不會顯示 IP 地址),但有些則非常重要。特別是,由 docker run
啟動的容器可能無法與由 Kubernetes 啟動的容器通訊,並且網路整合商如果希望與 Kubernetes 完全整合,則必須提供 CNI 驅動程式。另一方面,Kubernetes 將變得更簡單、更靈活,並且早期引導(例如配置 Docker 使用我們的橋接)的許多醜陋之處都將消失。
在我們沿著這條道路前進時,我們一定會保持開放的心態,尋找更好的整合和簡化方式。如果您對我們如何做到這一點有任何想法,我們非常樂意傾聽——請在 slack 或我們的 網路 SIG 郵件列表上找到我們。