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 命名空间,也都是类似的,我们这里就不多说了。

相关推荐
人间凡尔赛7 小时前
K8s 十年之变:从 Cloud Native 到 AI Native,2026 年后端架构的范式革命
后端·云原生·架构
名字还没想好☜7 小时前
Kubernetes CrashLoopBackOff 排查全流程:从 describe 到日志到 exit code
运维·docker·云原生·容器·kubernetes
Cloud云卷云舒9 小时前
AI Agent记忆系统:分层记忆体系的技术架构与实践
云原生·ai-native·haishandb
Ulyanov9 小时前
Python雷达电子对抗仿真引擎(一):打破单体瓶颈,构建微服务与ECS架构的顶层设计
开发语言·python·微服务·云原生·架构·雷达电子对抗
spider_xcxc11 小时前
Helm 部署 K8s 集群完整笔记
java·开发语言·kubernetes
张忠琳1 天前
【NVIDIA】 NVIDIA Container Toolkit v1.19.1 — OCI 模块超深度分析之三
云原生·容器·架构·kubernetes·nvidia
阿里云云原生1 天前
让 AI Agent 看见正在发生的业务,阿里云 EventHouse 正式商业化
云原生
Elastic 中国社区官方博客1 天前
将你的 Grafana Kubernetes 仪表板迁移到 Elastic Observability:相同的 PromQL,30 倍更快的查询
大数据·人工智能·elasticsearch·搜索引擎·容器·kubernetes·grafana
AOwhisky1 天前
Python 学习笔记(第十五期)——运维自动化(下·后篇):堡垒机实战——paramiko高阶篇
运维·python·学习·云原生·自动化·运维开发
微三云 - 廖会灵 (私域系统开发)1 天前
电商系统从单体到微服务拆分实践:拆什么、怎么拆、拆完后怎么办
微服务·云原生·架构