kubelet 源码剖析(二):创建一个Pod的整体视角

这篇文章,我们来说说创建一个 Pod 的整体流程,这里不讲具体的细节,在后面的文章中我们会对重要的步骤展开详细的分析。

下面这张图是从组件层面来看创建 Pod 的主要流程

pF0bUVs.md.png

kubelet 需要能够实时感知到有新 pod 需要创建,并且要知道 pod 的定义是什么,kubelet 有如下三种方式数据来源

  • kube-apiserver

    kube-apiserver 可以说是最常见的 pod 数据源,用户的应用基本都是通过 kube-apiserver 创建 deployment 等资源,kube-control-manager 基于 deployment 等资源调用 kube-apiserver 接口创建 pod。kubelet 启动时先从 kube-apiserver list 所有 pod,然后判断有哪些 pod 是分配到本节点上的,并把这些 pod 创建出来。后续 kubelet 也会持续 watch kube-apiserver 感知 pod 的变化,不错过任何分配到本节点的 pod。

  • node 文件系统

    这种方式是 kubelet 通过监测本地文件系统特定目录的方式来获取需要创建的 pod,这个目录通过启动参数 --pod-manifest-path="" 或者配置文件参数 staticPodPath 来配置,如果是通过 kubeadm 部署的话默认为 /etc/kubernetes/manifests。这种 pod 叫 static pod。

    如果你想通过这种方式部署一个 pod,只需要在上述目录中放入 pod 的 manifest 文件即可,kubelet 会自动监测到该文件,并创建 pod。 这种 pod 你无法通过 Kubectl 命令行工具管理,因为他的生命周期不归控制面组件管理,只能通过在节点上编辑文件来变更 pod。

    通常,管理面组件如 apiserver、controller-manager、scheduler 会通过这种方式部署。因为想要通过监测 kube-apiserver 的方式部署 pod,那么 kube-apiserver 必须是正常工作的,现在 kube-apiserver 还没有部署,所以需要通过 static pod 的方式部署

  • 外部 http 服务

    这种方式是通过定期访问外部的 http 提供的服务,监测是否有需要创建的 pod,这种方式创建的 pod 也是 static pod,本质上和第二种方式区别不大,平时我们也用的不多,这里就不多赘述。

我们来看下这个循环监测的整体代码框架

go 复制代码
// pkg/kubelet/kubelet.go
func (kl *Kubelet) syncLoop(...) {
	...
	for {
		...
		if !kl.syncLoopIteration(updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) {
			break
		}
		...
	}
}

// pkg/kubelet/kubelet.go
func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate,...) {
	select {
		case u, open := <-configCh:
			if !open {
				klog.Errorf("Update channel is closed. Exiting the sync loop.")
				return false
			}
			switch u.Op {
			case kubetypes.ADD:
				klog.V(2).Infof("SyncLoop (ADD, %q): %q", u.Source, format.Pods(u.Pods))
				handler.HandlePodAdditions(u.Pods)
			...
			}
	}
}

syncLoop 中会无限循环的调用 syncLoopIteration ,在 syncLoopIteration 中会检测 configCh 通道,如果能够从通道中读取到数据且判断为是新建 pod,那么就调用 HandlePodAdditions 开始一个 pod的创建流程。

我们在上面说了 kubelet 会监测三种数据源,实际上是通过三个协程去处理的,当有新 Pod 创建时,对应的协程就会发需要创建的 pod 配置发送到 configCh 这个通道,这样 syncLoopIteration 就能获取到数据做处理。

上述流程,我们可以用下图示意:

pFB00zT.md.png

syncLoopIteration 拿到需要新建的 pod 配置后,于是调用 HandlePodAdditions 开始创建 pod。因为这篇文章,我们只讲整体框架,不讲细节,所以具体的详细流程我们后面讲。

我们知道,一个 pod 中可以有多个容器,这些容器共享这个 pod 的 Linux 命名空间,从而进行资源隔离,常见的有网络命名空间、PID命名空间等。在创建 Pod 时,首先会创建一个 pause 容器,然后用这个容器中的进程去 "hold" 住网络命名空间,后续 pod 中所有的容器启动后容器内的进程后会加入到该网络命名空间内。

下图为 pod 网络命名空间示意图

pFsFh8K.md.png

为了创建 sandbox(一个独立的网络栈),kubelet 开始调用 cri(容器运行时,如containerd),cri 会先启动一个 pause 容器,然后调用 cni(容器网络接口,如calico/flannel等都实现了cni)为该 pause 容器(其实也就是一个进程)设置网络栈,cni所做的事就是为 pause 容器设置网络栈,如创建网卡、设置路由、获取一个 IP,为网卡配置IP,该IP就是容器IP。不同的 cni 实现有不同的 IP 管理方式。该过程可以通过下面图示表示:

pFsGYNV.md.png

cri 创建完了 sandbox后,那么 pause 容器就启动了,这个容器有自己的网络命名空间,返回 sandbox id 给 kubelet。

有了 sandbox后,就获得了一个隔离环境,进而可以后续的其他容器创建,一共有三种类型的容器:

  • 临时容器

  • init 容器

  • 业务容器(承载用户业务)

在创建这些容器时,kubelet 会传给 cri 之前返回的 sandbox id,cri收到创建任务后,就会拉起容器,并且把容器进程加入到 sandbox id 所代表的的网络命名空间中,这样这个 pod 所看到的网络栈就是一致的,所有容器共享一个 pod ip。

我们可以看到这个过程中,主要处理创建 pod 任务的组件是 cri 和 cni,现在有如下几个问题:

  • kubelet 是怎么确定 cri 的地址的?
  • 如果一个节点上有多个 cri,kubelet又是怎么确定使用哪个的?
  • cri 又是怎么确定调用哪个 cni 的?

除了网络命名空间这个隔离外,如 pid 命名空间、IPC 命名空间,也都是类似的,我们这里就不多说了。

相关推荐
妍妍的宝贝1 小时前
k8s 中的金丝雀发布(灰度发布)
云原生·容器·kubernetes
梆子井欢喜坨2 小时前
《Cloud Native Data Center Networking》(云原生数据中心网络设计)读书笔记 -- 12数据中心中的EVPN
网络·云原生
Dylanioucn3 小时前
【分布式微服务云原生】掌握 Redis Cluster架构解析、动态扩展原理以及哈希槽分片算法
算法·云原生·架构
飞酱不会电脑5 小时前
云计算第四阶段 CLOUD2周目 01-03
云原生·容器·kubernetes
程序那点事儿7 小时前
k8s 之安装busybox
云原生·容器·kubernetes
weixin_453965008 小时前
master节点k8s部署]33.ceph分布式存储(四)
分布式·ceph·kubernetes
是芽芽哩!8 小时前
【Kubernetes】常见面试题汇总(五十八)
云原生·容器·kubernetes
福大大架构师每日一题19 小时前
22.1 k8s不同role级别的服务发现
容器·kubernetes·服务发现
weixin_4539650020 小时前
[单master节点k8s部署]30.ceph分布式存储(一)
分布式·ceph·kubernetes
weixin_4539650020 小时前
[单master节点k8s部署]32.ceph分布式存储(三)
分布式·ceph·kubernetes