deployment controller 源码分析-深蓝源码网


时间: 2020-09-03 00:08:26 人气: 2274 评论: 0

在前面的文章中已经分析过 kubernetes 中多个组件的源码了,本章会继续解读 kube-controller-manager 源码,kube-controller-manager 中有数十个 controller,本文会分析最常用到的 deployment controller。

deployment 的功能

deployment 是 kubernetes 中用来部署无状态应用的一个对象,也是最常用的一种对象。

deployment、replicaSet 和 pod 之间的关系

deployment 的本质是控制 replicaSet,replicaSet 会控制 pod,然后由 controller 驱动各个对象达到期望状态。

DeploymentController 是 Deployment 资源的控制器,其通过 DeploymentInformer、ReplicaSetInformer、PodInformer 监听三种资源,当三种资源变化时会触发 DeploymentController 中的 syncLoop 操作。

deployment 的基本功能

下面通过命令行操作展示一下 deployment 的基本功能。

以下是 deployment 的一个示例文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  progressDeadlineSeconds: 600    // 执行操作的超时时间
  replicas: 20
  revisionHistoryLimit: 10    // 保存的历史版本数量
  selector:
    matchLabels:
      app: nginx-deployment
  strategy:
    rollingUpdate:
      maxSurge: 25%         // 升级过程中最多可以比原先设置多出的 pod 数量
      maxUnavailable: 25%   // 升级过程中最多有多少个 pod 处于无法提供服务的状态
    type: RollingUpdate     // 更新策略
  template:
    metadata:
      labels:
        app: nginx-deployment
    spec:
      containers:
      - name: nginx-deployment
        image: nginx:1.9
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
创建
$ kubectl create -f nginx-dep.yaml --record

$ kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   20/20   20           20          22h

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-68b649bd8b   20        20        20      22h
滚动更新
$ kubectl set image deploy/nginx-deployment nginx-deployment=nginx:1.9.3

$ kubectl rollout status deployment/nginx-deployment
回滚
// 查看历史版本
$ kubectl rollout history deployment/nginx-deployment
deployment.extensions/nginx-deployment
REVISION  CHANGE-CAUSE
4         <none>
5         <none>

// 指定版本回滚
$ kubectl rollout undo deployment/nginx-deployment --to-revision=2
扩缩容
$ kubectl scale deployment nginx-deployment --replicas 10
deployment.extensions/nginx-deployment scaled
暂停与恢复
$ kubectl rollout pause deployment/nginx-deployment
$ kubectl rollout resume deploy nginx-deployment
删除
// 级联删除
$ kubectl delete deployment nginx-deployment

// 非级联删除
$ kubectl delete deployment nginx-deployment --cascade=false

以上是 deployment 的几个常用操作,下面会结合源码分析这几个操作都是如何实现的。

deployment controller 源码分析

kubernetes 版本:v1.16

在控制器模式下,每次操作对象都会触发一次事件,然后 controller 会进行一次 syncLoop 操作,controller 是通过 informer 监听事件以及进行 ListWatch 操作的,关于 informer 的基础知识可以参考以前写的文章。

deployment controller 启动流程

kube-controller-manager 中所有 controller 的启动都是在 Run 方法中完成初始化并启动的。在 Run 中会调用 run 函数,run 函数的主要流程有:

  • 1、调用 NewControllerInitializers 初始化所有 controller
  • 2、调用 StartControllers 启动所有 controller

k8s.io/kubernetes/cmd/kube-controller-manager/app/controllermanager.go:158

func Run(c *config.CompletedConfig, stopCh <-chan struct{}) error {
    ......
    run := func(ctx context.Context) {
        ......
        // 1.调用 NewControllerInitializers 初始化所有 controller
        // 2.调用 StartControllers 启动所有 controller
        if err := StartControllers(controllerContext, saTokenControllerInitFunc, NewControllerInitializers(controllerContext.LoopMode), unsecuredMux); err != nil {
            klog.Fatalf("error starting controllers: %v", err)
        }
        ......
        select {}
    }
    ......
}

NewControllerInitializers 中定义了所有的 controller 以及 start controller 对应的方法。deployment controller 对应的启动方法是 startDeploymentController

k8s.io/kubernetes/cmd/kube-controller-manager/app/controllermanager.go:373

func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc {
    controllers := map[string]InitFunc{}

    ......
    controllers["deployment"] = startDeploymentController
    ......
}

startDeploymentController 中对 deploymentController 进行了初始化,并执行 dc.Run() 方法启动了 controller。

k8s.io/kubernetes/cmd/kube-controller-manager/app/apps.go:82

func startDeploymentController(ctx ControllerContext) (http.Handler, bool, error) {
    ......

    // 初始化 controller
    dc, err := deployment.NewDeploymentController(
        ctx.InformerFactory.Apps().V1().Deployments(),
        ctx.InformerFactory.Apps().V1().ReplicaSets(),
        ctx.InformerFactory.Core().V1().Pods(),
        ctx.ClientBuilder.ClientOrDie("deployment-controller"),
    )
    ......

    // 启动 controller
    go dc.Run(int(ctx.ComponentConfig.DeploymentController.ConcurrentDeploymentSyncs), ctx.Stop)
    return nil, true, nil
}

ctx.ComponentConfig.DeploymentController.ConcurrentDeploymentSyncs 指定了 deployment controller 中工作的 goroutine 数量,默认值为 5,即会启动五个 goroutine 从 workqueue 中取出 object 并进行 sync 操作,该参数的默认值定义在 k8s.io/kubernetes/pkg/controller/deployment/config/v1alpha1/defaults.go 中。

dc.Run 方法会执行 ListWatch 操作并根据对应的事件执行 syncLoop。

k8s.io/kubernetes/pkg/controller/deployment/deployment_controller.go:148

func (dc *DeploymentController) Run(workers int, stopCh <-chan struct{}) {
    ......

    // 1、等待 informer cache 同步完成
    if !cache.WaitForNamedCacheSync("deployment", stopCh, dc.dListerSynced, dc.rsListerSynced, dc.podListerSynced) {
        return
    }

    // 2、启动 5 个 goroutine
    for i := 0; i < workers; i++ {
        // 3、在每个 goroutine 中每秒执行一次 dc.worker 方法
        go wait.Until(dc.worker, time.Second, stopCh)
    }

    <-stopCh
}

dc.worker 会调用 syncHandler 进行 sync 操作。

func (dc *DeploymentController) worker() {
    for dc.processNextWorkItem() {
    }
}

func (dc *DeploymentController) processNextWorkItem() bool {
    key, quit := dc.queue.Get()
    if quit {
        return false
    }
    defer dc.queue.Done(key)

    // 若 workQueue 中有任务则进行处理
    err := dc.syncHandler(key.(string))
    dc.handleErr(err, key)

    return true
}

syncHandler 是 controller 的核心逻辑,下面会进行详细说明。至此,对于 deployment controller 的启动流程已经分析完,再来看一下 deployment controller 启动过程中的整个调用链,如下所示:

Run() --> run() --> NewControllerInitializers() --> StartControllers() --> startDeploymentController() --> deployment.NewDeploymentController() --> deployment.Run()
--> deployment.syncDeployment()

deployment controller 在初始化时指定了 dc.syncHandler = dc.syncDeployment,所以该函数名为 syncDeployment,本文开头介绍 deployment 中的基本操作都是在 syncDeployment 中完成的。

syncDeployment 的主要流程如下所示:

  • 1、调用 getReplicaSetsForDeployment 获取集群中与 Deployment 相关的 ReplicaSet,若发现匹配但没有关联 deployment 的 rs 则通过设置 ownerReferences 字段与 deployment 关联,已关联但不匹配的则删除对应的 ownerReferences;
  • 2、调用 getPodMapForDeployment 获取当前 Deployment 对象关联的 pod,并根据 rs.UID 对上述 pod 进行分类;
  • 3、通过判断 deployment 的 DeletionTimestamp 字段确认是否为删除操作;
  • 4、执行 checkPausedConditions检查 deployment 是否为pause状态并添加合适的condition
  • 5、调用 getRollbackTo 函数检查 Deployment 是否有Annotations:"deprecated.deployment.rollback.to"字段,如果有,调用 dc.rollback 方法执行 rollback 操作;
  • 6、调用 dc.isScalingEvent 方法检查是否处于 scaling 状态中;
  • 7、最后检查是否为更新操作,并根据更新策略 RecreateRollingUpdate 来执行对应的操作;

k8s.io/kubernetes/pkg/controller/deployment/deployment_controller.go:562

func (dc *DeploymentController) syncDeployment(key string) error {
    ......
    namespace, name, err := cache.SplitMetaNamespaceKey(key)
    if err != nil {
        return err
    }

    // 1、从 informer cache 中获取 deployment 对象
    deployment, err := dc.dLister.Deployments(namespace).Get(name)
    if errors.IsNotFound(err) {
        ......
    }

    ......
    d := deployment.DeepCopy()

    // 2、判断 selecor 是否为空
    everything := metav1.LabelSelector{}
    if reflect.DeepEqual(d.Spec.Selector, &everything) {
        ......
        return nil
    }


    // 3、获取 deployment 对应的所有 rs,通过 LabelSelector 进行匹配
    rsList, err := dc.getReplicaSetsForDeployment(d)
    if err != nil {
        return err
    }

    // 4、获取当前 Deployment 对象关联的 pod,并根据 rs.UID 对 pod 进行分类
    podMap, err := dc.getPodMapForDeployment(d, rsList)
    if err != nil {
        return err
    }

    // 5、如果该 deployment 处于删除状态,则更新其 status
    if d.DeletionTimestamp != nil {
        return dc.syncStatusOnly(d, rsList)
    }

    // 6、检查是否处于 pause 状态
    if err = dc.checkPausedConditions(d); err != nil {
        return err
    }

    if d.Spec.Paused {
        return dc.sync(d, rsList)
    }

    // 7、检查是否为回滚操作
    if getRollbackTo(d) != nil {
        return dc
						 技术沙龙 教程文章 热点综合					

评论