k3s的單程式模式如何執行整個K8S服務
為了提升k3s的使用體驗,我們將推出由k3s開發人員撰寫的“k3s黑魔法”系列文章來詳細介紹k3s功能、原理等方面的內容。本篇文章是該系列的第一篇,文章詳細分析了k3s的單程式模式如何執行整個Kubernetes服務。
同時,歡迎大家新增k3s助手(微訊號:k3s2019),加入官方微信群和大家一起交流。
前 言
Rancher Labs一直致力於雲基礎設施的建設,我們釋出了很多產品Rancher1.x、Rancher2.x、RancherOS、Longhorn、Rio等來滿足基礎設施應用的各種場景,但這其中沒有一款產品可以和k3s的發展速度相比,整個社群對它的認可超乎我們的想象。釋出了僅僅10個月的k3s專案,就在Github上獲得超9000顆star數,我們也正星夜兼程,爭取在11月份釋出1.0GA版本。我將撰寫一系列文章來介紹k3s所使用的技術及其原理,尤其是這其中使用的一些黑魔法,它讓k3s的體驗變得無比美好。更深入更細節得了解k3s,才能將它使用好,讓工具本身產生事半功倍的效用,同時也能讓大家有機會一起參與k3s社群的建設。
很多人在體驗k3s時,都對它的無比精簡感到折服。我們都瞭解Kubernetes(以下簡稱K8s)是個非常複雜的架構,controlplane中就包括apiserver/controller-manager/scheduler等,worker中還需要有kubelet/kube-proxy,元資料還需要儲存在etcd上。這些服務每一項都需要單獨部署,還需要進行配置聯動,儘管我們可以借用很多開源工具(比如RKE),但是部署前你還是需要準備大量映象或者二進位制檔案等。k3s的部署就非常簡便,它通過一個binary就可以部署上面提到的大部分服務,這也就是本文要介紹的內容,k3s的黑魔法之一“單程式k8s”。
單程式k8s分析
我們先不管具體如何實現,先來看一下單程式k8s的表面現象。我們安裝一個k3s,但禁用agent,這樣更有利於觀察結果:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable-agent" sh -
複製程式碼
這時使用kubectl是看不到node資訊,但是我們能夠獲取namespace資訊,這就說明k8s的controlplane相關服務已經啟動了:
# kubectl get no
No resources found in default namespace.
# kubectl get ns
NAME STATUS AGE
default Active 10s
kube-system Active 10s
kube-public Active 10s
kube-node-lease Active 10s
複製程式碼
使用ps命令觀察k3s程式執行結果,下方可以看到k3s只啟動了一個程式:
# ps aux | grep k3s
root 1900 2.8 40.8 564468 411436 ? Ssl 12:01 0:21 /usr/local/bin/k3s server --disable-agent
複製程式碼
那麼controlplane其他服務apiserver/scheduler/controller-manager等是如何啟動的,我們檢視k3s對應的thread:
# ps -T 1900
PID SPID TTY STAT TIME COMMAND
1900 1900 ? Ssl 0:01 /usr/local/bin/k3s server --disable-agent
1900 1910 ? Ssl 0:06 /usr/local/bin/k3s server --disable-agent
1900 1911 ? Ssl 0:01 /usr/local/bin/k3s server --disable-agent
1900 1912 ? Ssl 0:00 /usr/local/bin/k3s server --disable-agent
1900 1916 ? Ssl 0:00 /usr/local/bin/k3s server --disable-agent
1900 1917 ? Ssl 0:06 /usr/local/bin/k3s server --disable-agent
1900 1918 ? Ssl 0:10 /usr/local/bin/k3s server --disable-agent
1900 1948 ? Ssl 0:06 /usr/local/bin/k3s server --disable-agent
1900 1957 ? Ssl 0:00 /usr/local/bin/k3s server --disable-agent
複製程式碼
我們知道k3s是純粹Golang實現的,而Golang通常並不會直接使用thread,一般是通過goroutine來使用系統的thread,分析原始碼中k3s server的實現,可以看到api-server/scheduler等服務確實是goroutine來啟動的:
# https://github.com/rancher/k3s/blob/master/pkg/daemons/control/server.go
---------
go func() {
logrus.Infof("Running kube-scheduler %s",config.ArgString(args))
logrus.Fatalf("scheduler exited: %v",command.Execute())
}()
---------
---------
go func() {
logrus.Infof("Running kube-apiserver %s",config.ArgString(args))
logrus.Fatalf("apiserver exited: %v",command.Execute())
}()
startupConfig := <-app.StartupConfig
return startupConfig.Authenticator,startupConfig.Handler,nil
---------
複製程式碼
以kube-apiserver為例,在k3s server的thread中執行它,還需另外兩項工作:
-
在k3s中引入kube-apiserver程式碼,並將其編譯到k3s binary中,這部分在原始碼中的go.mod中有所體現
-
由於是在thread中執行,所以k3s不能執行apiserver的main函式,這部分在上面提到server.go原始碼中也有體現
k3s server的goroutine除了剛才提到的controlplane相關服務外,還包括預設內建執行的flannel/ingress controller,更有一些k3s擴充套件的一些高階controller,這部分我們會以單獨的文章進行分析。
以上內容,我們只是在單獨的k3s server層面分析,一旦我們加入一個節點作為worker,那麼worker節點上會怎樣的展現?
curl -sfL https://get.k3s.io | K3S_URL=https://myserver:6443 K3S_TOKEN=XXX sh -
複製程式碼
我們依然按照上面的思路,先看一下worker節點上k3s相關的程式和執行緒情況:
# ps aux | grep k3s
root 1949 0.8 12.2 220060 123648 ? Ssl 13:12 1:03 /usr/local/bin/k3s agent
root 1967 0.3 13.0 220932 131340 ? Sl 13:13 0:23 containerd -c /var/lib/rancher/k3s/agent/etc/containerd/config.toml -a /run/k3s/containerd/containerd.sock --state /run/k3s/containerd --root /var/lib/rancher/k3s/agent/containerd
# ps -T 1949
PID SPID TTY STAT TIME COMMAND
1949 1949 ? Ssl 0:01 /usr/local/bin/k3s agent
1949 1954 ? Ssl 0:06 /usr/local/bin/k3s agent
1949 1955 ? Ssl 0:00 /usr/local/bin/k3s agent
1949 1956 ? Ssl 0:00 /usr/local/bin/k3s agent
1949 1960 ? Ssl 0:00 /usr/local/bin/k3s agent
1949 1961 ? Ssl 0:14 /usr/local/bin/k3s agent
...
複製程式碼
worker節點的kubelet和kube-proxy的執行方式與k3s server上執行api-server/scheduler等服務的方式是一樣的,也包括agent上flannel和tunnel proxy等服務,都是通過goroutine呼叫,並在作業系統上以thread方式執行。而worker節點中,有一個特立獨行的存在就是containerd(如果你還是喜歡使用docker,請忽略以下內容),containerd是作為一個k3s agent的子程式來執行。
containerd因為有其特殊性,它會為每個容器建立單獨的containerd-shim程式為容器提供執行時支援,正因為這樣containerd本身必須是程式級別的,它可以擁有獨立的上下文,進而提供容器管理能力。較新版本的k3s,已經使用了containerd-shim-runc-v2來執行容器,這種模式對k8s的Pod更加友好,早期containerd-shim v1版本,pod的pause容器需要單獨執行一個containerd-shim程式,v2版本可以把Pod內的容器都放在一個containerd-shim程式下執行,Pod內每個容器會成為這個containerd-shim的子程式。比如coredns Pod對應的containerd-shim程式Pid是2325,那麼它的兩個子程式分別是coredns本身和pause容器服務:
# pstree -p -aT 2325
containerd-shim,2325 -namespace k8s.io -id 5aad2ea4d09f997baab6a0343dfb10abd86971601bae29200e39cffb5709b938-a
├─coredns,2598 -conf /etc/coredns/Corefile
└─pause,2392
複製程式碼
後 記
本文向大家分析了k3s這種單程式模式如何執行整個k8s服務,相當於我們對k3s的原理有了一個基本瞭解。然而,k3s仍然有很多未解之謎,agent和server如何通訊組建叢集?k3s內建的rootfs起到什麼作用?k3s內建的CLI工具如何使用?k3s如何實現使用sqllite/mysql來代替etcd等等,這些問題我們會在後續文章中一一解答。