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

相关推荐
Elastic 中国社区官方博客5 小时前
我们如何在 Elasticsearch Serverless 上将向量搜索吞吐量提升一倍
大数据·数据库·人工智能·elasticsearch·搜索引擎·云原生·serverless
maomao大哥闯天下5 小时前
K8s如何实现滚动更新、健康检查与探测机制
docker·容器·kubernetes
张忠琳8 小时前
【kubernetes v1.21】(一)Kubernetes 总览架构深度分析
云原生·架构·kubernetes
maomao大哥闯天下8 小时前
K8s对象deployment、job、service应用详解
java·容器·kubernetes
IT策士9 小时前
第 20 篇 搭建 Kubernetes 实验环境:Minikube 与 kubectl
云原生·容器·kubernetes
JackSparrow41410 小时前
使用Ansible批量管理+更新产品环境服务器配置
运维·服务器·ci/cd·kubernetes·自动化·ansible·sre
Elastic 中国社区官方博客10 小时前
Kibana 仪表板即代码:在 Elastic 9.4 中用于 Kibana 仪表板的 GitOps、漂移检测与 Terraform
大数据·人工智能·elasticsearch·搜索引擎·云原生·kibana·terraform
小哈里11 小时前
【K8S】云原生时代的GitOps最佳实践 —— ArgoCD
云原生·kubernetes·云计算·argocd·基础设施
张忠琳11 小时前
【kubernetes v1.21】(kube-apiserver 1)kube-apiserver 核心架构与启动流程超深度分析
云原生·架构·kubernetes
IT策士12 小时前
第 24 篇 k8s之健康检查:探针机制详解
云原生·容器·kubernetes