kubelet主逻辑源码分析

本文基于kubernetes源码1.27整理

kubelet介绍

引用官方文档的kubelet的简介。

kubelet 是在每个节点上运行的主要 “节点代理”。

kubelet 是基于 PodSpec 来工作的。每个 PodSpec 是一个描述 Pod 的 YAML 或 JSON 对象。

1 kubelet 接受通过各种机制(主要是通过 apiserver)提供的一组 PodSpec。

2 确保这些 PodSpec 中描述的容器处于运行状态且运行状况良好。

kubelet启动过程分析

笔者主要针对以上两点去阅读kubelet的源码。关于k8s源码的目录结构和cobra命令行框架介绍,本文不介绍。

众所周知k8s组件的启动入口都在cmd目录下,kubelet也是。先分析kubelet的启动过程.

在main方法中进入NewKubeletCommand方法,

func NewKubeletCommand() *cobra.Command {

    cmd := &cobra.Command{

    Use: componentKubelet,

    Long: `The kubelet is the primary "node agent" that runs on each

    DisableFlagParsing: true,

    SilenceUsage:       true,

    RunE: func(cmd *cobra.Command, args []string) error {
……
}
……

在RunE方法的结尾,进入kubelet的Run方法。忽略掉非主干代码,启动流程如下图。

kubelet的pod变更事件来源分析

Kubelet的主要职责接受事件,操作容器。

获取变更事件的所有channel

在简介中的第一条-kubelet 接受通过各种机制(主要是通过 apiserver)提供的一组 PodSpec,是通过apiserver/http/file的事件来源。

第二条-确保这些 PodSpec 中描述的容器处于运行状态且运行状况良好,是通过PLEG的事件来源。(PLEG=pod lifecycle event generate)

这两方面事件来源是通过两个channel实现的。分析一下两个channel的源码。

在syncLoopIteration方法中可以看到,事件的几个主要来源。(主要分析前两个channel)

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://重点关注对象
	case e := <-plegCh://重点关注对象
	case <-syncCh:
	case update := <-kl.livenessManager.Updates():
	case update := <-kl.readinessManager.Updates():
	case update := <-kl.startupManager.Updates():
	case <-housekeepingCh:
	}
	return true
}

本方法消费channel中数据。

configCh和plegCh两个channel的写入来源总体源码流程如下(后续分析细致的变化来源):

图中configCh的来源以apiserver为例

 apiserver/http/file的podspec变更来源的通道configCh

单向configCh的channel产生的位置。根据源码cmd/kubelet/app/server.go startkubelet方法中go k.Run(podCfg.Updates())

寻找podcfg初始化的地方。

三种configsource的初始化源码如下,最后一个参数就是事件的变化通道。

	// define file config source
	if kubeCfg.StaticPodPath != "" {
		klog.InfoS("Adding static pod path", "path", kubeCfg.StaticPodPath)
		config.NewSourceFile(kubeCfg.StaticPodPath, nodeName, kubeCfg.FileCheckFrequency.Duration, cfg.Channel(ctx, kubetypes.FileSource))
	}

	// define url config source
	if kubeCfg.StaticPodURL != "" {
		klog.InfoS("Adding pod URL with HTTP header", "URL", kubeCfg.StaticPodURL, "header", manifestURLHeader)
		config.NewSourceURL(kubeCfg.StaticPodURL, manifestURLHeader, nodeName, kubeCfg.HTTPCheckFrequency.Duration, cfg.Channel(ctx, kubetypes.HTTPSource))
	}

	if kubeDeps.KubeClient != nil {
		klog.InfoS("Adding apiserver pod source")
		config.NewSourceApiserver(kubeDeps.KubeClient, nodeName, nodeHasSynced, cfg.Channel(ctx, kubetypes.ApiserverSource))
	}

通道的写入方代码,以第三个apiserver为例:

NewSourceApiserver方法代码中可以看到熟悉的listAndWatch,并没有使用informer机制。

lw := cache.NewListWatchFromClient(c.CoreV1().RESTClient(), "pods", metav1.NamespaceAll, fields.OneTermEqualSelector("spec.nodeName", string(nodeName)))
	klog.InfoS("Watching apiserver")
	newSourceApiserverFromLW(lw, updates)
}()

newSourceApiserverFromLW方法代码中可以找到channel的写入。

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)
}

获取到来源还需要处理,从config.NewSourceApiserver(kubeDeps.KubeClient, nodeName, nodeHasSynced, cfg.Channel(ctx, kubetypes.ApiserverSource)) 开始,最后一个参数cfg.Channel(ctx, kubetypes.ApiserverSource),跟踪代码,

 func (m *Mux) ChannelWithContext(ctx context.Context, source string) chan interface{} {


  newChannel := make(chan interface{})


  m.sources[source] = newChannel


  go wait.Until(func() { m.listen(source, newChannel) }, 0, ctx.Done())


  return newChannel


 }

进入方法m.listen

func (m *Mux) listen(source string, listenChannel <-chan interface{}) {

  for update := range listenChannel {

   m.merger.Merge(source, update)

  }

 }

下面就是根据读取的内容,处理后写入channel,找到写入来源。

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

  adds, updates, deletes, removes, reconciles := s.merge(source, change)

         s.updates <- *removes 

  return nil

 }

PLEG的变更事件来源channel

从syncLoopIteration方法追踪plegCh的channel写入

在启动过程的第四步看到看到方法kl.pleg.Start(),在此方法中运行重要线程Relist。

 func (g *GenericPLEG) Start() {

      g.runningMu.Lock()

      defer g.runningMu.Unlock()

      if !g.isRunning {

       g.isRunning = true

       g.stopCh = make(chan struct{})

       go wait.Until(g.Relist, g.relistDuration.RelistPeriod, g.stopCh)

  }
 }

Relist的作用是获取所有的Pod列表,并将其转换为kubecontainer.Pods类型,更新podRecords中的当前Pod列表,比较旧的Pod和当前Pod,并生成相应的事件,并写入通道。以下代码是relist方法中的一部分。

 select {

  case g.eventChannel <- events[i]:

  default:

   metrics.PLEGDiscardEvents.Inc()

   klog.ErrorS(nil, "Event channel is full, discard this relist() cycle event")

 }

可以看到PLEG事件的写入来源源码g.eventChannel <- events[i],也已经找到。

kubelet和容器交互处理变更

我们分析了主要的事件来源,其他存活探针、就绪探针等产生的事件,有兴趣的同学可以自己摸索。

下面可以进入到和容器交互的源码上了。回到syncLoopIteration方法;

 select {

  case u, open := <-configCh:

   switch u.Op {

   case kubetypes.ADD:

    klog.V(2).InfoS("SyncLoop ADD", "source", u.Source, "pods", klog.KObjSlice(u.Pods))

    handler.HandlePodAdditions(u.Pods)

   case kubetypes.UPDATE:

   case kubetypes.REMOVE:

   case kubetypes.RECONCILE:

   case kubetypes.DELETE:

   case kubetypes.SET:

   }

追踪ADD事件代码,从HandlePodAdditions往下的流程如下:

由于kubernetes已经把docker代码移除,管理pod的主干代码就分析到这里、后续是grpc调用cri api的流程,不在分析范围。

总结

        简单阅读了kubelet的启动源码后,我们从重点方法syncLoopIteration开始,按照kubelet的定义,忽略了存活探针和就绪探针等产生的事件。分析了configCh和plegCh两个channel的写入源头,最后搞清kubelet如何同步变化到容器,理清kubelet的主逻辑。

  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值