前文说到Kubelet启动时,调用到kubelet.Run方法,里面最核心的就是调用到kubelet.syncLoop。它是一个循环,这个循环里面有若干个检查和同步操作,其中一个是地在监听Pod的增删改事件,当一个Pod被Scheduler调度到某个Node之后,就会触发到kubelet.syncLoop里面的事件,经过一系列的操作,最后达到Pod正常跑起来。

kubelet.syncLoop

  1. kubelet.syncLoop /pkg/kubelet/kubelet.go
  2. |--kl.syncLoopIteration(updates, handler, syncTicker.C, housekeepingTicker.C, plegCh)
  3. |--u, open := <-configCh
  4. |--handler.HandlePodAdditions(u.Pods)即Kubelet.HandlePodAdditions
  5. |--sort.Sort(sliceutils.PodsByCreationTime(pods))
  6. |--kl.handleMirrorPod(pod, start)
  7. |--kl.dispatchWork
  8. |--kl.dispatchWork(pod, kubetypes.SyncPodCreate, mirrorPod, start)
  9. |--kl.podWorkers.UpdatePodpodWorkers.UpdatePod /pkg/kubelet/pod_worker.go
  10. |--p.managePodLoop
  11. |--p.syncPodFn

syncLoop

即使没有需要更新的 pod 配置,kubelet 也会定时去做同步和清理 pod 的工作。然后在 for 循环中一直调用 syncLoopIteration,如果在每次循环过程中出现比较严重的错误,kubelet 会记录到 runtimeState 中,遇到错误就等待 5 秒中继续循环。

  1. func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler) {
  2. // syncTicker 每秒检测一次是否有需要同步的 pod workers
  3. syncTicker := time.NewTicker(time.Second)
  4. defer syncTicker.Stop()
  5. // 每两秒检测一次是否有需要清理的 pod
  6. housekeepingTicker := time.NewTicker(housekeepingPeriod)
  7. defer housekeepingTicker.Stop()
  8. // pod 的生命周期变化
  9. plegCh := kl.pleg.Watch()
  10. ...
  11. for {
  12. if err := kl.runtimeState.runtimeErrors(); err != nil {
  13. klog.Errorf("skipping pod synchronization - %v", err)
  14. // exponential backoff
  15. time.Sleep(duration)
  16. duration = time.Duration(math.Min(float64(max), factor*float64(duration)))
  17. continue
  18. }
  19. // reset backoff if we have a success
  20. duration = base
  21. ...
  22. if !kl.syncLoopIteration(updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) {
  23. break
  24. }
  25. ...
  26. }
  27. ...
  28. }

syncLoopIteration

syncLoopIteration 这个方法就会对多个管道进行遍历,发现任何一个管道有消息就交给 handler 去处理。对于pod创建相关的就是configCh,它会传递来自3个来源(file,http,apiserver)的pod的变化(增,删,改)。其他相关管道还有没1秒同步一次pod的syncCh,每1秒检查一下是否需要清理pod的housekeepingCh 等等。

  1. func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
  2. syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
  3. select {
  4. case u, open := <-configCh: //三个来源的更新事件
  5. ....
  6. switch u.Op {
  7. case kubetypes.ADD:
  8. klog.V(2).Infof("SyncLoop (ADD, %q): %q", u.Source, format.Pods(u.Pods))
  9. // After restarting, kubelet will get all existing pods through
  10. // ADD as if they are new pods. These pods will then go through the
  11. // admission process and *may* be rejected. This can be resolved
  12. // once we have checkpointing.
  13. handler.HandlePodAdditions(u.Pods)
  14. .....
  15. }
  16. case <-syncCh: //定时器1秒一次,说是sync
  17. ....
  18. case update := <-kl.livenessManager.Updates(): ///存活检查
  19. ....
  20. case <-housekeepingCh: //定时器2秒一次,清理的 pod
  21. }

HandlePodAddtions 处理pod的新增事件

  1. func (kl *Kubelet) HandlePodAdditions(pods []*v1.Pod) {
  2. sort.Sort(sliceutils.PodsByCreationTime(pods)) //将pods按照创建日期排列,保证最先创建的 pod 会最先被处理
  3. for _, pod := range pods {
  4. // 把 pod 加入到 podManager 中。statusManager,volumeManager,runtimeManager都依赖于这个podManager
  5. kl.podManager.AddPod(pod)
  6. //处理静态pod,实际上内部同样是调用了kl.dispatchWork,这里主要跳过了拒绝掉pod的判断
  7. if kubetypes.IsMirrorPod(pod) {
  8. kl.handleMirrorPod(pod, start)
  9. continue
  10. }
  11. if !kl.podIsTerminated(pod) {
  12. // Only go through the admission process if the pod is not
  13. // terminated.
  14. // We failed pods that we rejected, so activePods include all admitted
  15. // pods that are alive.
  16. activePods := kl.filterOutTerminatedPods(existingPods)
  17. ////验证 pod 是否能在该节点运行,如果不可以直接拒绝;
  18. // Check if we can admit the pod; if not, reject it.
  19. if ok, reason, message := kl.canAdmitPod(activePods, pod); !ok {
  20. kl.rejectPod(pod, reason, message)
  21. continue
  22. }
  23. }
  24. ....
  25. kl.dispatchWork(pod, kubetypes.SyncPodCreate, mirrorPod, start)
  26. .....
  27. }
  28. }

UpdatePod

此处调用managePodLoop通过一个协程去执行,通过一个podUpdates的map标记是否有创建过协程,然后通过working这个map标记是否有运行,没有运行的往通道里面传递,让managePodLoop得以执行

  1. func (p *podWorkers) UpdatePod(options *UpdatePodOptions) {
  2. var podUpdates chan UpdatePodOptions
  3. if podUpdates, exists = p.podUpdates[uid]; !exists {
  4. p.podUpdates[uid] = podUpdates
  5. go func() {
  6. defer runtime.HandleCrash()
  7. p.managePodLoop(podUpdates)
  8. }()
  9. }
  10. if !p.isWorking[pod.UID] {
  11. p.isWorking[pod.UID] = true
  12. podUpdates <- *options
  13. } else {
  14. ...
  15. }
  16. ....
  17. }

managePodLoop

到达syncPodFn方法调用,他是podWorkers的一个字段,在构造podWorkers的时候传入,实际就是kubelet.syncPod方法

  1. func (p *podWorkers) managePodLoop(podUpdates <-chan UpdatePodOptions) {
  2. ...
  3. err = p.syncPodFn(syncPodOptions{
  4. mirrorPod: update.MirrorPod,
  5. pod: update.Pod,
  6. podStatus: status,
  7. killPodOptions: update.KillPodOptions,
  8. updateType: update.UpdateType,
  9. })
  10. ...
  11. }

Pod sync(Kubelet.syncPod)

1 如果是 pod 创建事件,会记录一些 pod latency 相关的 metrics;

2 生成一个 v1.PodStatus 对象,Pod的状态包括这些 Pending Running Succeeded Failed Unknown

3 PodStatus 生成之后,将发送给 Pod status manager

4 运行一系列 admission handlers,确保 pod 有正确的安全权限

5 kubelet 将为这个 pod 创建 cgroups。

6 创建容器目录 /var/run/kubelet/pods/podid volume $poddir/volumes plugins $poddir/plugins

7 volume manager 将 等待volumes attach 完成

8 从 apiserver 获取 Spec.ImagePullSecrets 中指定的 secrets,注入容器

9 容器运行时(runtime)创建容器

由于代码篇幅较长,这里就只粘出关键的方法或函数调用,代码位于/pkg/kubelet/kubelet.go

  1. func (kl *Kubelet) syncPod(o syncPodOptions) error {
  2. //1. 如果是 pod 创建事件,会记录一些 pod latency 相关的 metrics
  3. // Record pod worker start latency if being created
  4. // TODO: make pod workers record their own latencies
  5. if updateType == kubetypes.SyncPodCreate {
  6. if !firstSeenTime.IsZero() {
  7. // This is the first time we are syncing the pod. Record the latency
  8. // since kubelet first saw the pod if firstSeenTime is set.
  9. metrics.PodWorkerStartDuration.Observe(metrics.SinceInSeconds(firstSeenTime))
  10. } else {
  11. klog.V(3).Infof("First seen time not recorded for pod %q", pod.UID)
  12. }
  13. }
  14. //2. 生成一个 v1.PodStatus 对象
  15. apiPodStatus := kl.generateAPIPodStatus(pod, podStatus)
  16. //3.1. 生成PodStatus
  17. apiPodStatus := kl.generateAPIPodStatus(pod, podStatus)
  18. //4. 运行一系列 admission handlers,确保 pod 有正确的安全权限
  19. runnable := kl.canRunPod(pod)
  20. ....
  21. //3.2. PodStatus 生成之后,将发送给 Pod status manager
  22. kl.statusManager.SetPodStatus(pod, apiPodStatus)
  23. //5. kubelet 将为这个 pod 创建 cgroups
  24. if !kl.podIsTerminated(pod) {
  25. if !(podKilled && pod.Spec.RestartPolicy == v1.RestartPolicyNever) {
  26. if !pcm.Exists(pod) {
  27. if err := kl.containerManager.UpdateQOSCgroups(); err != nil {
  28. klog.V(2).Infof("Failed to update QoS cgroups while syncing pod: %v", err)
  29. }
  30. if err := pcm.EnsureExists(pod); err != nil {
  31. kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedToCreatePodContainer, "unable to ensure pod container exists: %v", err)
  32. return fmt.Errorf("failed to ensure that the pod: %v cgroups exist and are correctly applied: %v", pod.UID, err)
  33. }
  34. }
  35. }
  36. }
  37. //6 创建容器目录
  38. // Make data directories for the pod
  39. if err := kl.makePodDataDirs(pod); err != nil {
  40. kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedToMakePodDataDirectories, "error making pod data directories: %v", err)
  41. klog.Errorf("Unable to make pod data directories for pod %q: %v", format.Pod(pod), err)
  42. return err
  43. }
  44. // Volume manager will not mount volumes for terminated pods
  45. if !kl.podIsTerminated(pod) {
  46. //7 volume manager 将 等待volumes attach 完成
  47. //等待挂载,但是挂载不在这里执行
  48. // Wait for volumes to attach/mount
  49. if err := kl.volumeManager.WaitForAttachAndMount(pod); err != nil {
  50. kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedMountVolume, "Unable to attach or mount volumes: %v", err)
  51. klog.Errorf("Unable to attach or mount volumes for pod %q: %v; skipping pod", format.Pod(pod), err)
  52. return err
  53. }
  54. }
  55. //8 从 apiserver 获取 Spec.ImagePullSecrets 中指定的 secrets,注入容器
  56. //部分pod会有ImagePullSecrets,用于登录镜像库拉镜像
  57. // Fetch the pull secrets for the pod
  58. pullSecrets := kl.getPullSecretsForPod(pod)
  59. //9 容器运行时(runtime)创建容器
  60. // Call the container runtime's SyncPod callback
  61. result := kl.containerRuntime.SyncPod(pod, podStatus, pullSecrets, kl.backOff)
  62. }

运行时创建容器(kubeGenericRuntimeManager.SyncPod)

1 计算sandbox和container变化

2 如果sandbox变更了就要把pod kill了

3 kill掉pod中没有运行的container

4 要创建sandbox的就创建

5 创建临时容器

6 创建init容器

7 创建业务容器

代码位于/pkg/kubelet/kuberuntime/kuberuntime_manager.go

  1. func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {
  2. // Step 1: Compute sandbox and container changes.
  3. podContainerChanges := m.computePodActions(pod, podStatus)
  4. // Step 2: Kill the pod if the sandbox has changed.
  5. if podContainerChanges.KillPod {
  6. killResult := m.killPodWithSyncResult(pod, kubecontainer.ConvertPodStatusToRunningPod(m.runtimeName, podStatus), nil)
  7. } else {
  8. // Step 3: kill any running containers in this pod which are not to keep.
  9. for containerID, containerInfo := range podContainerChanges.ContainersToKill {
  10. if err := m.killContainer(pod, containerID, containerInfo.name, containerInfo.message, nil); err != nil {
  11. }
  12. }
  13. }
  14. // Step 4: Create a sandbox for the pod if necessary.
  15. podSandboxID := podContainerChanges.SandboxID
  16. if podContainerChanges.CreateSandbox {
  17. podSandboxID, msg, err = m.createPodSandbox(pod, podContainerChanges.Attempt)
  18. }
  19. // Step 5: start ephemeral containers
  20. if utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
  21. for _, idx := range podContainerChanges.EphemeralContainersToStart {
  22. start("ephemeral container", ephemeralContainerStartSpec(&pod.Spec.EphemeralContainers[idx]))
  23. }
  24. }
  25. // Step 6: start the init container.
  26. if container := podContainerChanges.NextInitContainerToStart; container != nil {
  27. // Start the next init container.
  28. if err := start("init container", containerStartSpec(container)); err != nil {
  29. return
  30. }
  31. }
  32. // Step 7: start containers in podContainerChanges.ContainersToStart.
  33. for _, idx := range podContainerChanges.ContainersToStart {
  34. start("container", containerStartSpec(&pod.Spec.Containers[idx]))
  35. }
  36. return
  37. }
创建sandbox

1 拉sandbox镜像

2 创建sandbox 容器

3 创建sandbox的checkpoint

4 启动sandbox容器,如果失败交由kubelet GC

5 hostNetwork就可以返回,否则让CNI编织网络

这个过程会涉及到几层的调用链,才会找到最终创建sandbox的代码,从kubeGenericRuntimeManager.SyncPod起

  1. m.createPodSandbox /pkg/kubelet/kuberuntime/kuberuntime_manager.go
  2. |--m.runtimeService.RunPodSandbox /pkg/kubelet/kuberuntime/kuberuntime_sandbox.go
  3. |--r.runtimeClient.RunPodSandbox runtimeService.RunPodSandbox的实现类是remoteRuntimeService /pkg/kubelet/cri/remote/remote_runtime.go
  4. |--dockerService.RunPodSandbox /pkg/kubelet/dockershim/docker_sandbox/go

dockerService.RunPodSandbox方法的简略如下

  1. func (ds *dockerService) RunPodSandbox(ctx context.Context, r *runtimeapi.RunPodSandboxRequest) (*runtimeapi.RunPodSandboxResponse, error) {
  2. // Step 1: Pull the image for the sandbox.
  3. if err := ensureSandboxImageExists(ds.client, image); err != nil {
  4. return nil, err
  5. }
  6. // Step 2: Create the sandbox container.
  7. createConfig, err := ds.makeSandboxDockerConfig(config, image)
  8. createResp, err := ds.client.CreateContainer(*createConfig)
  9. // Step 3: Create Sandbox Checkpoint.
  10. if err = ds.checkpointManager.CreateCheckpoint(createResp.ID, constructPodSandboxCheckpoint(config)); err != nil {
  11. return nil, err
  12. }
  13. // Step 4: Start the sandbox container.
  14. err = ds.client.StartContainer(createResp.ID)
  15. // Step 5: Setup networking for the sandbox.
  16. if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtimeapi.NamespaceMode_NODE {
  17. return resp, nil
  18. }
  19. err = ds.network.SetUpPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID, config.Annotations, networkOptions)
  20. }
CNI编织网路

kubelet使用 /etc/cni/net.d的配置文件启动 /opt/cni/bin 二进制的CNI 插件

CNI 插件创建veth,master到指定设备,必要是通过unix socket与daemonset里面的CNI容器获取目标pod的信息

创建临时容器、 init 容器及业务容器

1 拉镜像

2 创建容器

3 启动容器

4 执行post start hook

三种容器都是调用了kubeGenericRuntimeManager.SyncPod内定义的局部函数,只是因为容器类型不一样而入参不一样而已

在局部函数调用kubeGenericRuntimeManager.startContainer方法简略如下,代码路径/pkg/kubelet/kuberuntime/kuberuntime_container.go

  1. func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandboxConfig *runtimeapi.PodSandboxConfig, spec *startSpec, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, podIP string, podIPs []string) (string, error) {
  2. // Step 1: pull the image.
  3. imageRef, msg, err := m.imagePuller.EnsureImageExists(pod, container, pullSecrets, podSandboxConfig)
  4. // Step 2: create the container.
  5. containerID, err := m.runtimeService.CreateContainer(podSandboxID, containerConfig, podSandboxConfig)
  6. // Step 3: start the container.
  7. err = m.runtimeService.StartContainer(containerID)
  8. // Step 4: execute the post start hook.
  9. if container.Lifecycle != nil && container.Lifecycle.PostStart != nil {
  10. msg, handlerErr := m.runner.Run(kubeContainerID, pod, container, container.Lifecycle.PostStart)
  11. }
  12. }

小结

本篇从kubelet的主循环开始,讲述了pod的启动过程,包括状态更新,分配cgroup,创建容器目录,等待volume挂载,注入imagepull secret,创建sandbox,调用cni编织网络,启动临时容器,init容器,业务容器,执行postStart生命周期钩子。

参考文章

万字长文:K8s 创建 pod 时,背后到底发生了什么?

kubelet 创建 pod 的流程

Pod 的创建

kubernetes/k8s CRI分析-kubelet创建pod分析

kubelet源码分析——启动Pod的更多相关文章

  1. kubelet源码分析——关闭Pod

    上一篇说到kublet如何启动一个pod,本篇讲述如何关闭一个Pod,引用一段来自官方文档介绍pod的生命周期的话 你使用 kubectl 工具手动删除某个特定的 Pod,而该 Pod 的体面终止限期 ...

  2. kubelet源码分析——监控Pod变更

    前言 前文介绍Pod无论是启动时还是关闭时,处理是由kubelet的主循环syncLoop开始执行逻辑,而syncLoop的入参是一条传递变更Pod的通道,显然syncLoop往后的逻辑属于消费者一方 ...

  3. apiserver源码分析——启动流程

    前言 apiserver是k8s控制面的一个组件,在众多组件中唯一一个对接etcd,对外暴露http服务的形式为k8s中各种资源提供增删改查等服务.它是RESTful风格,每个资源的URI都会形如 / ...

  4. Zookeeper 源码分析-启动

    Zookeeper 源码分析-启动 博客分类: Zookeeper   本文主要介绍了zookeeper启动的过程 运行zkServer.sh start命令可以启动zookeeper.入口的main ...

  5. Symfony2源码分析——启动过程2

    文章地址:http://www.hcoding.com/?p=46 上一篇分析Symfony2框架源码,探究Symfony2如何完成一个请求的前半部分,前半部分可以理解为Symfony2框架为处理请求 ...

  6. quartz2.x源码分析——启动过程

    title: quartz2.x源码分析--启动过程 date: 2017-04-13 14:59:01 categories: quartz tags: [quartz, 源码分析] --- 先简单 ...

  7. mysql源码分析-启动过程

    mysql源码分析-启动过程 概要 # sql/mysqld.cc, 不包含psi的初始化过程 mysqld_main: // 加载my.cnf和my.cnf.d,还有命令行参数 if (load_d ...

  8. kubelet源码分析(version: git tag 1.7.6)

    一.概述 kubelet源码入口:cmd/kubelet/kubelet.go main() cmd/kubelet/app 包中的Run函数: 查看先参数,kubelet.KubeletDeps t ...

  9. Symfony2源码分析——启动过程1

    本文通过阅读分析Symfony2的源码,了解Symfony2启动过程中完成哪些工作,从阅读源码了解Symfony2框架. Symfony2的核心本质是把Request转换成Response的一个过程. ...

随机推荐

  1. 深入浅出Mybatis系列(八)---objectFactory、plugins、mappers

    1.objectFactory是干什么的? 需要配置吗? MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成.默认的对象工厂需要做的仅仅是实例化 ...

  2. 【nodejs】request 和 response 对象

    request 和 response 对象的具体介绍: Request 对象 - request 对象表示 HTTP 请求,包含了请求查询字符串,参数,内容,HTTP 头部等属性.常见属性有: req ...

  3. BootStrap学习代码

    要为毕设做准备了! 哎,毕设前台得自己来,所以打算学学bootstrap,把学习的代码放到码云上面了,使用HbuilderX来写,界面友好,适合我这种前端小白- 第一天就感受到了写html快捷键的强大 ...

  4. k8s 探针 exec多个判断条件条件 多个检测条件

    背景 1,之前我们的yaml文件里面有就绪探针. 2,探针是检测一个文件是否生成,生成了说明服务正常. 3,现在要加一个检测,也是一个文件是否存在并且不为空. 4,只有两个条件同时满足了 服务才算正常 ...

  5. Learning ROS: Running ROS across multiple machines

    Start the master ssh hal roscore Start the listener ssh hal export ROS_MASTER_URI=http://hal:11311 r ...

  6. kubebuilder实战之八:知识点小记

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  7. How to check type of files without extensions in python? 不通过文件扩展名,怎样知道文件类型?

    有一个命令 file 可以用 $ file fuck fuck.png: PNG image data, 1122 x 750, 8-bit colormap, non-interlaced pyth ...

  8. Mysql常用sql语句(10)- is null 空值查询

    测试必备的Mysql常用sql语句系列 https://www.cnblogs.com/poloyy/category/1683347.html 前言 is null是一个关键字来的,用于判断字段的值 ...

  9. GRE隧道协议

    1. GRE协议简介 GRE(General Routing Encapsulation ,通用路由封装)是对某些网络层协议(如IP和IPX)的数据报文进行封装,使这些被封装的报文能够在另一网络层协议 ...

  10. Mybatis-基本学习(上)

    目录 Mybatis mybatis开始 -----环境准备 一.简介 1.什么是MyBatis 2.持久化 3.持久层 4.为什么需要Mybatis? 二.第一个Mybatis程序 1.搭建环境 1 ...