k3s的单进程模式如何运行整个K8S服务
为了提升k3s的使用体验,我们将推出由k3s开发人员撰写的“k3s黑魔法”系列文章来详细介绍k3s功能、原理等方面的内容。本篇文章是该系列的第一篇,文章详细分析了k3s的单进程模式如何运行整个Kubernetes服务。同时,欢迎大家添加k3s助手(微信号:k3s2019),加入官方微信群和大家一起交流。前 言Rancher Labs一直致力于云基础设施的建设,我们发布了很多产品Ranch...
为了提升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等等,这些问题我们会在后续文章中一一解答。
更多推荐
所有评论(0)