OpenKruise: Kruise-daemon

303 阅读4分钟

Kruise-daemon 是运行在每个 node 上的 pod,是一些功能实现的底层逻辑

系列教程: Openkruise

目录: pkg/daemon

  1. Container-Recreate

上层逻辑 ContainerRestart

调用层级

Run 
 |- processNextWorkItem
   |- sync // 只有当 crr 处于 active 状态的时候才会进入下一步,这里也会等待 UnreadyGracePeriodSeconds 参数
     |- manage // do re-create
  1. 通过 crr 拿到 pod: pod := convertCRRToPod(crr)

  2. 再通过函数 runtimeManager.GetPodStatus(pod.UID, pod.Name, pod.Namespace)拿到 pod 里每个 container 的状态 podStatus

  3. 根据 podStatus 调用函数 getCurrentCRRContainersRecreateStates(crr, podStatus) 生成 newCRRContainerRecreateStates

  4. 遍历 newCRRContainerRecreateStates 里每一个元素的 Phase

    1. 如果 phase == Success,意味着重建成功,则只是把 completedCount++

    2. 如果 phase == Failed,意味着重建失败,此时要根据失败策略(crr.Spec.Strategy.FailurePolicy)进行选择

      1. 如果失败策略为 Ignore,意味着忽略失败,那么直接继续处理其他的 status
      2. 如果失败策略不为 Ignore,则直接报错
    3. 如果 phase == Recreating,意味着该 container 正在重建,此时需要根据参数 crr.Spec.Strategy.OrderedRecreate 来调整并发度

      1. 如果 OrderedRecreate == true, 意味着需要按序进行,则直接跳出当前循环处理当下的 container
      2. 如果 OrderedRecreate == false,意味着可以批处理,继续循环处理即可
    4. 如果以上状态都不是,那么就 kill container: runtimeManager.KillContainer(pod, kubeContainerStatus.ID, state.Name, msg, nil),然后将 container 状态设置为 Recreating

      1. killContainer 位于 pkg/daemon/kuberuntime/kuberuntime_container.go

        整体步骤分为两步

        1. 如果可以的话尝试运行 pre-stop lifecycle hooks
        2. Stop container
      2. 根据 pod.DeletionGracePeriodSeconds 或 pod.Spec.TerminationGracePeriodSeconds 设置优雅退出时间 gracePeriod,其中 DeletionGracePeriodSeconds 具有更高的优先级;另外优雅退出时间的最小值为 minimumGracePeriodInSeconds(2), 这是代码里写死的
      3. 通过调用 m.runtimeService.StopContainer(containerID.ID, gracePeriod)触发 container 的重建,该函数是 kubelet 的功能,位于 k8s.io/kubernetes/pkg/kubelet/cri/remote/remote_runtime.go
  1. ImagePull (NodeImage)

pkg/daemon/imagepuller

openkruise.io/zh/docs/use…

用于在指定 node 去拉镜像

Controller

Controller 主要用于管理 NodeImage,核心逻辑都在 sync 函数里面

  1. 调用 c.puller.Sync(nodeImage.DeepCopy(), ref) 来调用访问 worker 的 sync 函数以实现 image pull
  2. 刷新 NodeImage 的 status 来反应当前 job 的状态

Worker

调度逻辑

调度的主要逻辑都在 sync 里,所以下面详述 sync 的逻辑

  1. reset workerPools(map[string]workerPool): 把不在 spec 里的 work 停止并删除; 把 pools 里没有的worker 创建并加入; 最终使得 workerPools 里每个元素都与 NodeImage.Spec.Images 一一对应

  2. 对每个 worker(wokerPools[imageName]) 调用 pool.Sync(&imageSpec, imageStatus, ref) 函数

    1. 遍历 NodeImage.Spec.Tags 字段(用于管理多版本的 image),并检查 status 中是否存在对应的 tag,并根据 status 的结果来将 tags 分为 allTags 和 activeTags
    2. apiVersion: apps.kruise.io/v1alpha1
      kind: NodeImage
      metadata:
        name: my-app-image
      spec:
        image: my-registry/my-app
        tags:
          - v1.0
          - v1.1
          - latest
      
    3. 清理 workerpool 中 non-active | version 不对的 worker: delete(w.pullWorkers, tag), 这里 w 就是上一层函数的 pool
    4. 对于 activeTags 中存在但 worker pool 里找不到的对象,起新的 worker newPullWorker(w.name, tagSpec, secrets, w.runtime, w, ref, w.eventRecorder)
    5. func newPullWorker(name string, tagSpec appsv1alpha1.ImageTagSpec, secrets []v1.Secret, runtime runtimeimage.ImageService, statusUpdater imageStatusUpdater, ref *v1.ObjectReference, eventRecorder record.EventRecorder) *pullWorker {
          o := &pullWorker{
              name:          name,
              tagSpec:       tagSpec,
              secrets:       secrets,
              runtime:       runtime,
              statusUpdater: statusUpdater,
              ref:           ref,
              eventRecorder: eventRecorder,
              active:        true,
              stopCh:        make(chan struct{}),
          }
          go o.Run()
          return o
      }
      

工作逻辑

这里介绍 pullWorker Run 函数的逻辑

  1. 参数初始化阶段

    1. timeout = tagSpec.PullPolicy.TimeoutSeconds, 默认值 10min
    2. backoffLimit = tagSpec.PullPolicy.BackoffLimit, 默认值 3(最大重试次数)
    3. deadline = now + tagSpec.PullPolicy.ActiveDeadlineSeconds, 默认值为 nil (不设上限)
  2. 会根据上述三个参数来决定是否使任务失败,如果在最大重试次数之内且单次任务没超时且没超过deadline,那么会调用函数 doPullImage(pullContext, newStatus)

    1. 调用 getImageInfo(ctx)拿到 image 信息,image 信息如下所示

    2. type ImageInfo struct {
          // ID of an image.
          ID string `json:"Id,omitempty"`
          // repository with digest.
          RepoDigests []string `json:"RepoDigests"`
          // repository with tag.
          RepoTags []string `json:"RepoTags"`
          // size of image's taking disk space.
          Size int64 `json:"Size,omitempty"`
      }
      
    3. w.runtime.PullImage(ctx, w.name, tag, w.secrets) 拉镜像

      1. 该函数位于 pkg/daemon/criruntime/containerd.go

      2. imageRef = imageName:tag (e.g.: my-registry/my-app:v1.0)

      3. 规范化镜像名 namedRef = daemonutil.NormalizeImageRef(imageRef)

      4. 获取镜像解析器 resolver, isSchema1, err := d.getResolver(ctx, namedRef, pullSecrets),其返回值分别为镜像解析器和是否为 Docker Schema 1 格式的镜像还有错误信息

        1. 尝试用传进来的 pullSecrets 解析镜像,如果解析失败则进入下一步
        2. 尝试用 containerdImageClient 的 accountManager 里记录的默认账户信息或匿名账户信息解析镜像
      5. d.doPullImage(ctx, namedRef, isSchema1, resolver)

        1. 创建一个 newFeatchJobs 实例 ongoing 用于跟踪当前的拉取任务

        2. 创建 pipeRpipeW, 分别代表管道读取器和管道写入器,pipeR 用于返回 ImagePullStatusReader

        3. 创建一个基于 json 的 stream 流,用于将 pullimage 进度信息写入 pipeW

        4. 使用containerd.RemoteOpt选项配置了拉取镜像的参数。其中包括使用Schema1转换、指定解析器、解压缩拉取的镜像、指定快照存储器以及设置镜像处理程序。

        5. 起一个协程调用 fetchProgress函数使得 ongoing 的内容能够写入 stream

        6. 起一个协程拉镜像

          1. 首先调用了 img, err := d.client.Pull(pctx, ref.String(), opts...) 拉镜像,其中ops 就是步骤 4 里的参数

            1. 这里的 pull 函数是 k8s 原生的,用于将提供的内容(通常是镜像)下载到 container 的内容存储中
          2. 调用 createRepoDigestRecord 更新 container 中的摘要信息(Digest)

    4. Select 等待上述函数的信号,根据返回值判断任务执行情况并更新 status

  3. 根据 doPullImage 的结果来处理失败或者正常结束