时间: 2020-09-03 00:08:26 人气: 2274 评论: 0
在前面的文章中已经分析过 kubernetes 中多个组件的源码了,本章会继续解读 kube-controller-manager 源码,kube-controller-manager 中有数十个 controller,本文会分析最常用到的 deployment controller。
deployment 是 kubernetes 中用来部署无状态应用的一个对象,也是最常用的一种对象。
deployment 的本质是控制 replicaSet,replicaSet 会控制 pod,然后由 controller 驱动各个对象达到期望状态。

DeploymentController 是 Deployment 资源的控制器,其通过 DeploymentInformer、ReplicaSetInformer、PodInformer 监听三种资源,当三种资源变化时会触发 DeploymentController 中的 syncLoop 操作。
下面通过命令行操作展示一下 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 的几个常用操作,下面会结合源码分析这几个操作都是如何实现的。
kubernetes 版本:v1.16
在控制器模式下,每次操作对象都会触发一次事件,然后 controller 会进行一次 syncLoop 操作,controller 是通过 informer 监听事件以及进行 ListWatch 操作的,关于 informer 的基础知识可以参考以前写的文章。
kube-controller-manager 中所有 controller 的启动都是在 Run 方法中完成初始化并启动的。在 Run 中会调用 run 函数,run 函数的主要流程有:
NewControllerInitializers 初始化所有 controllerStartControllers 启动所有 controllerk8s.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 的主要流程如下所示:
getReplicaSetsForDeployment 获取集群中与 Deployment 相关的 ReplicaSet,若发现匹配但没有关联 deployment 的 rs 则通过设置 ownerReferences 字段与 deployment 关联,已关联但不匹配的则删除对应的 ownerReferences;getPodMapForDeployment 获取当前 Deployment 对象关联的 pod,并根据 rs.UID 对上述 pod 进行分类;checkPausedConditions检查 deployment 是否为pause状态并添加合适的condition;getRollbackTo 函数检查 Deployment 是否有Annotations:"deprecated.deployment.rollback.to"字段,如果有,调用 dc.rollback 方法执行 rollback 操作;dc.isScalingEvent 方法检查是否处于 scaling 状态中;Recreate 或 RollingUpdate 来执行对应的操作;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 技术沙龙 教程文章 热点综合