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 天前
国内本地WSL2编译rancher源码
云原生
小猿姐3 天前
MySQL Top 10 热点问题 AI 运维实战:从内核诊断到云原生运维
mysql·云原生·aiops
阿里云云原生4 天前
深入内核:拆解 OpenTelemetry eBPF 探针如何优雅地“透视”多语言微服务?
云原生
java_cj4 天前
深入kube-apiserver认证机制:从Bearer Token到mTLS的完整认证链解析
linux·运维·服务器·云原生·容器·kubernetes
qq_452396234 天前
第十三篇:《K8s 安全基础:RBAC、ServiceAccount、Pod Security》
java·安全·kubernetes
睡不醒男孩0308234 天前
云原生运维实战:高并发架构下的云原生可观测性、韧性降级与自动化干预体系
数据库·kubernetes·高并发·prometheus·devops·sre·缓存调优
qq_452396234 天前
第十四篇:《K8s 网络模型与 CNI 插件(Calico、Flannel、Cilium)》
网络·kubernetes·php
Hadoop_Liang4 天前
Kubernetes 应用 HTTPS 安全访问配置实践
https·kubernetes
互联网推荐官4 天前
上海软件定制开发公司推荐:从PaaS工程化路径看D-coding的技术取舍
云原生·云计算·paas·软件开发·开发经验·上海
sbjdhjd4 天前
从零搭建企业级 CI/CD(下):Jenkins+GitLab+Harbor 全链路实战指南
git·servlet·ci/cd·云原生·云计算·gitlab·jenkins