本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
Kubernetes-in-Kubernetes 和 WEDOS PXE 可引導伺服器農場
當您擁有兩個資料中心、數千臺物理伺服器、虛擬機器以及託管數十萬個網站時,Kubernetes 實際上可以簡化所有這些事情的管理。實踐證明,透過使用 Kubernetes,您不僅可以宣告式地描述和管理應用程式,還可以管理基礎設施本身。我為捷克最大的託管服務提供商 WEDOS Internet a.s 工作,今天我將向您展示我的兩個專案——Kubernetes-in-Kubernetes 和 Kubefarm。
藉助它們,您只需幾條命令即可使用 Helm 在另一個 Kubernetes 叢集中部署一個完全正常執行的 Kubernetes 叢集。如何做到以及為什麼?
讓我向您介紹我們的基礎設施是如何工作的。我們所有的物理伺服器可以分為兩組:控制平面和計算節點。控制平面節點通常是手動設定的,安裝了穩定的作業系統,旨在執行包括 Kubernetes 控制平面在內的所有叢集服務。這些節點的主要任務是確保叢集本身的平穩執行。計算節點預設沒有安裝任何作業系統,而是透過網路直接從控制平面節點啟動作業系統映象。它們的工作是承擔工作負載。
一旦節點下載了映象,它們就可以繼續工作,而無需保持與 PXE 伺服器的連線。也就是說,PXE 伺服器只是儲存 rootfs 映象,不包含任何其他複雜的邏輯。我們的節點啟動後,我們可以安全地重啟 PXE 伺服器,它們不會發生任何關鍵問題。
啟動後,我們的節點做的第一件事是加入現有的 Kubernetes 叢集,即執行 kubeadm join 命令,以便 kube-scheduler 可以在它們上排程一些 Pod,然後啟動各種工作負載。從一開始,我們使用的方案是節點加入用於控制平面節點的同一個叢集。
這個方案穩定運行了兩年多。然而,後來我們決定加入容器化的 Kubernetes。現在,我們可以非常輕鬆地直接在我們的控制平面節點上生成新的 Kubernetes 叢集,這些節點現在是特殊管理叢集的成員。現在,計算節點可以直接加入它們自己的叢集——取決於配置。
Kubefarm
該專案的目標是讓任何人只需幾條命令,使用 Helm 就能部署這樣的基礎設施,並最終獲得相同的結果。
此時,我們放棄了單叢集的想法。因為事實證明,在同一個叢集中管理多個開發團隊的工作不是很方便。事實上,Kubernetes 從來沒有被設計成一個多租戶解決方案,目前它沒有提供專案之間足夠的隔離手段。因此,為每個團隊執行獨立的叢集是一個好主意。然而,叢集不應該太多,以便於管理。也不應該太少,以便開發團隊之間有足夠的獨立性。
在那次改變之後,我們叢集的可擴充套件性明顯改善。每節點擁有的叢集數量越多,故障域越小,它們的工作就越穩定。作為一個額外的好處,我們獲得了完全宣告式描述的基礎設施。因此,現在您可以像在 Kubernetes 中部署任何其他應用程式一樣部署一個新的 Kubernetes 叢集。
它以 Kubernetes-in-Kubernetes 為基礎,以 LTSP 作為節點啟動的 PXE 伺服器,並使用 dnsmasq-controller 自動化 DHCP 伺服器配置。

工作原理
現在讓我們看看它是如何工作的。總的來說,如果你從應用程式的角度看 Kubernetes,你會發現它遵循了 Twelve-Factor App 的所有原則,而且實際上寫得非常好。因此,這意味著在另一個 Kubernetes 中將 Kubernetes 作為應用程式執行應該不是什麼大問題。
在 Kubernetes 中執行 Kubernetes
現在讓我們來看看 Kubernetes-in-Kubernetes 專案,它提供了一個現成的 Helm Chart,用於在 Kubernetes 中執行 Kubernetes。
這是您可以在 values 檔案中傳遞給 Helm 的引數

除了 persistence(叢集的儲存引數),這裡描述了 Kubernetes 控制平面元件:即 etcd cluster、apiserver、controller-manager 和 scheduler。這些都是相當標準的 Kubernetes 元件。有一個輕鬆的說法是“Kubernetes 只是五個二進位制檔案”。所以這裡就是這些二進位制檔案的配置所在。
如果您曾經嘗試使用 kubeadm 引導叢集,那麼這個配置會提醒您它的配置。但除了 Kubernetes 實體之外,您還有一個 admin 容器。事實上,它是一個內部包含兩個二進位制檔案的容器:kubectl 和 kubeadm。它們用於為上述元件生成 kubeconfig,並執行叢集的初始配置。此外,在緊急情況下,您始終可以執行進入它來檢查和管理您的叢集。
釋出 部署 後,您可以看到 Pod 列表:admin-container、兩個副本的 apiserver、controller-manager、etcd-cluster、scheduler 和初始化叢集的初始作業。最後,您會有一個命令,允許您進入 admin 容器的 shell,您可以使用它來檢視內部發生的情況。
此外,讓我們看看證書。如果您曾經安裝過 Kubernetes,那麼您就知道它有一個“可怕”的目錄 /etc/kubernetes/pki
,裡面有一堆證書。在 Kubernetes-in-Kubernetes 的情況下,您可以使用 cert-manager 完全自動化地管理它們。因此,只需在安裝時將所有證書引數傳遞給 Helm,所有證書就會自動為您的叢集生成。
檢視其中一個證書,例如 apiserver,您可以看到它有一個 DNS 名稱和 IP 地址列表。如果您希望此叢集可從外部訪問,只需在 values 檔案中描述額外的 DNS 名稱並更新發布即可。這將更新證書資源,cert-manager 將重新生成證書。您將不再需要考慮這個問題。如果 kubeadm 證書需要至少每年續訂一次,在這裡 cert-manager 將負責並自動續訂它們。
現在讓我們登入到管理員容器並檢視叢集和節點。當然,還沒有節點,因為目前您只部署了 Kubernetes 的空白控制平面。但在 kube-system 名稱空間中,您可以看到一些 coredns Pod 正在等待排程,並且 configmap 已經出現。也就是說,您可以得出結論,叢集正在執行。
這是已部署叢集的圖表。您可以看到所有 Kubernetes 元件的服務:apiserver、controller-manager、etcd-cluster 和 scheduler。右側是它們轉發流量的 Pod。
順便說一下,這個圖表是用 ArgoCD 繪製的,我們用它來管理叢集的 GitOps 工具,酷炫的圖表是它的特色之一。
編排物理伺服器
好的,現在您可以看到我們的 Kubernetes 控制平面是如何部署的,但是工作節點呢,我們是如何新增它們的呢?正如我之前所說,我們所有的伺服器都是裸機。我們不使用虛擬化來執行 Kubernetes,而是自己編排所有物理伺服器。
此外,我們還積極使用 Linux 網路啟動功能。而且,這正是啟動,而不是某種安裝自動化。當節點啟動時,它們只是執行為其準備好的系統映象。也就是說,要更新任何節點,我們只需重新啟動它——它就會下載一個新的映象。這非常容易、簡單和方便。
為此,建立了 Kubefarm 專案,它允許您實現自動化。最常用的示例可以在 examples 目錄中找到。其中最標準的一個名為 generic。讓我們看看 values.yaml
在這裡,您可以指定傳遞到上游 Kubernetes-in-Kubernetes chart 的引數。為了讓您的控制平面可以從外部訪問,您只需在這裡指定 IP 地址,但如果您願意,也可以在這裡指定一些 DNS 名稱。
在 PXE 伺服器配置中,您可以指定時區。您還可以新增 SSH 金鑰以實現無密碼登入(但也可以指定密碼),以及應在系統啟動期間應用的核心模組和引數。
接下來是 nodePools 配置,即節點本身。如果您曾經使用過 GKE 的 terraform 模組,那麼這個邏輯會提醒您它。在這裡,您靜態地描述了所有帶有引數集的節點
名稱(主機名);
MAC 地址——我們有帶有兩塊網絡卡的節點,每塊網絡卡都可以從這裡指定的任何 MAC 地址啟動。
IP 地址,DHCP 伺服器應該分配給此節點的 IP 地址。
在這個例子中,你有兩個池:第一個有五個節點,第二個只有一個,第二個池還分配了兩個標籤。標籤是描述特定節點配置的方式。例如,你可以為某些池新增特定的 DHCP 選項,為 PXE 伺服器新增啟動選項(例如,這裡啟用了除錯選項),以及一組 kubernetesLabels 和 kubernetesTaints 選項。這意味著什麼?
例如,在此配置中,您有第二個節點池,其中包含一個節點。該池分配了 debug 和 foo 標籤。現在檢視 kubernetesLabels 中 foo 標籤的選項。這意味著 m1c43 節點將使用這些兩個標籤和汙點啟動。一切看起來都很簡單。現在讓我們在實踐中嘗試一下。
演示
轉到 examples 並將先前部署的 chart 更新到 Kubefarm。只需使用 generic 引數並檢視 Pod。您可以看到添加了一個 PXE 伺服器和一個額外的作業。此作業本質上是進入已部署的 Kubernetes 叢集並建立一個新令牌。現在它將每 12 小時重複執行以生成一個新令牌,以便節點可以連線到您的叢集。
在 圖形表示 中,它看起來大致相同,但現在 apiserver 開始對外暴露。
在圖中,IP 以綠色突出顯示,PXE 伺服器可以透過它訪問。目前,Kubernetes 預設不允許為 TCP 和 UDP 協議建立單個 LoadBalancer 服務,因此您必須建立兩個具有相同 IP 地址的不同服務。一個用於 TFTP,第二個用於 HTTP,透過它下載系統映象。
但這個簡單的例子並不總是足夠的,有時您可能需要修改啟動邏輯。例如,這裡有一個目錄 advanced_network,其中包含一個帶有一個簡單 shell 指令碼的 值檔案。我們稱之為 network.sh
這個指令碼所做的只是在啟動時獲取環境變數,並根據它們生成網路配置。它會建立一個目錄並將 netplan 配置放在裡面。例如,這裡建立了一個繫結介面。基本上,這個指令碼可以包含您需要的一切。它可以包含網路配置或生成系統服務,新增一些鉤子或描述任何其他邏輯。任何可以用 bash 或 shell 語言描述的東西都可以在這裡工作,並且會在啟動時執行。
讓我們看看它是如何部署的。我們將通用值檔案作為第一個引數,並將一個額外的值檔案作為第二個引數傳遞。這是一個標準的 Helm 功能。透過這種方式,您也可以傳遞秘密,但在本例中,配置只是由第二個檔案擴充套件。
讓我們看看 netboot 伺服器的 ConfigMap foo-kubernetes-ltsp,並確保 network.sh
指令碼確實存在。這些命令用於在啟動時配置網路。
在這裡,您可以看到它實際上是如何工作的。機箱介面(我們使用 HPE Moonshots 1500)擁有節點,您可以輸入 show node list
命令來獲取所有節點的列表。現在您可以看到啟動過程。
您還可以透過 show node macaddr all
命令獲取它們的 MAC 地址。我們有一個聰明的操作員,它會自動從機箱收集 MAC 地址並將其傳遞給 DHCP 伺服器。實際上,它只是為在同一個管理 Kubernetes 叢集中執行的 dnsmasq-controller 建立自定義配置資源。此外,透過此介面,您可以控制節點本身,例如開啟和關閉它們。
如果您無法透過 iLO 進入機箱並收集節點的 MAC 地址列表,可以考慮使用全捕獲叢集模式。嚴格來說,它只是一個具有動態 DHCP 池的叢集。因此,所有未在其他叢集配置中描述的節點都將自動加入此叢集。
例如,您可以看到一個帶有某些節點的特殊叢集。它們以基於 MAC 地址自動生成的名稱加入叢集。從這一點開始,您可以連線到它們並檢視那裡發生的事情。在這裡,您可以以某種方式準備它們,例如設定檔案系統,然後將它們重新加入另一個叢集。
現在讓我們嘗試連線到節點終端,看看它是如何啟動的。BIOS 之後,網絡卡被配置,它從一個特定的 MAC 地址向 DHCP 伺服器傳送請求,DHCP 伺服器將其重定向到特定的 PXE 伺服器。隨後,核心和 initrd 映象使用標準的 HTTP 協議從伺服器下載。
載入核心後,節點下載 rootfs 映象並將控制權移交給 systemd。然後啟動過程照常進行,之後節點加入 Kubernetes。
如果你檢視 fstab,你會看到只有兩個條目:/var/lib/docker 和 /var/lib/kubelet,它們被掛載為 tmpfs(實際上是從 RAM)。同時,根分割槽被掛載為 overlayfs,所以你在系統上做的所有更改都將在下次重啟時丟失。
檢視節點上的塊裝置,您可以看到一些 nvme 磁碟,但它尚未掛載到任何地方。還有一個迴圈裝置——這是從伺服器下載的確切 rootfs 映象。目前它位於 RAM 中,佔用 653 MB,並使用 loop 選項掛載。
如果你檢視 /etc/ltsp,你會找到在啟動時執行的 network.sh
檔案。從容器中,你可以看到正在執行的 kube-proxy
和它的 pause
容器。
詳情
網路啟動映象
但主映象從何而來?這裡有一個小技巧。節點的映象透過 Dockerfile 與伺服器一起構建。Docker 多階段構建 功能允許您在映象構建階段輕鬆新增任何軟體包和核心模組。它看起來像這樣
這裡發生了什麼?首先,我們使用普通的 Ubuntu 20.04 並安裝所有我們需要的軟體包。首先,我們安裝 kernel、lvm、systemd、ssh。總的來說,你希望在最終節點上看到的一切都應該在這裡描述。我們還在這裡安裝了 docker
、kubelet
和 kubeadm
,它們用於將節點加入叢集。
然後我們執行額外的配置。在最後階段,我們只需安裝 tftp
和 nginx
(它為客戶端提供我們的映象)、grub(引導載入程式)。然後將前一階段的根目錄複製到最終映象中,並從中生成壓縮映象。也就是說,實際上,我們得到了一個 docker 映象,它同時包含伺服器和我們節點的引導映象。同時,可以透過更改 Dockerfile 輕鬆更新。
Webhook 和 API 聚合層
我想特別關注 Webhook 和聚合層的問題。一般來說,Webhook 是 Kubernetes 的一個功能,它允許您響應任何資源的建立或修改。因此,您可以新增一個處理程式,以便當資源被應用時,Kubernetes 必須向某個 Pod 傳送請求並檢查此資源的配置是否正確,或者對其進行額外的更改。
但關鍵是,為了讓 Webhook 正常工作,API 伺服器必須能夠直接訪問它所執行的叢集。如果它像我們的情況一樣,在單獨的叢集中啟動,甚至與任何叢集分開啟動,那麼 Konnectivity 服務可以幫助我們。Konnectivity 是一個可選但官方支援的 Kubernetes 元件。
以一個有四個節點的叢集為例,每個節點都執行著 `kubelet`,我們還有其他 Kubernetes 元件在外部執行:`kube-apiserver`、`kube-scheduler` 和 `kube-controller-manager`。預設情況下,所有這些元件都直接與 apiserver 互動——這是 Kubernetes 邏輯中最知名的部分。但實際上,也存在反向連線。例如,當您想檢視日誌或執行 `kubectl exec command` 時,API 伺服器會獨立地建立與特定 kubelet 的連線。
但問題是,如果我們有一個 Webhook,那麼它通常作為我們叢集中的標準 Pod 和服務執行。當 apiserver 試圖訪問它時,它會失敗,因為它會嘗試訪問一個名為 webhook.namespace.svc 的叢集內服務,而它本身位於它實際執行的叢集之外。
而 Konnectivity 在這裡就派上了用場。Konnectivity 是一個專為 Kubernetes 開發的巧妙代理伺服器。它可以作為伺服器部署在 apiserver 旁邊。Konnectivity-agent 以多個副本直接部署在您要訪問的叢集中。代理與伺服器建立連線,並設定一個穩定的通道,使 apiserver 能夠訪問叢集中的所有 Webhook 和所有 kubelet。因此,現在與叢集的所有通訊都將透過 Konnectivity-server 進行。
我們的計劃
當然,我們不會止步於此。對該專案感興趣的人經常給我寫信。如果感興趣的人足夠多,我希望將 Kubernetes-in-Kubernetes 專案轉移到 Kubernetes SIGs 下,以官方 Kubernetes Helm chart 的形式呈現。也許,透過使這個專案獨立,我們將聚集一個更大的社群。
我還在考慮將其與 Machine Controller Manager 整合,這將允許建立工作節點,不僅是物理伺服器,還可以,例如,使用 kubevirt 建立虛擬機器並在同一個 Kubernetes 叢集中執行它們。順便說一下,它還允許在雲中生成虛擬機器,並在本地部署控制平面。
我還在考慮與 Cluster-API 整合的選項,以便您可以直接透過 Kubernetes 環境建立物理 Kubefarm 叢集。但目前我對此想法並非完全確定。如果您對此事有任何想法,我將樂於傾聽。