k3s的单进程模式如何运行整个K8S服务



为了提升k3s的使用体验,我们将推出由k3s开发人员撰写的“k3s黑魔法”系列文章来详细介绍k3s功能、原理等方面的内容。本篇文章是该系列的第一篇,文章详细分析了k3s的单进程模式如何运行整个Kubernetes服务。

前 言

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 ... er.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中执行它,还需另外两项工作:

  1. 在k3s中引入kube-apiserver代码,并将其编译到k3s binary中,这部分在源码中的go.mod中有所体现

  2. 由于是在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 个评论

要回复文章请先登录注册