本文翻譯自:https://kubernetes.io/blog/2021/12/22/kubernetes-in-kubernetes-and-pxe-bootable-server-farm/
作者:Andrei Kvapil
本文翻譯自Andrei Kvapil的文章,其中對文字有部分的整理和删減。
當你有兩個資料中心,數千個實體機、虛拟機以及數十萬個站點需要托管的時候,通過Kubernetes就可以很簡單的實作上述需求。然而,使用Kubernetes,不僅可以聲明式的描述應用,還可以聲明式的描述基礎設施。我在捷克最大的托管服務提供商
WEDOS Internet a.s
工作,今天我将向您展示我的兩個項目——Kubernetes-in-Kubernetes【1】 和 Kubefarm【2】。
使用它們,就可以使用Helm在一個Kubernetes叢集中部署一個完整的Kubernetes叢集。
首先介紹一下我們基礎設施是如何工作的。我們将實體伺服器分為兩組:控制平面和計算節點。其中控制平面通常是手動設定并且安裝穩定的作業系統,旨在運作包括Kubernetes在内的所有叢集服務,這些節點用于保障Kubernetes叢集本身的穩定運作。而計算節點是沒有安裝任何作業系統的,在需要的時候,會直接通過控制平面節點通過網絡下載下傳鏡像。

當節點把鏡像下載下傳下來過後,它們就可以繼續後續的工作而不需要一直和PXE伺服器建立連接配接。也就是說,PXE 伺服器隻儲存 rootfs 映像,不儲存任何其他複雜的邏輯。在我們的節點啟動後,我們可以安全地重新啟動 PXE 伺服器,它們不會發生任何嚴重的事情。
當計算節點啟動後,就需要使用
kubeadm join
指令将其加入Kubernetes叢集,這樣才會将Pod排程到該計算節點并且啟動工作負載。
從一開始,當節點加入到用于控制平面節點的同一叢集時,就使用了該方案并且穩定運作了兩年多,後面我們決定向裡面添加容器化的Kubernetes。現在我們可以很容易的在控制平面生成新的Kubernetes叢集,而且這些計算節點也變成了特殊的叢集成員。截至目前,根據不同的配置可以将計算節點加入到不同的叢集中。
Kubefarm
Kubefarm項目的目标是讓任何人都可以使用Helm來部署基礎設施已達到預定的效果。
為此,我們放棄了單叢集的想法。事實證明,在同一個叢集中管理多個開發團隊的工作不是很友善,并且Kubernetes 從未被設計為多租戶解決方案,因為它并沒有提供足夠的項目之間的隔離手段。是以,為每個團隊運作單獨的叢集被證明是一個好辦法, 但是叢集不能太多,不然不友善管理。
重構之後,我們叢集的可擴充性明顯更好,擁有的叢集越多,故障域越小,它們的工作就越穩定,作為回報,我們得到了一個完全聲明式描述的基礎設施, 是以,現在您可以像在 Kubernetes 中部署任何其他應用程式一樣部署新的 Kubernetes 叢集。
它使用 Kubernetes-in-Kubernetes【1】作為基礎,LTSP 作為 PXE-server,節點從中啟動,并使用 dnsmasq-controller 自動配置 DHCP 伺服器。
image.png
它是如何工作的?
現在我們來看看它是如何工作的。
如果從應用角度來看Kubernetes,你可以發現它完全遵循
十二要素
【3】的所有原則,是以,如果僅僅将Kubernetes當成一個應用,将其部署到Kubernetes中是一件理所當然的事情。
在Kubernetes中運作Kubernetes
現在來看看
Kubernetes-in-Kubernetes
【1】項目,它提供現成的
Helm Chart
【4】,幫助我們快速的在Kubernetes中部署Kubernetes。
使用以下指令,就可以在Kubernetes中部署一套Kubernetes叢集。
helm repo add kvaps https://kvaps.github.io/charts
helm install foo kvaps/kubernetes --version 0.13.1 \
--namespace foo \
--create-namespace \
--set persistence.storageClassName=local-path
複制
描述檔案在
value.yaml
檔案中,它主要描述了Kubernetes控制器元件:Etcd叢集、apiserver、controller-manager、scheduler等,這些都是标準的Kubernetes元件。
如果你打算使用kubeadm安裝叢集,value.yaml就是其配置,但是除了kubernetes之外,還有一個管理容器,其主要包含兩個二進制檔案:kubectl和kubeadm,它們用于為kubernetes叢集生成kubeconfig并執行叢集初始化,除此之外,你還可以通過這個管理容器來檢查和管理你的kubernetes叢集。
待叢集部署完成過後,就可以看到一系列的Pod:admin-container,apiserver,controller-manager,etcd-cluster,scheduller以及初始化叢集的Job。在最後會展示如何進入admin-container的指令,如下:
另外,我們可以通過以下指令檢視kubernetes叢集證書。如果使用過kubeadm安裝過kubernetes叢集,一定知道
/etc/kubernetes/pki
目錄,它是用來存放kubernetes證書的。對于Kubernetes-in-Kubernetes,你可以使用cert-manager對叢集證書進行管理,隻需要在使用Helm進行安裝的時候傳遞證書參數,cert-manager就可以幫你自動生成所有證書。
我們來檢視其中一個證書,比如apiserver,你可以看到它有一個DNS名稱和IP位址清單,如果想通過外部通路叢集,隻需要在配置檔案中描述額外的DNS名稱并更新版本,cert-manager就會幫助我們重新生成證書,并且不必擔心kubeadm證書更新的問題,因為cert-manager會管理并自動更新它們。
現在,我們可以進入管理容器檢視叢集狀态和節點資訊,當然,現在叢集是沒有節點的,因為我們僅僅部署了kubernetes控制平面,但是我們可以在kube-system名稱空間下coredns pod以及一些configmap,如下。
下面是一個叢集示意圖,你可以看到kubernetes的整個控制平面:apiserver、controller-manager、etcd-cluster以及scheduler。還有一些流量的轉發路徑。
改圖是通過argocd生成---argocd是一個gitops工具,如上的圖表隻是它的工具之一。
編排實體伺服器
通過上面的介紹,我們知道如何在Kubernetes中部署控制平面,但是并沒有添加任何工作節點,我們應該如何添加它們呢?我之前介紹過,我們所有的伺服器都是裸機,不使用任何虛拟化來運作Kubernetes,而是自己編排所有的實體伺服器。
此外,我們非常積極的使用Linux網絡引導功能(注意這裡指的是網絡引導而不是某種自動化安裝)。當節點啟動時,我們隻為它運作一個現成的鏡像,也就是說,如果要修改或者更新節點,隻需要更新鏡像,然後重新開機即可,這是不是非常容易、簡單和友善。
為此,我們建立了kubefarm【5】項目,它可以自動完成上述的操作。樣例操作可以參考examples【6】目錄,其中穩定版我們命名為:generic,我們可以到value.yaml中檢視配置資訊。
- generic/values.yaml
我們可以在value.yaml中定義傳遞給kubernetes-in-kubernetes chart的參數,如果想從外部通路控制平面,可以在value.yaml中定義IP位址,除此之外,還可以定義一些DNS。
在PXE的服務配置中(ltsp子產品),我們可以指定時區、可以添加SSH密鑰以及在系統引導期間的核心子產品以及參數等。
接下來就是nodePools的配置,即work節點的配置,如果你之前使用過GKE的terraform ,那你應該能快速上手。在這裡,我們通過以下參數來描述所有work節點。
- Name:主機名
- MAC-address:我們有帶有兩個網卡的節點,每個節點都可以在此處指定MAC位址,然後以指定的MAC位址啟動
- IP-address:用于DHCP服務發現
在上面的value.yaml中,我們定義了兩個node pools:第一個pool定義了5個node節點,第二個pool定義了一個node節點,不過定義了兩個tags,tag是用來描述節點配置的。比如,你可以為某些特定的pool添加DHCP選項用于啟動PXE服務以及一組KubernetesLabels和KubernetesTaints選項。
比如,在上面的配置中的第二個pool裡配置了一個節點,這個pool配置設定了debug和foo标記,現在檢視kubernetesLabels中的foo标記選項,其意味着m1c43節點将配置設定這兩個labels和taint。一切看起來都那麼簡單,下面将進行具體的實踐。
樣例實踐
到example【6】目錄中更新kubefarm的chart包,其中generic目錄下是通用配置,如下圖更新即可。這時候檢視叢集Pods,就可以看到一個PXE服務和許多的job被添加運作。Job是用來部署Kubernetes叢集和建立Token,它每隔12小時會建立一個新的Token,以便這些節點能連接配接到你的叢集。
在argocd的圖表上檢視如下,apiserver進行對外暴露。
在圖中,IP 以綠色突出顯示,可以通過它通路 PXE 伺服器。目前,Kubernetes 預設不允許為 TCP 和 UDP 協定建立單個 LoadBalancer 服務,是以您必須建立兩個具有相同 IP 位址的不同服務, 一個用于 TFTP,第二個用于 HTTP,通過它下載下傳系統映像。
當然這隻是一個簡單的示例,有時候你需要在啟動的時候修改邏輯,比如在advanced_network【7】目錄下,其中有一個帶有簡單 shell 腳本的值檔案。我們稱之為network.sh,它是用來修改網絡相關的配置:
- network.sh
該腳本所做的隻是在啟動時擷取環境變量,并根據它們生成網絡配置, 它會建立一個目錄并将 netplan 配置放入其中。例如,在這裡建立一個綁定接口。基本上,這個腳本可以包含你需要的一切。它可以儲存網絡配置或生成系統服務,添加一些鈎子或描述任何其他邏輯。,任何可以用 bash 或 shell 語言描述的東西都可以在這裡工作,并且會在啟動時執行。
現在我們來看看其是如何被部署的,通過傳遞一些value檔案來傳遞參數,這是Helm的正常使用方式。這種方式可以傳遞一些secrets,但是在這個示例中,擴充配置檔案在第二個values.yaml中。
我們可以檢視針對netboot的配置檔案foo-kubernetes-ltsp,確定network.sh是存在的,這些配置主要在網絡引導時使用。
你可以在這裡【8】檢視其工作原理,可以通過輸入
show node list
檢視所有的節點,如下:
你也可以通過show node macaddr all指令來檢視節點的mac位址。我們定義了一個Operator自動從機箱搜集Mac位址并傳遞給DHCP伺服器。實際上,它隻是為同一個管理叢集中的dnsmsap-controller建立自定義配置,另外,通過這個接口可以控制節點本身,比如打開或者關閉它們。
如果您沒有通過 iLO 進入機箱并為您的節點收集 MAC 位址清單的機會,您可以考慮使用包羅萬象的叢集模式。純粹來說,它隻是一個帶有動态 DHCP 池的叢集。是以,所有未在其他叢集的配置中描述的節點将自動加入該叢集。
如上,你可以看到該叢集的work節點就是通過自動加入的方式加入叢集的,它們的名字是通過其MAC位址自動生成。你可以通過node-shell 指令連接配接節點并檢視其狀态,你也可以在這裡初始化它們,比如設定檔案系統或将其加入其他的叢集。
現在讓我們連接配接到其中一個節點并觀察其是如何啟動的。當BIOS之後,就會配置網卡,并通過特定的MAC位址向DHCP伺服器發送請求,然後會被重定向到PXE服務,最後通過HTTP方式從服務端下載下傳kernel和initrd鏡像。
kernel加載完成過後,work節點就會下載下傳rootfs鏡像并将控制權交給systemd,引導會繼續進行,然後會加入Kubernetes叢集。
如果你檢視
fstab
檔案,你可以看到隻有兩個目錄挂載:/var/lib/docker和/var/lib/kubelet,它們被挂載為tmpfs。同時,根分區挂載為overlayfs,是以,你對系統做的任何修改在下次重新開機過後都會丢失。
檢視節點上的塊裝置,您可以看到一些 nvme 磁盤,但它還沒有挂載到任何地方, 還有一個loop裝置 - -這是從伺服器下載下傳的确切 rootfs 映像,目前它位于 RAM 中,占用 653 MB 并使用loop選項安裝。
如果你檢視/etc/ltsp,你可以看到network.sh檔案在啟動的時候被執行。通過
docker ps
檢視容器的話,可以看到
kube-proxy
和
pause
容器。
以上就完成了work節點初始化并加入Kubernetes叢集。
其他
Network Boot Image
我們的主鏡像從哪裡來呢?這裡其實是有個小技巧,節點的鏡像是通過這個Dockerfile【9】建構而來,Docker的多階段建構允許你靈活添加一個包和子產品,我們可以通過連結來看看這個Dockfile【9】。
首先,我們使用Ubuntu 20.04鏡像并安裝需要的軟體包,我們會安裝kernel、lvm、systemd、ssh。一般來說,你期望節點擁有什麼能力都應該在這裡配置描述。這裡我們還安裝了帶有kubelet和kubeadm的Docker,用于将node加入叢集。
然後我們執行額外的配置。在最後階段,我們隻需安裝 tftp 和 nginx(将我們的映像提供給用戶端)、grub(引導加載程式), 然後将先前階段的根複制到最終圖像中并從中生成壓縮圖像。實際上,我們得到了一個 docker 鏡像,其中包含我們節點的伺服器和啟動鏡像,我們可以通過更改 Dockerfile 輕松更新配置。
Webhooks and API aggregation layer
我特别關注webhooks和api aggregation layer的問題。一般來說,webhook是Kubernetes的一項功能,它允許你響應任何資源的建立和修改,是以,你可以添加一個處理程式,以便在應用資源時,kubernetes必須向某個pod發送請求檢查該資源的配置是否正确,或者對其進行額外的修改。
但是關鍵的問題在于如果要讓webhook工作,apiserver必須能夠直接通路它正在運作的叢集。如果它像我們的案例一樣在單獨的叢集啟動或者在其他叢集分開啟動,這時候我們就要借助Konnectivity【10】服務來為我們提供幫助。Konnectivity是Kubernetes官方支援的插件。
讓我們以四個節點的叢集為例,每個節點都運作一個 kubelet,我們還有其他 Kubernetes 元件在外部運作:kube-apiserver、kube-scheduler 和 kube-controller-manager。預設情況下,所有這些元件都直接與 apiserver 互動——這是 Kubernetes 邏輯中最知名的部分。但實際上,也存在反向連接配接,例如,當您要檢視日志或運作 kubectl exec 指令時,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的形式表示。也許,通過使這個項目獨立,我們将聚集一個更大的社群。
我也在考慮将它與機器控制器管理器內建,這将允許建立工作節點,不僅是實體伺服器,例如,用于使用 kubevirt 建立虛拟機并在同一個 Kubernetes 叢集中運作它們。順便說一句,它還允許在雲中生成虛拟機,并在本地部署控制平面。
我還在考慮與 Cluster-API 內建的選項,以便您可以直接通過 Kubernetes 環境建立實體 Kubefarm 叢集。但目前我并不完全确定這個想法。如果您對此事有任何想法,我很樂意聽取他們的意見。
我是 喬克,一線運維農民工,雲原生實踐者,這裡不僅有硬核的技術幹貨,還有我們對技術的思考和感悟
引用
【1】https://github.com/kvaps/kubernetes-in-kubernetes
【2】https://github.com/kvaps/kubefarm
【3】https://www.kubernetes.org.cn/8492.html
【4】https://github.com/kvaps/kubernetes-in-kubernetes/tree/v0.13.1/deploy/helm
【5】https://github.com/kvaps/kubefarm.git
【6】https://github.com/kvaps/kubefarm/tree/v0.13.1/examples
【7】https://github.com/kvaps/kubefarm/tree/v0.13.1/examples/advanced_network
【8】https://asciinema.org/a/407286
【9】https://github.com/kvaps/kubefarm/blob/v0.13.1/build/ltsp/Dockerfile
【10】https://kubernetes.io/docs/tasks/extend-kubernetes/setup-konnectivity/