一、k8s 架构
我们在构建k8s集群的时候首先需要搭建master节点、其次需要创建node节点并将node节点加入到k8s集群中。当我们构建好k8s集群后,我们可以通过kubectl create -f nginx.yml 命令的方式来创建应用对应的pod。
当我们执行命令后,命令会提交给API server,它会解析yml文件,并将其以API对象的形式存到 etcd里。这时master组件中的Controller Manager会通过控制循环的方式来做编排工作,创建应用所需要的Pod。Scheduler 会 watch etcd中新Pod 的变化。
如果他发现有一个新的Pod 出现,Scheduler会运行调度算法,通过调度算法最终选择出最佳的Node节点,并将这个Node节点的名字写到pod对象的NodeName字段上面,这一步就是所谓的Bind Pod to Node(下图的标注),然后把bind的结果写回到etcd。
其次,当我们在构建k8s集群的时候,默认每个节点上都会初始化创建一个kubelet进程,kubelet进程的会watch etcd中的pod的变化,当kubelet进程watch到pod的bind的更新操作,并且bind的节点是本节点时,它会接管接下来的
所做的事情,如镜像下载,容器创建等。
image二、k8s 默认容器运行时架构
接下来将通过k8s默认集成的容器运行时架构来看kublete如何创建一个容器(如下图所示)。
1. kubelet 通过 CRI(Container Runtime Interface) 接口(gRPC) 调用 dockershim, 请求创建一个容器, 这一步中, Kubelet 可以视作一个简单的 CRI Client, 而 dockershim 就是接收请求的 Server.
2. dockershim 收到请求后, 通过适配的方式,适配成 Docker Daemon 的请求格式, 发到 Docker Daemon 上请求创建一个容器。在docker 1.12后版本中,docker daemon被拆分成dockerd和containerd,containerd负责操作容器。
3. dockerd收到请求后, 调用containerd进程去创建一个容器。
4. containerd 收到请求后, 并不会自己直接去操作容器, 而是创建一个叫做 containerd-shim 的进程, 让 containerd-shim 去操作容器. 创建containered-shim的目的主要有:
1)让containerd-shim做诸如收集状态, 维持 stdin 等 fd 打开等工作.
2)允许容器运行时(runC)启动容器后退出,不必为每个容器一直运行一个容器运行时runC。
3)即使在 containerd 和 dockerd 都挂掉的情况下,容器的标准 IO 和其它的文件描述符也都是可用的。
4)向 containerd 报告容器的退出状态
5)在不中断容器运行的情况下升级或重启 dockerd
5. 而containerd-shim 在这一步需要调用 runC 这个命令行工具, 来启动容器,runC是**OCI(Open Container Initiative, 开放容器标准) **的一个参考实现。主要用来设置 namespaces 和 cgroups, 挂载 root filesystem等操作。
6.runC
启动完容器后本身会直接退出, containerd-shim 则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理, 确保不会出现僵尸进程(关闭进程描述符等)。
三、容器与容器编排背景简述
从k8s的容器运行时可以看出,kubelet启动容器的过程经过了很长的一段调用链路。这个是由于在容器及编排领域各大厂商与docker之间的竞争以及docker公司为了抢占paas领域市场,对架构做出的一系列调整。
其实 k8s 最开始的运行时架构链路调用没有这么复杂: kubelet 想要创建容器直接通过 docker api 调用 Docker Daemon,Docker Daemon 调 libcontainer 这个库来启动容器。
为了防止docker垄断以及受控docker运行时, 各大厂商于是就联合起来制定出开放容器标准OCI(Open Containers Initiative).大家可以基于这个标准开发自己的容器运行时。Docker公司则把 libcontainer做了一层封装, 变成 runC 捐献给CNCF作为 OCI 的参考
接下来就是 Docker 要搞 Swarm 进军 PaaS 市场, 于是做了个架构切分, 把容器操作都移动到一个单独的 Daemon 进程 containerd 中去, 让 Docker Daemon 专门负责上层的封装编排. 最终swarm败给了k8s, 于是Docker 公司就把 containerd 捐给 CNCF ,专注于搞 Docker 企业版
与此同时,容器领域,core os公司推出了个rkt容器运行时。希望 k8s 原生支持 rkt 作为运行时, 由于core os与google的关系,最终rkt运行时的支持在2016年也被合并进kubelet主干代码里.
这样做后反而给k8s中负责维护 kubelet 的小组 SIG-Node带来了更大的负担,每一次kubelet的更新都要维护docker和rkt两部分代码。与此同时,随着虚拟化技术强隔离容器技术runV(Kata Containers前身,后与intel clear container 合并)的逐渐成熟。
k8s上游对虚拟化容器的支持很快被提上了日程。为了从集成每一种运行时都要维护一份代码中解放出来,k8s SIG-Node工作组决定对容器的操作统一地抽象成一个接口,这样kubelet只需要跟这个接口打交道,而具体地容器运行时,他们只需要实现该接口,并对kubelet暴露gRPC服务即可。这个统一地抽象地接口就是k8s中俗称的 CRI。
网友评论