一文搞懂 Kubernetes Pod的创建流程

Pod 创建整体流程

通过Pod创建的时序图如下

接下来我们来具体分析每一个过程

api-server --- 用于接收客户端Pod创建请求

API 服务器是 Kubernetes 控制平面的组件, 该组件负责公开了 Kubernetes API,负责处理接受请求的工作。 API 服务器是 Kubernetes 控制平面的前端。

api-server先初始化Config,通过Config初始化Server。在 CompletedConfig 的New方法中 为每个Server初始化REST存储,调用了NewRESTStorage。

e.g. 请求Pod资源的时候都是返回 Pod一系列Rest操作,起到一个聚合的作用

Storage的接口定义及具体实现,可以通过路经定位。e.g. /api/v1/watch/pods

scss 复制代码
type RESTStorageProvider interface {
	GroupName() string
	NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error)
}

// 初始化具体实现
func (c *GenericConfig) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error){}

安装 API 时首先为每个 API Resource 创建对应的后端存储 RESTStorage

go 复制代码
// InstallAPIs 在给定的 Instance 的 GenericAPIServer 上安装 API 组和相应的 REST 存储处理程序。
func (m *Instance) InstallAPIs(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, restStorageProviders ...RESTStorageProvider) error {
	// 根据服务器的版本初始化资源过期评估器
	resourceExpirationEvaluator, err := genericapiserver.NewResourceExpirationEvaluator(*m.GenericAPIServer.Version)
	if err != nil {
		return err
	}

	// 遍历提供的 REST 存储提供程序
	for _, restStorageBuilder := range restStorageProviders {
		groupName := restStorageBuilder.GroupName()

		// 创建 API 组信息和 REST 存储处理程序
		apiGroupInfo, err := restStorageBuilder.NewRESTStorage(apiResourceConfigSource, restOptionsGetter)
		if err != nil {
			return fmt.Errorf("初始化 API 组 %q 时出现问题:%v", groupName, err)
		}

		// core API 注册到路由中,是 apiserver 初始化流程中最核心的方法之一
		if len(groupName) == 0 {
			if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil {
				return fmt.Errorf("注册遗留 API 时发生错误:%w", err)
			}
		} else {
			// 否则,将其添加到非遗留 API 组的切片中,将其安装到 /apis 路径下
			nonLegacy = append(nonLegacy, &apiGroupInfo)
		}
	}

	if err := m.GenericAPIServer.InstallAPIGroups(nonLegacy...); err != nil {}
	return nil
}

在为资源创建完 RESTStorage 后,调用 m.GenericAPIServer.InstallLegacyAPIGroup 为 APIGroup 注册路由信息.

InstallLegacyAPIGroup方法的调用链非常深,主要调用链如下:

go 复制代码
InstallLegacyAPIGroup--> installAPIResources --> InstallREST --> Install --> registerResourceHandlers

// 真正执行安装的方法
func (a *APIInstaller) Install() ([]metav1.APIResource, []*storageversion.ResourceInfo, *restful.WebService, []error)
  for path := range a.group.Storage {
		paths[i] = path
		i++
	}
	sort.Strings(paths)
	for _, path := range paths {
		apiResource, resourceInfo, err := a.registerResourceHandlers(path, a.group.Storage[path], ws)
		if err != nil {
			errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err))
		}
		if apiResource != nil {
			apiResources = append(apiResources, *apiResource)
		}
		if resourceInfo != nil {
			resourceInfos = append(resourceInfos, resourceInfo)
		}
	}
}

最终核心的路由构造在registerResourceHandlers方法内,通过上一步骤构造的 REST Storage 判断该资源可以执行哪些操作(如 create、update等),将其对应的操作存入到 action 中,每一个 action 对应一个标准的 REST 操作,如 create 对应的 action 操作为 POST、update 对应的 action 操作为PUT。

最终根据 actions 数组依次遍历,对每一个操作添加一个 handler 方法,注册到 route 中去,再将 route 注册到 webservice 中去,webservice 最终会注册到 container 中,遵循 go-restful 的设计模式。

kube-scheduler 调度到合适的 Node 上进行 Pod的创建

go 复制代码
func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework, state *framework.CycleState, pod *v1.Pod) (result ScheduleResult, err error) {
	feasibleNodes, diagnosis, err := sched.findNodesThatFitPod(ctx, fwk, state, pod)

	priorityList, err := sched.prioritizeNodes(ctx, fwk, state, pod, feasibleNodes)

	host, _, err := selectHost(priorityList, numberOfHighestScoredNodesToReport)

	return ScheduleResult{
		SuggestedHost:  host,
		EvaluatedNodes: len(feasibleNodes) + len(diagnosis.NodeToStatusMap),
		FeasibleNodes:  len(feasibleNodes),
	}, err
}
  1. 先调用 findNodesThatFitPod 过滤出符合要求的预选节点
  2. 然后调用 prioritizeNodes 为预选的节点进行打分
  3. 最后调用 selectHost 选择最合适的节点返回

kubelet 初始化

kubelet 接收一组通过各类机制提供给它的 PodSpec,确保这些 PodSpec 中描述的容器处于运行状态且健康。 kubelet 不会管理不是由 Kubernetes 创建的容器

kubelet 启动时会通过 watch 的restful请求来获取 Pod 变更的信息:

css 复制代码
$ curl -i http://{kube-api-server-ip}:{kube-api-server-port}/api/v1/watch/pods
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 02 Jan 2020 20:22:59 GMT
Transfer-Encoding: chunked

{"type":"ADDED", "object":{"kind":"Pod","apiVersion":"v1",...}}
{"type":"ADDED", "object":{"kind":"Pod","apiVersion":"v1",...}}
{"type":"MODIFIED", "object":{"kind":"Pod","apiVersion":"v1",...}}

kubelet 通过 list 和 watch 监听API server信息来源,只获取需要自己对应节点创建的Pod,将信息写入到 updates 中等待处理

go 复制代码
// 构造 kubelet
func NewMainKubelet(
	// ...
){
	if kubeDeps.PodConfig == nil {
		kubeDeps.PodConfig, err = makePodSourceConfig(kubeCfg, kubeDeps, nodeName, nodeHasSynced)
	}
}

// 初始化Pod的配置
func makePodSourceConfig(kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *Dependencies, nodeName types.NodeName, nodeHasSynced func() bool) (*config.PodConfig, error) {
	if kubeDeps.KubeClient != nil {
		config.NewSourceApiserver(kubeDeps.KubeClient, nodeName, nodeHasSynced, cfg.Channel(ctx, kubetypes.ApiserverSource))
	}
	return cfg, nil
}

func NewSourceApiserver(c clientset.Interface, nodeName types.NodeName, nodeHasSynced func() bool, updates chan<- interface{}) {
	// 接口监听API server,只获取跟Node节点匹配的Pod
	lw := cache.NewListWatchFromClient(c.CoreV1().RESTClient(), "pods", metav1.NamespaceAll, fields.OneTermEqualSelector("spec.nodeName", string(nodeName)))

	go func() 
		newSourceApiserverFromLW(lw, updates)
	}()
}

// 从APIserver获取Pod的变更发送给updates
func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- interface{}) {
	send := func(objs []interface{}) {
		var pods []*v1.Pod
		for _, o := range objs {
			pods = append(pods, o.(*v1.Pod))
		}
		updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.ApiserverSource}
	}
	r := cache.NewReflector(lw, &v1.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc), 0)
	go r.Run(wait.NeverStop)
}

这里转换成 Channel 后会对监听到的Pod事件进行合并

go 复制代码
// 返回一个包装过的 channel 结构,interface 里面一次会包含多个Pod的信息
func (c *PodConfig) Channel(ctx context.Context, source string) chan<- interface{} {
	return c.mux.ChannelWithContext(ctx, source)
}

func (m *mux) ChannelWithContext(ctx context.Context, source string) chan interface{} {
	// 如果channel 已经创建过了,那么直接返回原来已经有的channel极客
	m.sourceLock.Lock()
	defer m.sourceLock.Unlock()
	channel, exists := m.sources[source]
	if exists {
		return channel
	}

	// 新建channel 用于监听
	newChannel := make(chan interface{})
	m.sources[source] = newChannel
	go wait.Until(func() { m.listen(source, newChannel) }, 0, ctx.Done())
	return newChannel
}

// 合并监听到的信号量
func (m *mux) listen(source string, listenChannel <-chan interface{}) {
	for update := range listenChannel {
		// 这里 merger的实现是 podStorage
		m.merger.Merge(source, update)
	}
}

func (s *podStorage) Merge(source string, change interface{}) error {

	seenBefore := s.sourcesSeen.Has(source)
	adds, updates, deletes, removes, reconciles := s.merge(source, change)
	firstSet := !seenBefore && s.sourcesSeen.Has(source)

	// deliver update notifications
	switch s.mode {
	case PodConfigNotificationIncremental:
		if len(removes.Pods) > 0 {
			s.updates <- *removes
		}
		if len(adds.Pods) > 0 {
			s.updates <- *adds
		}
		if len(updates.Pods) > 0 {
			s.updates <- *updates
		}
		if len(deletes.Pods) > 0 {
			s.updates <- *deletes
		}
		if firstSet && len(adds.Pods) == 0 && len(updates.Pods) == 0 && len(deletes.Pods) == 0 {
			// Send an empty update when first seeing the source and there are
			// no ADD or UPDATE or DELETE pods from the source. This signals kubelet that
			// the source is ready.
			s.updates <- *adds
		}
		// Only add reconcile support here, because kubelet doesn't support Snapshot update now.
		if len(reconciles.Pods) > 0 {
			s.updates <- *reconciles
		}

	case PodConfigNotificationSnapshotAndUpdates:
		if len(removes.Pods) > 0 || len(adds.Pods) > 0 || firstSet {
			s.updates <- kubetypes.PodUpdate{Pods: s.mergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source}
		}
		if len(updates.Pods) > 0 {
			s.updates <- *updates
		}
		if len(deletes.Pods) > 0 {
			s.updates <- *deletes
		}

	case PodConfigNotificationSnapshot:
		if len(updates.Pods) > 0 || len(deletes.Pods) > 0 || len(adds.Pods) > 0 || len(removes.Pods) > 0 || firstSet {
			s.updates <- kubetypes.PodUpdate{Pods: s.mergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source}
		}

	case PodConfigNotificationUnknown:
		fallthrough
	default:
		panic(fmt.Sprintf("unsupported PodConfigNotificationMode: %#v", s.mode))
	}

	return nil
}

Kubelet 监听 updates 的信息,并进行Pod状态的同步

go 复制代码
func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencies, runOnce bool) error {
	podCfg := kubeDeps.PodConfig

	// 开始监听处理Pod的信息
	if runOnce {
		if _, err := k.RunOnce(podCfg.Updates()); err != nil {}
	} else {
		startKubelet(k, podCfg, &kubeServer.KubeletConfiguration, kubeDeps, kubeServer.EnableServer)
	}
}

func startKubelet(k kubelet.Bootstrap, podCfg *config.PodConfig, kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *kubelet.Dependencies, enableServer bool) {
	// 开始监听Pod配置的变化
	go k.Run(podCfg.Updates())
}

初始化总体流程如下图:

api-server 接收来自客户端的 restful 请求创建Pod资源,这个时候 api-server 会根据客户端请求初始化Pod的字段信息,将其存入到 ETCD 中。

Kubelet 运行所有维护Pod的组件

scss 复制代码
func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
	// Start the cloud provider sync manager
	if kl.cloudResourceSyncManager != nil {
		go kl.cloudResourceSyncManager.Run(wait.NeverStop)
	}

	if err := kl.initializeModules(); err != nil {}

	// Start volume manager
	go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)

	// 启动协程进行状态更新
	if kl.kubeClient != nil {
		go wait.JitterUntil(kl.syncNodeStatus, kl.nodeStatusUpdateFrequency, 0.04, true, wait.NeverStop)
		go kl.fastStatusUpdateOnce()

		// start syncing lease
		go kl.nodeLeaseController.Run(context.Background())
	}
	go wait.Until(kl.updateRuntimeUp, 5*time.Second, wait.NeverStop)

	// Set up iptables util rules
	if kl.makeIPTablesUtilChains {
		kl.initNetworkUtil()
	}

	// Start component sync loops.
	kl.statusManager.Start()

	// Start syncing RuntimeClasses if enabled.
	if kl.runtimeClassManager != nil {
		kl.runtimeClassManager.Start(wait.NeverStop)
	}

	// Start the pod lifecycle event generator.
	kl.pleg.Start()

	// Start eventedPLEG only if EventedPLEG feature gate is enabled.
	if utilfeature.DefaultFeatureGate.Enabled(features.EventedPLEG) {
		kl.eventedPleg.Start()
	}
	
	// 启动循环
	kl.syncLoop(ctx, updates, kl)
}

func (kl *Kubelet) initializeModules() error {
	// Prometheus metrics.
	metrics.Register(
		collectors.NewVolumeStatsCollector(kl),
		collectors.NewLogMetricsCollector(kl.StatsProvider.ListPodStats),
	)
	metrics.SetNodeName(kl.nodeName)
	servermetrics.Register()

	// Setup filesystem directories.
	if err := kl.setupDataDirs(); err != nil {}
	// 创建日志文件夹
	if _, err := os.Stat(ContainerLogsDir); err != nil {
		if err := kl.os.MkdirAll(ContainerLogsDir, 0755); err != nil {
			return fmt.Errorf("failed to create directory %q: %v", ContainerLogsDir, err)
		}
	}

	// Start the image manager.
	kl.imageManager.Start()

	// Start the certificate manager if it was enabled.
	if kl.serverCertificateManager != nil {
		kl.serverCertificateManager.Start()
	}

	// Start out of memory watcher.
	if kl.oomWatcher != nil {
		if err := kl.oomWatcher.Start(kl.nodeRef); err != nil {
			return fmt.Errorf("failed to start OOM watcher: %w", err)
		}
	}

	// Start resource analyzer
	kl.resourceAnalyzer.Start()

	return nil
}

可以看到主要启动了以下组件

cloudResourceSyncManager

用于定时获取节点的地址信息并进行广播。

metrics

初始化了 Prometheus 的监控指标

imageManager

启动了镜像管理,用于对镜像进行GC

serverCertificateManager

对证书进行管理

VolumeManager

观察正在运行的 pod 集合,并且负责挂载和卸载这些Pod的卷

oomWatcher

Go判断物理资源,封装一个包用于控制整体速率。

www.kernel.org/doc/Documen... 内核文件信息详解

通过 /dev/kmsg 的内核信息,监听了oom的事件,然后将事件解析后传递给 kubeletEventRecorder 进行相应处理

record.EventRecorder 事件接收

oomWatcher 将事件通过 Eventf 方法写入 boardcastincoming *chan 中*

record.Boradcaster 广播处理事件

css 复制代码
func makeEventRecorder(ctx context.Context, kubeDeps *kubelet.Dependencies, nodeName types.NodeName) {
	if kubeDeps.Recorder != nil {
		return
	}
	eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx))
	kubeDeps.Recorder = eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: componentKubelet, Host: string(nodeName)})
	eventBroadcaster.StartStructuredLogging(3)
	if kubeDeps.EventClient != nil {
		klog.V(4).InfoS("Sending events to api server")
		eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeDeps.EventClient.Events("")})
	} else {
		klog.InfoS("No api server defined - no events will be sent to API server")
	}
}

StartStructuredLoggingStartRecordingToSink 内部通过 loop 循环将事件读出后通过传入的 eventHandler 进行处理。

resourceAnalyzer

提供节点资源消耗统计信息。

pleg.PodLifecycleEventGenerator

主要用于观察容器运行时状态并通知 kubelet 容器状态的变化,通知 podWorkers 协调 Pod 的状态。例如容器停止了需要清理并重新启动。

statusManager

管理 kubelet 所有Pod的状态,并且通过 client-go 将状态同步给 apiserver

kubelet 真正创建及维护 Pod 的资源

syncLoop Pod 变更的循环

kubelet 将所有组件都启动后就启动 syncLoop 循环进行事件监听,真正将监听到的事件进行消费

go 复制代码
func (kl *Kubelet) syncLoop(ctx context.Context, updates <-chan kubetypes.PodUpdate, handler SyncHandler) {
	// 每一秒钟唤醒一次同步,因为同步周期默认是10s
	syncTicker := time.NewTicker(time.Second)
	defer syncTicker.Stop()

	// 全局清理任务的时间
	housekeepingTicker := time.NewTicker(housekeepingPeriod)

	plegCh := kl.pleg.Watch()
	for {
		// 真正执行同步循环
		if !kl.syncLoopIteration(ctx, updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) {
			break
		}
	}
}

syncLoopInteration 每一个Pod变更事件的迭代

go 复制代码
func (kl *Kubelet) syncLoopIteration(ctx context.Context, configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
	syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
	select {
	case u, open := <-configCh:
		// 根据时间类型调用 SyncHandler 处理Pod更新的各种任务
	case e := <-plegCh:
		// 监听Pod的生命周期,调用 SyncHandler定期清理Pod
	case <-syncCh:
		// 调用 SyncHandler 同步Pod
	case update := <-kl.livenessManager.Updates():
	case update := <-kl.readinessManager.Updates():
	case update := <-kl.startupManager.Updates():
		kl.statusManager.SetContainerStartup(update.PodUID, update.ContainerID, started)
	case <-housekeepingCh:
		if !kl.sourcesReady.AllReady() {
			// 如果没有所有资源都 Ready则跳过清理任务,避免清理了还未准备好的资源
		} else {
			// 进行资源清理
		}
	}
	return true
}

syncLoopInteration 会根据监听到的 chan 信号执行各种不同的逻辑,主要包含了

  • Pod 资源的创建和更新:这里会通过调用 CRI 插件来实现
  • Pod 状态的维护,通过 statusManager 进行管理
  • 资源的清理

SyncHandler 真正执行同步逻辑的方法

Pod同步处理的的接口定义如下:

scss 复制代码
type SyncHandler interface {
	HandlePodAdditions(pods []*v1.Pod)
	HandlePodUpdates(pods []*v1.Pod)
	HandlePodRemoves(pods []*v1.Pod)
	HandlePodReconcile(pods []*v1.Pod)
	HandlePodSyncs(pods []*v1.Pod)
	HandlePodCleanups(ctx context.Context) error
}

这里原先具体实现是 Kubelet ,传入参数后将具体类型转化成接口类型 SyncHandler ,好处是 Kubelet 本身过于复杂, syncLoopInteration 只需要关心维护Pod的相关方法,所以内部通过接口的定义只让它看到这几个 Pod 维护状态的方法,避免信息过多导致无法确定自己要如何调用。

Kubelet.HandlePodAdditions

  1. 将Pod存入到 podManager 内存中
  2. 判断 Pod 是否能在该节点上创建,如磁盘空间是否足够,如果不行则拒绝
  3. 给每个 Pod创建一个podWorkLoop的协程
  4. 成功创建Pod之后增加探针的监听,来维护Pod的状态

kubelet 通过 SyncPod 来进行Pod资源的维护。

先将Pod加入到探测列表中,并开启协程进行探测

scss 复制代码
func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (isTerminal bool, err error){
	kl.probeManager.AddPod(pod)
}

可以看到 Kubelet 通过 kubeGenericRuntimeManager.createSandbox 来创建 Pod 的沙箱。

其中内部的逻辑会通过 GRPC 调用 CRI 插件进行资源的创建,如命名空间的创建、镜像资源的拉取和容器编排等。

快速回顾

到这里我们就了解了一个 Pod 在 Kubernetes 中创建的整体流程:

  1. apiserver 接收所有创建 Pod的请求
  2. kube-scheduler 将创建 Pod 的请求分配给合适的节点,将信息 NodeName 写入到 apiserver
  3. kubelet 通过 client-golistwatch 来监听自己对应 NodeName 的Pod变化,然后写入到channel等待处理。这里NodeName的匹配是由 kube-scheduler 来进行分配。
  4. 启动 Pod 事件的监听循环,来处理 Pod 变更事件的写入,并将同步完的 Pod 状态重新写回到 apiserver 中。
  5. 等到进入到 SyncPod 后就可以将容器的创建交给 CRI 来实现, kubelet 专注于维护 Pod 的可用状态以及是否能够正常提供服务。
相关推荐
小_太_阳15 分钟前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾18 分钟前
scala借阅图书保存记录(三)
开发语言·后端·scala
星就前端叭1 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
小林coding2 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者2 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu
从善若水2 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust
机器之心3 小时前
终于等来能塞进手机的文生图模型!十分之一体量,SnapGen实现百分百的效果
人工智能·后端
机器之心3 小时前
首次!大模型自动搜索人工生命,做出AI科学家的Sakana AI又放大招
人工智能·后端
花晓木3 小时前
k8s etcd 数据损坏处理方式
容器·kubernetes·etcd
运维&陈同学3 小时前
【模块一】kubernetes容器编排进阶实战之基于velero及minio实现etcd数据备份与恢复
数据库·后端·云原生·容器·kubernetes·etcd·minio·velero