kubelet源码分析-pod新建流程

来源:互联网 发布:土建材料计划软件 编辑:程序博客网 时间:2024/05/29 10:40

syncLoop

syncLoop 是 kubelet 的主循环方法,它从不同的管道(文件、URL 和 apiserver)监听变化,并把它们汇聚起来。当有新的变化发生时,它会调用对应的处理函数,保证 pod 处于期望的状态。如果 pod 没有变化,它也会定期保证所有的容器和最新的期望状态保持一致。这个方法是 for 循环,不会退出。

func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler) {    glog.Info("Starting kubelet main sync loop.")    syncTicker := time.NewTicker(time.Second)    defer syncTicker.Stop()    housekeepingTicker := time.NewTicker(housekeepingPeriod)    defer housekeepingTicker.Stop()    plegCh := kl.pleg.Watch()    for {        if rs := kl.runtimeState.runtimeErrors(); len(rs) != 0 {            glog.Infof("skipping pod synchronization - %v", rs)            time.Sleep(5 * time.Second)            continue        }        if !kl.syncLoopIteration(updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) {            break        }    }}

这里的代码主逻辑是 for 循环,不断调用 syncLoopIteration 方法。在此之前创建了两个定时器: syncTicker 和 housekeepingTicker,即使没有需要更新的 pod 配置,kubelet 也会定时去做同步和清理工作。如果在每次循环过程中出现比较严重的错误,kubelet 会记录到 runtimeState 中,遇到错误就等待 5 秒中继续循环。注意第二个参数变成了 SyncHandler 类型,这是一个 interface,定义了处理不同情况的接口,我们在在后面会看到它的具体方法。

我们继续看 syncLoopIteration,这个方法就是对多个管道做遍历,发现任何一个管道有消息就交给 handler 去处理。

func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,    syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {    kl.syncLoopMonitor.Store(kl.clock.Now())    select {    case u, open := <-configCh:        switch u.Op {        case kubetypes.ADD:            glog.V(2).Infof("SyncLoop (ADD, %q): %q", u.Source, format.Pods(u.Pods))            handler.HandlePodAdditions(u.Pods)        case kubetypes.UPDATE:            glog.V(2).Infof("SyncLoop (UPDATE, %q): %q", u.Source, format.PodsWithDeletiontimestamps(u.Pods))            handler.HandlePodUpdates(u.Pods)        case kubetypes.REMOVE:            glog.V(2).Infof("SyncLoop (REMOVE, %q): %q", u.Source, format.Pods(u.Pods))            handler.HandlePodRemoves(u.Pods)        case kubetypes.RECONCILE:            glog.V(4).Infof("SyncLoop (RECONCILE, %q): %q", u.Source, format.Pods(u.Pods))            handler.HandlePodReconcile(u.Pods)        case kubetypes.DELETE:            glog.V(2).Infof("SyncLoop (DELETE, %q): %q", u.Source, format.Pods(u.Pods))            // DELETE is treated as a UPDATE because of graceful deletion.            handler.HandlePodUpdates(u.Pods)        case kubetypes.SET:            // TODO: Do we want to support this?            glog.Errorf("Kubelet does not support snapshot update")        }        // 收到消息之后就把对应的来源标记为 ready 状态        kl.sourcesReady.AddSource(u.Source)    case e := <-plegCh:        if isSyncPodWorthy(e) {            // PLEG event for a pod; sync it.            if pod, ok := kl.podManager.GetPodByUID(e.ID); ok {                glog.V(2).Infof("SyncLoop (PLEG): %q, event: %#v", format.Pod(pod), e)                handler.HandlePodSyncs([]*api.Pod{pod})            } else {                glog.V(4).Infof("SyncLoop (PLEG): ignore irrelevant event: %#v", e)            }        }        if e.Type == pleg.ContainerDied {            if containerID, ok := e.Data.(string); ok {                kl.cleanUpContainersInPod(e.ID, containerID)            }        }    case <-syncCh:        podsToSync := kl.getPodsToSync()        if len(podsToSync) == 0 {            break        }        glog.V(4).Infof("SyncLoop (SYNC): %d pods; %s", len(podsToSync), format.Pods(podsToSync))        kl.HandlePodSyncs(podsToSync)    case update := <-kl.livenessManager.Updates():        if update.Result == proberesults.Failure {            // The liveness manager detected a failure; sync the pod.            pod, ok := kl.podManager.GetPodByUID(update.PodUID)            if !ok {                glog.V(4).Infof("SyncLoop (container unhealthy): ignore irrelevant update: %#v", update)                break            }            glog.V(1).Infof("SyncLoop (container unhealthy): %q", format.Pod(pod))            handler.HandlePodSyncs([]*api.Pod{pod})        }    case <-housekeepingCh:        if !kl.sourcesReady.AllReady() {            glog.V(4).Infof("SyncLoop (housekeeping, skipped): sources aren't ready yet.")        } else {            glog.V(4).Infof("SyncLoop (housekeeping)")            if err := handler.HandlePodCleanups(); err != nil {                glog.Errorf("Failed cleaning pods: %v", err)            }        }    }    kl.syncLoopMonitor.Store(kl.clock.Now())    return true}

可以看到,它会从以下管道中获取消息:

configCh:读取配置事件的管道,就是之前讲过的通过文件、URL 和 apiserver 汇聚起来的事件syncCh:定时器管道,每次隔一段事件去同步最新保存的 pod 状态houseKeepingCh:housekeeping 事件的管道,做 pod 清理工作plegCh:PLEG 状态,如果 pod 的状态发生改变(因为某些情况被杀死,被暂停等),kubelet 也要做处理livenessManager.Updates():健康检查发现某个 pod 不可用,一般也要对它进行重启需要注意的是, switch-case 语句从管道中读取数据的时候,不像一般情况下那样会从上到下按照顺序,只要任何管道中有数据,switch 就会选择执行对应的 case 语句。

每个管道的处理思路大同小异,我们只分析用户通过 apiserver 添加新 pod 的情况,也就是 handler.HandlePodAdditions(u.Pods) 这句话的处理逻辑。

HandlePodAddtions

func (kl *Kubelet) HandlePodAdditions(pods []*api.Pod) {    start := kl.clock.Now()    sort.Sort(sliceutils.PodsByCreationTime(pods))    for _, pod := range pods {        existingPods := kl.podManager.GetPods()        kl.podManager.AddPod(pod)        if kubepod.IsMirrorPod(pod) {            kl.handleMirrorPod(pod, start)            continue        }        ......        mirrorPod, _ := kl.podManager.GetMirrorPodByPod(pod)        kl.dispatchWork(pod, kubetypes.SyncPodCreate, mirrorPod, start)        kl.probeManager.AddPod(pod)    }}

对于事件中的每个 pod,执行以下操作:

把所有的 pod 按照创建日期进行排序,保证最先创建的 pod 会最先被处理
把它加入到 podManager 中,因为 podManager 是 kubelet 的 source of truth,所有被管理的 pod 都要出现在里面。如果 podManager 中找不到某个 pod,就认为这个 pod 被删除了
如果是 mirror pod调用其单独的方法
验证 pod 是否能在该节点运行,如果不可以直接拒绝
把 pod 分配给给 worker 做异步处理
在 probeManager 中添加 pod,如果 pod 中定义了 readiness 和 liveness 健康检查,启动 goroutine 定期进行检测
这里可以看到 podManger 和 probeManager 发挥用处了,它们两个的具体实现都不复杂,感兴趣的读者可以自行阅读相关的代码。

pod 具体会被怎么处理呢?我们再来看 dispatchWorker 方法,它的作用就是根据 pod 把任务发送给特定的执行者 podWorkers:

func (kl *Kubelet) dispatchWork(pod *api.Pod, syncType kubetypes.SyncPodType, mirrorPod *api.Pod, start time.Time) {    if kl.podIsTerminated(pod) {        if pod.DeletionTimestamp != nil {            kl.statusManager.TerminatePod(pod)        }        return    }    // Run the sync in an async worker.    kl.podWorkers.UpdatePod(&UpdatePodOptions{        Pod:        pod,        MirrorPod:  mirrorPod,        UpdateType: syncType,        OnCompleteFunc: func(err error) {            if err != nil {                metrics.PodWorkerLatency.WithLabelValues(syncType.String()).Observe(metrics.SinceInMicroseconds(start))            }        },    })    // Note the number of containers for new pods.    if syncType == kubetypes.SyncPodCreate {        metrics.ContainersPerPodCount.Observe(float64(len(pod.Spec.Containers)))    }}

dispatchWork 主要工作就是把接收到的参数封装成 UpdatePodOptions,调用 kl.podWorkers.UpdatePod 方法。podWorkers 的代码在 pkg/kubelet/pod_workers.go 文件中,它通过 podUpdates 字典保存了一个字典,每个 pod 的 id 作为 key,而类型为 UpdatePodOptions 的管道作为 value 传递 pod 信息。

func (p *podWorkers) UpdatePod(options *UpdatePodOptions) {    pod := options.Pod    uid := pod.UID    var podUpdates chan UpdatePodOptions    var exists bool    p.podLock.Lock()    defer p.podLock.Unlock()    if podUpdates, exists = p.podUpdates[uid]; !exists {        podUpdates = make(chan UpdatePodOptions, 1)        p.podUpdates[uid] = podUpdates        go func() {            defer runtime.HandleCrash()            p.managePodLoop(podUpdates)        }()    }    if !p.isWorking[pod.UID] {        p.isWorking[pod.UID] = true        podUpdates <- *options    } else {        update, found := p.lastUndeliveredWorkUpdate[pod.UID]        if !found || update.UpdateType != kubetypes.SyncPodKill {            p.lastUndeliveredWorkUpdate[pod.UID] = *options        }    }}

UpdatePod 会先去检查 podUpdates 字典是否已经存在对应的 pod,因为这里的新建的 pod,所以会调用 p.managePodLoop() 方法作为 goroutine 运行更新工作。也就是说对于管理的每个 pod,podWorkers 都会启动一个 goroutine 在后台执行,除此之外,它还会更新 podUpdate 和 isWorking,填入新 pod 的信息,并往 podUpdates 管道中发送接收到的 pod 选项信息。

managePodLoop 的代码如下:

func (p *podWorkers) managePodLoop(podUpdates <-chan UpdatePodOptions) {    var lastSyncTime time.Time    for update := range podUpdates {        err := func() error {            podUID := update.Pod.UID            status, err := p.podCache.GetNewerThan(podUID, lastSyncTime)            if err != nil {                return err            }            err = p.syncPodFn(syncPodOptions{                mirrorPod:      update.MirrorPod,                pod:            update.Pod,                podStatus:      status,                killPodOptions: update.KillPodOptions,                updateType:     update.UpdateType,            })            lastSyncTime = time.Now()            if err != nil {                return err            }            return nil        }()        // notify the call-back function if the operation succeeded or not        if update.OnCompleteFunc != nil {            update.OnCompleteFunc(err)        }        if err != nil {            glog.Errorf("Error syncing pod %s, skipping: %v", update.Pod.UID, err)            p.recorder.Eventf(update.Pod, api.EventTypeWarning, events.FailedSync, "Error syncing pod, skipping: %v", err)        }        p.wrapUp(update.Pod.UID, err)    }}

managePodLoop 调用 syncPodFn 方法去同步 pod,syncPodFn 实际上就是 kubelet.SyncPod。

SyncPod

SyncPod 的内容比较长,我们这里就不贴出它的代码了,它做的事情包括:

如果是删除 pod,立即执行并返回
检查 pod 是否能运行在本节点,主要是权限检查(是否能使用主机网络模式,是否可以以 privileged 权限运行等)。如果没有权限,就删除本地旧的 pod 并返回错误信息
如果是 static Pod,就创建或者更新对应的 mirrorPod
创建 pod 的数据目录,存放 volume 和 plugin 信息
如果定义了 PV,等待所有的 volume mount 完成(volumeManager 会在后台做这些事情)
如果有 image secrets,去 apiserver 获取对应的 secrets 数据
调用 container runtime 的 SyncPod 方法,去实现真正的容器创建逻辑
这里所有的事情都和具体的容器没有关系,可以看做是提前做的准备工作。最重要的事情发生在 kl.containerRuntime.SyncPod() 里,也就是上面过程的最后一个步骤,它调 runtime 执行具体容器的创建,对于 docker 来说,具体的代码位于 pkg/kubelet/dockertools/docker_manager.go:

func (dm *DockerManager) SyncPod(pod *api.Pod, _ api.PodStatus, podStatus *kubecontainer.PodStatus, pullSecrets []api.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {    // 计算容器的变化    containerChanges, err := dm.computePodContainerChanges(pod, podStatus)    ......    // 如果需要,先删除运行的容器    if containerChanges.StartInfraContainer || (len(containerChanges.ContainersToKeep) == 0 && len(containerChanges.ContainersToStart) == 0) {        ......        killResult := dm.killPodWithSyncResult(pod, kubecontainer.ConvertPodStatusToRunningPod(dm.Type(), podStatus), nil)        ......    }    podIP := ""    if podStatus != nil {        podIP = podStatus.IP    }    // 先创建 infrastructure 容器    podInfraContainerID := containerChanges.InfraContainerId    if containerChanges.StartInfraContainer && (len(containerChanges.ContainersToStart) > 0) {        ......        // 通过 docker 创建出来一个运行的 pause 容器。        // 如果镜像不存在,kubelet 会先下载 pause 镜像;        // 如果 pod 是主机模式,容器也是;其他情况下,容器会使用 None 网络模式,让 kubelet 的网络插件自己进行网络配置        podInfraContainerID, err, msg = dm.createPodInfraContainer(pod)        ......        // 配置 infrastructure 容器的网络        if !kubecontainer.IsHostNetworkPod(pod) {            err = dm.networkPlugin.SetUpPod(pod.Namespace, pod.Name, podInfraContainerID.ContainerID())            ......        }    }    ......    // 启动正常的容器    for idx := range containerChanges.ContainersToStart {        container := &pod.Spec.Containers[idx]        startContainerResult := kubecontainer.NewSyncResult(kubecontainer.StartContainer, container.Name)        result.AddSyncResult(startContainerResult)        // containerChanges.StartInfraContainer causes the containers to be restarted for config reasons        if !containerChanges.StartInfraContainer {            isInBackOff, err, msg := dm.doBackOff(pod, container, podStatus, backOff)            if isInBackOff {                startContainerResult.Fail(err, msg)                continue            }        }        if err, msg := dm.tryContainerStart(container, pod, podStatus, pullSecrets, namespaceMode, pidMode, podIP); err != nil {            startContainerResult.Fail(err, msg)            utilruntime.HandleError(fmt.Errorf("container start failed: %v: %s", err, msg))            continue        }    }    return}

这个方法的内容也非常多,它的主要逻辑是先比较传递过来的 pod 信息和实际运行的 pod(对于新建 pod 来说后者为空),计算出两者的差别,也就是需要更新的地方。然后先创建 infrastructure 容器,配置好网络,然后再逐个创建应用容器。

dm.computePodContainerChanges 根据最新拿到的 pod 配置,和目前实际运行的容器对比,计算出其中的变化,得到需要重新启动的容器信息。不管是创建、更新还是删除 pod,最终都会调用 syncPod 方法,所以这个结果涵盖了所有的可能性。

type podContainerChangesSpec struct {    StartInfraContainer  bool    InfraChanged         bool    InfraContainerId     kubecontainer.DockerID    InitFailed           bool    InitContainersToKeep map[kubecontainer.DockerID]int    ContainersToStart    map[int]string    ContainersToKeep     map[kubecontainer.DockerID]int}

这个结构体中的内容可以分成三部分:infrastructure 变化信息,init containers 变化信息,以及应用 containers 变化信息。检测 infrastructure pod 有没有变化,只需要检查下面这些内容:

pasue 镜像
网络模型有没有变化
暴露的端口号有没有变化
镜像拉取策略
环境变量
根据 infrastructure 容器的状态,其需要执行的操作可以分为三种情况:

容器还不存在,或者没有在运行状态:启动新的 pause 容器(这就是我们一直分析的 pod 新建的情况)
容器正在运行,但是新的 pod 配置发生了变化:杀掉 pause 容器,重新启动
pause 容器已经运行,而且没有变化,不做任何事情
应用容器要重建的原因包括:

容器异常退出
infrastructure 容器要重启(pod 新建也属于这种情况)
init 容器运行失败
container 配置的哈希值发生了变化(对 pod 的内容做了更新操作)
liveness 检测失败
容器创建就是根据配置得到 docker client 新建容器需要的所有参数,最终发送给 docker API,这里不再赘述。创建应用容器的时候,会把 infrastructure 容器的网络模式和 pidMode 传过去,这也是 pod 中所有容器共享网络和 pid 资源的地方。

原创粉丝点击