clusterapi
当前`kubernetes`生态中,生命周期管理相关工具官方提供的有`kubeadm`、`kubespray`等,开源社区还有很多其他的实现,基于这类工具可以实现`k8s`集群部署、升级、增删节点,但是使用这些工具的前提是:基础设施已经准备完成,即只有当基础设施准备完成后,`kubeadm`之类的工具才可以正常工作。如果想要自动化配置基础设施,通常需要根据环境不同编写不同的代码支持虚拟化或服务器场景。`kubernetes`社区针对基础设施问题,发起了`cluster api`项目,旨在使用声明式`API`形式管理`kubernetes`集群的生命周期、支持多种云环境。
# 1.概述
# 1.1.cluster api
cluster api可以理解成是一个集群生命周期管理的框架,它实现了一些基础能力,同时定义了集群生命周期管理的新的规范,即集群生命周期的领域模型,也定义了新增自定义基础设施要支持的cluster api的实现标准。其中,cluster api定义的领域模型包括cluster、controlplane、bootstrap config和machine。
cluster代表了一个完整的集群的安装,包括安装kubernetes集群需要的集群层面的资源,如LB、VPC等;包括机器的创建;暴露整个控制器平面组件的安装;包括集群的健康检查等等。bootstrapconfig代表了集群的不同角色的机器在启动的时候的配置,如控制节点的bootstrap config和计算节点的bootstrap config是不同的,以及控制节点里的第一台机器和剩余的控制节点也是不同的。需要注意的是,bootstrapconfig和machine绑定,不同的机器配置不同,每个提供商在实现的时候可以自己封装启动配置。因此,bootstrap更像是一种启动的类型定义,默认实现的kubeadm作为bootstrap的类型。controlplane代表了一个集群的控制平面的完整安装,包括api server、controller manager等kubernetes的管理组件。其中也包含机器的创建以及不同机器需要的bootstrap config的创建,集群证书的创建,集群控制平面组件的健康检查,版本升级,控制平面的扩缩容等。一个controlplane被创建,同时状态变为ready的话,代表着一个kubernetes的所有管理组件都是ready,可以直接使用kubectl操作集群。machine代表了一台被集群纳管和使用的机器,不同的provider可以根据自己的平台实现如何创建一台机器出来,需要注意的是机器的启动需要绑定自己的bootstrap config。machinedeployment代表了一组机器的创建,类似于kubernetes的deployment,可以通过deployment创建出一组机器出来。其中,创建机器时需要事先知道哪个provider提供的机器以及启动时依赖的bootstrap config,所以machinedeployment的字段里就定义了相关的关键字,对应bootstrap和Infrastructure两个字段。machineset代表一组机器的创建,类似于replicaset。
# 1.2.cluster provider
cluster api使用声明式API管理K8S集群,前提是需要建一个管理集群,通常称为bootstrap cluster。管理集群中,可以部署CRD及相应的cluster api控制器及provider控制器。相关条件就绪后,当在管理集群创建资源类型为cluster、machine或machinedeployment,对应控制器会自动构建基础设施,然后基于provider实现创建k8s集群。目前,cluster api提供了三种创建bootstrap node/cluster的方式,包括kind、minikube和existing。其中,
cluster api需要在各个cloud provider里创建machine资源,所以需要为各个cloud provider实现一个cluster api provider,由这些provider来真正的执行创建机器资源操作。在v1alpha1版本中,cluster api定义了一套接口要求provider实现,并将实现的接口注册到cluster api的controller,最后只需要运行一个controller manager即可实现资源声明式创建。/// [Actuator] /// Actuator controls clusters on a specific infrastructure. All /// methods should be idempotent unless otherwise specified. type Actuator interface { // Reconcile creates or applies updates to the cluster. Reconcile(*clusterv1.Cluster) error // Delete the cluster. Delete(*clusterv1.Cluster) error }1
2
3
4
5
6
7
8
9
10
11cluster api的v1alpha2版本中,将Actuator接口删除了。cluster api provider也就不需要再向cluster api core controller注册Actuator的实现。相应地,cluster api provider需要实现两个新的providers,即bootstrap provider和Infrastructure provider,他们作为独立的controller运行。Infrastructure provider功能是在cloud provider中创建VM等资源,bootstrap的功能是将machine转变为k8s node。
# 1.3.整体架构
cluster api包含了作为基础框架需要提供的基础能力,比如cluster的controller实现;control plane的controller实现;machinedeployment的controller实现等等。bootstrap provider是不同的厂商自己实现的启动配置,内置实现了基于kubeadm的启动配置。infrastructure provider是不同的厂商实现自己在集群层面和机器层面所需要的资源的创建逻辑,比如AWS可以用SDK的方式创建机器,vSphere使用基于虚拟机模板的方式创建机器。它代表了不同的厂商需要遵循cluster api的规范去实现的接口和逻辑。,例如为集群创建vpc,为控制平面创建虚拟机或物理机。control plane provider是不同的厂商自己实现的控制平面的方法,比如cluster api内置实现了基于kubeadm方式的创建控制平面的方式,而AWS实现了基于AWS平台已有的kubernetes能力的api或sdk方式实现控制平面的创建。cluster api定义了一套标准的接口规范,包括cluster的接口规范,machine的接口规范,control plane的接口规范,bootstrap的接口规范。提供商实现provider的时候,会类似实现接口的方式实现自己的部分,例如vSphere会定义自己的CRD-vSphereCluster、vSphereMachine。target cluster工作负载集群是最终被创建出来的完整的kubernetes集群,不同的提供商建出的集群基于各自的实现存在差异。例如,基于kubeadm作为controlplane的实现建出的集群大致类似;以sdk方式直接利用AWS的基础设施能力建出的集群和kubeadm方式就存在差异。management cluster管理集群是运行cluster api组件以及提供商提供的扩展组件的运行环境,类似cluster、machine等CRD对象就是保存在这个集群的。
# 2.cluster分析
# 2.1.cluster调谐
cluster-api通过cluster这个CRD,以及对应的cluster controller完成cluster的整个逻辑控制,主要代码位于internal/controllers/cluster,真正执行集群创建调谐的其实是cluster_controller.go和cluster_controller_phases.go,通过分阶段的处理思路执行安装集群需要的所有步骤。func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { c, err := ctrl.NewControllerManagedBy(mgr). // 监听cluster对象 For(&clusterv1.Cluster{}). // 监听machine对象 Watches( &source.Kind{Type: &clusterv1.Machine{}}, handler.EnqueueRequestsFromMapFunc(r.controlPlaneMachineToCluster), ). WithOptions(options). WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(ctrl.LoggerFrom(ctx), r.WatchFilterValue)). Build(r) if err != nil { return errors.Wrap(err, "failed setting up with a controller manager") } // 初始化eventRecorder r.recorder = mgr.GetEventRecorderFor("cluster-controller") r.externalTracker = external.ObjectTracker{ Controller: c, } return nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23这里会
watch关心的CRD变化,包括cluster对象和machine对象。func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { // 获取cluster对象 cluster := &clusterv1.Cluster{} if err := r.Client.Get(ctx, req.NamespacedName, cluster); err != nil { if apierrors.IsNotFound(err) { return ctrl.Result{}, nil } return ctrl.Result{}, err } // 集群暂停提前结束 if annotations.IsPaused(cluster, cluster) { log.Info("Reconciliation is paused for this object") return ctrl.Result{}, nil } // 初始化 patchHelper,用于patch对象 patchHelper, err := patch.NewHelper(cluster, r.Client) if err != nil { return ctrl.Result{}, err } defer func() { // 调谐status.phase字段 r.reconcilePhase(ctx, cluster) // 没有err时收集 ObservedGeneration patchOpts := []patch.Option{} if reterr == nil { patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{}) } // patch最新的cluster对象 if err := patchCluster(ctx, patchHelper, cluster, patchOpts...); err != nil { reterr = kerrors.NewAggregate([]error{reterr, err}) } }() // 不存在finalizer时进行初始化,避免cluster对象没有finalizer时被直接删除 if !controllerutil.ContainsFinalizer(cluster, clusterv1.ClusterFinalizer) { controllerutil.AddFinalizer(cluster, clusterv1.ClusterFinalizer) return ctrl.Result{}, nil } // 如果是删除调谐,走delete流程 if !cluster.ObjectMeta.DeletionTimestamp.IsZero() { return r.reconcileDelete(ctx, cluster) } // 否则走部署流程 return r.reconcile(ctx, cluster) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50cluster_controller正式调谐时先获取了最新cluster对象,然后做了些检查和校验,包括cluster是否暂停、是否包含finalizer、初始化patchHelper用于后续更新对象以及调谐流程的判断。func (r *Reconciler) reconcile(ctx context.Context, cluster *clusterv1.Cluster) (ctrl.Result, error) { log := ctrl.LoggerFrom(ctx) // 如果集群启用拓扑结构且控制平面和基础设施未就绪则等待拓扑结构生成(控制平面和基础设施声明在拓扑字段由拓扑结构生成和管理) if cluster.Spec.Topology != nil { if cluster.Spec.ControlPlaneRef == nil || cluster.Spec.InfrastructureRef == nil { // TODO: add a condition to surface this scenario log.Info("Waiting for the topology to be generated") return ctrl.Result{}, nil } } // 定义需要做的事 phases := []func(context.Context, *clusterv1.Cluster) (ctrl.Result, error){ r.reconcileInfrastructure, r.reconcileControlPlane, r.reconcileKubeconfig, r.reconcileControlPlaneInitialized, } res := ctrl.Result{} errs := []error{} // 串行处理每件事,包括基础设施建设、控制平面建设、kubeconfig生成、控制平面初始化等 for _, phase := range phases { // 调用执行phase方法 phaseResult, err := phase(ctx, cluster) if err != nil { errs = append(errs, err) } if len(errs) > 0 { continue } res = util.LowestNonZeroResult(res, phaseResult) } return res, kerrors.NewAggregate(errs) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 2.2.reconcileInfrastructure建设
reconcileInfrastructure主要用于基础设施建设,建设完成后将Infrastructure的就绪信息patch到cluster。func (r *Reconciler) reconcileInfrastructure(ctx context.Context, cluster *clusterv1.Cluster) (ctrl.Result, error) { log := ctrl.LoggerFrom(ctx) // 如果没有声明基础设施则结束 if cluster.Spec.InfrastructureRef == nil { return ctrl.Result{}, nil } // 调用通用外部调谐器 infraReconcileResult, err := r.reconcileExternal(ctx, cluster, cluster.Spec.InfrastructureRef) if err != nil { return ctrl.Result{}, err } // 如果需要入队则提前返回 if infraReconcileResult.RequeueAfter > 0 { return ctrl.Result{RequeueAfter: infraReconcileResult.RequeueAfter}, nil } // 如果外部对象暂停则停止协调 if infraReconcileResult.Paused { return ctrl.Result{}, nil } infraConfig := infraReconcileResult.Result // 如果基础设施被标记为删除,提前结束 if !infraConfig.GetDeletionTimestamp().IsZero() { return ctrl.Result{}, nil } // 拿到上一次的基础设施状态 preReconcileInfrastructureReady := cluster.Status.InfrastructureReady // 拿到本次基础设施的状态 ready, err := external.IsReady(infraConfig) if err != nil { return ctrl.Result{}, err } cluster.Status.InfrastructureReady = ready // 如果基础设施状态变化则记录事件 if preReconcileInfrastructureReady != cluster.Status.InfrastructureReady { r.recorder.Eventf(cluster, corev1.EventTypeNormal, "InfrastructureReady", "Cluster %s InfrastructureReady is now %t", cluster.Name, cluster.Status.InfrastructureReady) } // 汇报集群中定义的基础设施对象的当前状态到condition conditions.SetMirror(cluster, clusterv1.InfrastructureReadyCondition, conditions.UnstructuredGetter(infraConfig), conditions.WithFallbackValue(ready, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, ""), ) // 如果基础设施还没有ready,提前结束 if !ready { log.V(3).Info("Infrastructure provider is not ready yet") return ctrl.Result{}, nil } // 如果cluster的Spec.ControlPlaneEndpoint没有设置,从基础设施对象的spec.controlPlaneEndpoint获取并解析入口 if !cluster.Spec.ControlPlaneEndpoint.IsValid() { if err := util.UnstructuredUnmarshalField(infraConfig, &cluster.Spec.ControlPlaneEndpoint, "spec", "controlPlaneEndpoint"); err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Spec.ControlPlaneEndpoint from infrastructure provider for Cluster %q in namespace %q", cluster.Name, cluster.Namespace) } } // 从基础设施对象`status.failureDomains`字段中解析故障域信息并回填到cluster failureDomains := clusterv1.FailureDomains{} if err := util.UnstructuredUnmarshalField(infraConfig, &failureDomains, "status", "failureDomains"); err != nil && err != util.ErrUnstructuredFieldNotFound { return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Status.FailureDomains from infrastructure provider for Cluster %q in namespace %q", cluster.Name, cluster.Namespace) } cluster.Status.FailureDomains = failureDomains return ctrl.Result{}, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70这里主要作基础设置协调控制,会通过通用调谐器根据
cluster.spec.InfrastructureRef拿到外部cluster provider对象信息,然后回填部分数据并判断基础设施是否就绪,如果没有就绪则停止协调等待,直至监听的tracker.watch监听的基础设施对象变化会再次入队。// reconcileExternal handles generic unstructured objects referenced by a Cluster. func (r *Reconciler) reconcileExternal(ctx context.Context, cluster *clusterv1.Cluster, ref *corev1.ObjectReference) (external.ReconcileOutput, error) { log := ctrl.LoggerFrom(ctx) // 更新引用对象最新的API合约,主要是利用gvk拿到最新的API信息 if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref); err != nil { return external.ReconcileOutput{}, err } // 获取引用的基础设施对象 obj, err := external.Get(ctx, r.Client, ref, cluster.Namespace) if err != nil { if apierrors.IsNotFound(errors.Cause(err)) { log.Info("Could not find external object for cluster, requeuing", "refGroupVersionKind", ref.GroupVersionKind(), "refName", ref.Name) return external.ReconcileOutput{RequeueAfter: 30 * time.Second}, nil } return external.ReconcileOutput{}, err } // 如果引用的外部基础设施对象暂停,停止协调 if annotations.IsPaused(cluster, obj) { log.V(3).Info("External object referenced is paused") return external.ReconcileOutput{Paused: true}, nil } // 初始化patchHelper patchHelper, err := patch.NewHelper(obj, r.Client) if err != nil { return external.ReconcileOutput{}, err } // 根据引用的外部基础设施对象的ownerRef为cluster if err := controllerutil.SetControllerReference(cluster, obj, r.Client.Scheme()); err != nil { return external.ReconcileOutput{}, err } // 更新引用的外部基础设施对象的labels labels := obj.GetLabels() if labels == nil { labels = make(map[string]string) } labels[clusterv1.ClusterNameLabel] = cluster.Name obj.SetLabels(labels) // 更新外部基础设施对象信息 if err := patchHelper.Patch(ctx, obj); err != nil { return external.ReconcileOutput{}, err } // 添加外部基础设施对象监听器,外部基础设施对象变化时cluster入队 if err := r.externalTracker.Watch(log, obj, &handler.EnqueueRequestForOwner{OwnerType: &clusterv1.Cluster{}}); err != nil { return external.ReconcileOutput{}, err } // 从外部基础设施对象拿到故障信息 failureReason, failureMessage, err := external.FailuresFrom(obj) if err != nil { return external.ReconcileOutput{}, err } // 故障信息不为空时回填到cluster if failureReason != "" { clusterStatusError := capierrors.ClusterStatusError(failureReason) cluster.Status.FailureReason = &clusterStatusError } if failureMessage != "" { cluster.Status.FailureMessage = pointer.String( fmt.Sprintf("Failure detected from referenced resource %v with name %q: %s", obj.GroupVersionKind(), obj.GetName(), failureMessage), ) } // 返回基础设施对象信息(cluster provider) return external.ReconcileOutput{Result: obj}, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72这里主要获取外部基础设施对象
cluster provider,然后更新基础设施对象部分引用及API定义信息,最后把基础设施对象的结果回填到cluster,其中会额外注册一个watch监听器用于监听基础设施对象,便于基础设施未就绪停止协调后可以感知到cluster provider变化。
# 2.3.reconcileControlPlane建设
reconcileControlPlane用于基础设施就绪后建设控制平面并更新cluster。func (r *Reconciler) reconcileControlPlane(ctx context.Context, cluster *clusterv1.Cluster) (ctrl.Result, error) { if cluster.Spec.ControlPlaneRef == nil { return ctrl.Result{}, nil } // 调用获取外部控制平面基础设施对象 controlPlaneReconcileResult, err := r.reconcileExternal(ctx, cluster, cluster.Spec.ControlPlaneRef) if err != nil { return ctrl.Result{}, err } // 如果需要入队,停止协调 if controlPlaneReconcileResult.RequeueAfter > 0 { return ctrl.Result{RequeueAfter: controlPlaneReconcileResult.RequeueAfter}, nil } // 如果控制平面基础设施暂停,停止协调 if controlPlaneReconcileResult.Paused { return ctrl.Result{}, nil } controlPlaneConfig := controlPlaneReconcileResult.Result // 如果控制平面基础设施被删除,停止协调 if !controlPlaneConfig.GetDeletionTimestamp().IsZero() { return ctrl.Result{}, nil } // 获取集群上一次控制平面就绪状态 preReconcileControlPlaneReady := cluster.Status.ControlPlaneReady // 获取本次控制平面基础设施就绪状态 ready, err := external.IsReady(controlPlaneConfig) if err != nil { return ctrl.Result{}, err } cluster.Status.ControlPlaneReady = ready // 如果控制平面状态发生变化,记录事件 if preReconcileControlPlaneReady != cluster.Status.ControlPlaneReady { r.recorder.Eventf(cluster, corev1.EventTypeNormal, "ControlPlaneReady", "Cluster %s ControlPlaneReady is now %t", cluster.Name, cluster.Status.ControlPlaneReady) } // 汇总集群当前控制平面状态到当前condition conditions.SetMirror(cluster, clusterv1.ControlPlaneReadyCondition, conditions.UnstructuredGetter(controlPlaneConfig), conditions.WithFallbackValue(ready, clusterv1.WaitingForControlPlaneFallbackReason, clusterv1.ConditionSeverityInfo, ""), ) // 如果cluster的控制平面初始化未完成则根据情况进行更新 if !conditions.IsTrue(cluster, clusterv1.ControlPlaneInitializedCondition) { // 判断控制平面基础设施对象的初始化动作是否完成 initialized, err := external.IsInitialized(controlPlaneConfig) if err != nil { return ctrl.Result{}, err } // 根据控制平面基础设施初始化完成情况更新cluster的初始化状态 if initialized { conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition) } else { conditions.MarkFalse(cluster, clusterv1.ControlPlaneInitializedCondition, clusterv1.WaitingForControlPlaneProviderInitializedReason, clusterv1.ConditionSeverityInfo, "Waiting for control plane provider to indicate the control plane has been initialized") } } return ctrl.Result{}, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61控制平面基础设施协调主要监听
controplane provider建设情况,根据是否完成控制平面建设及初始化更新cluster对象状态。
# 2.4.reconcileKubeconfig
reconcileKubeconfig主要用于准备集群访问证书,确保后续集群创建的正常访问。func (r *Reconciler) reconcileKubeconfig(ctx context.Context, cluster *clusterv1.Cluster) (ctrl.Result, error) { log := ctrl.LoggerFrom(ctx) // 如果cluster的API server入口无效 if !cluster.Spec.ControlPlaneEndpoint.IsValid() { return ctrl.Result{}, nil } // 如果cluster定义了控制平面引用,说明使用了自定义的controlplane provider,由该provider管理kubeconfig secret if cluster.Spec.ControlPlaneRef != nil { return ctrl.Result{}, nil } // 否则获取kubeconfig对应的secret _, err := secret.Get(ctx, r.Client, util.ObjectKey(cluster), secret.Kubeconfig) switch { case apierrors.IsNotFound(err): // 如果没找到生成默认的kubeconfig secret if err := kubeconfig.CreateSecret(ctx, r.Client, cluster); err != nil { if err == kubeconfig.ErrDependentCertificateNotFound { log.Info("Could not find secret for cluster, requeuing", "Secret", secret.ClusterCA) return ctrl.Result{RequeueAfter: 30 * time.Second}, nil } return ctrl.Result{}, err } case err != nil: return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Kubeconfig Secret for Cluster %q in namespace %q", cluster.Name, cluster.Namespace) } return ctrl.Result{}, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31这里主要是集群访问证书的生成协调,一般不会触发,因为
cluster多数不会使用默认的控制平面基础设施,而是使用自定义的控制平面基础设施实现。
# 2.5.reconcileControlPlaneInitialized
func (r *Reconciler) reconcileControlPlaneInitialized(ctx context.Context, cluster *clusterv1.Cluster) (ctrl.Result, error) { log := ctrl.LoggerFrom(ctx) // 如果cluster定义了控制平面,停止协调(因为控制平面基础设施协调里其实已经做了初始化工作) if cluster.Spec.ControlPlaneRef != nil { log.V(4).Info("Skipping reconcileControlPlaneInitialized because cluster has a controlPlaneRef") return ctrl.Result{}, nil } // 如果控制平面初始化已经完成,停止协调 if conditions.IsTrue(cluster, clusterv1.ControlPlaneInitializedCondition) { log.V(4).Info("Skipping reconcileControlPlaneInitialized because control plane already initialized") return ctrl.Result{}, nil } log.V(4).Info("Checking for control plane initialization") // 获取集群活跃机器 machines, err := collections.GetFilteredMachinesForCluster(ctx, r.Client, cluster, collections.ActiveMachines) if err != nil { log.Error(err, "unable to determine ControlPlaneInitialized") return ctrl.Result{}, err } // 如果有一台机器属于控制平面节点并且node已就绪则置控制平面初始化condition为true for _, m := range machines { if util.IsControlPlaneMachine(m) && m.Status.NodeRef != nil { conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition) return ctrl.Result{}, nil } } conditions.MarkFalse(cluster, clusterv1.ControlPlaneInitializedCondition, clusterv1.MissingNodeRefReason, clusterv1.ConditionSeverityInfo, "Waiting for the first control plane machine to have its status.nodeRef set") return ctrl.Result{}, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 3.machine分析
# 3.1.machine调谐
machine_controller也会初始化部分数据,包括client、eventRecorder、tracker等,其主要监听cluster和machine用于触发入队协调。func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { // 获取machine对象 m := &clusterv1.Machine{} if err := r.Client.Get(ctx, req.NamespacedName, m); err != nil { if apierrors.IsNotFound(err) { // Object not found, return. Created objects are automatically garbage collected. // For additional cleanup logic use finalizers. return ctrl.Result{}, nil } // Error reading the object - requeue the request. return ctrl.Result{}, err } // log日志添加owner标识,包括machine关联的controlplane、machineset和machinedeployment ctx, log, err := clog.AddOwners(ctx, r.Client, m) if err != nil { return ctrl.Result{}, err } log = log.WithValues("Cluster", klog.KRef(m.ObjectMeta.Namespace, m.Spec.ClusterName)) ctx = ctrl.LoggerInto(ctx, log) // 获取集群cluster对象 cluster, err := util.GetClusterByName(ctx, r.Client, m.ObjectMeta.Namespace, m.Spec.ClusterName) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to get cluster %q for machine %q in namespace %q", m.Spec.ClusterName, m.Name, m.Namespace) } // 如果集群暂停,停止协调 if annotations.IsPaused(cluster, m) { log.Info("Reconciliation is paused for this object") return ctrl.Result{}, nil } // 初始化patchHelper patchHelper, err := patch.NewHelper(m, r.Client) if err != nil { return ctrl.Result{}, err } defer func() { // 协调status.phase r.reconcilePhase(ctx, m) // 没有err时收集 ObservedGeneration patchOpts := []patch.Option{} if reterr == nil { patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{}) } // patch最新的machine对象数据 if err := patchMachine(ctx, patchHelper, m, patchOpts...); err != nil { reterr = kerrors.NewAggregate([]error{reterr, err}) } }() // 初始化labels if m.Labels == nil { m.Labels = make(map[string]string) } m.Labels[clusterv1.ClusterNameLabel] = m.Spec.ClusterName // 没有finalizer时添加finalizer if !controllerutil.ContainsFinalizer(m, clusterv1.MachineFinalizer) { controllerutil.AddFinalizer(m, clusterv1.MachineFinalizer) return ctrl.Result{}, nil } // 如果机器删除,走删除协调 if !m.ObjectMeta.DeletionTimestamp.IsZero() { res, err := r.reconcileDelete(ctx, cluster, m) // Requeue if the reconcile failed because the ClusterCacheTracker was locked for // the current cluster because of concurrent access. if errors.Is(err, remote.ErrClusterLocked) { log.V(5).Info("Requeuing because another worker has the lock on the ClusterCacheTracker") return ctrl.Result{Requeue: true}, nil } return res, err } // 否则走创建协调 res, err := r.reconcile(ctx, cluster, m) // Requeue if the reconcile failed because the ClusterCacheTracker was locked for // the current cluster because of concurrent access. if errors.Is(err, remote.ErrClusterLocked) { log.V(5).Info("Requeuing because another worker has the lock on the ClusterCacheTracker") return ctrl.Result{Requeue: true}, nil } return res, err }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91machine_controller协调时会获取machine对象,然后校验并填充一些补充数据,包括label、finalizer、status,然后根据机器的删除状态进行相关工作。func (r *Reconciler) reconcile(ctx context.Context, cluster *clusterv1.Cluster, m *clusterv1.Machine) (ctrl.Result, error) { // 如果machine对象是孤儿节点,没有直接关联的上由machinedeployment,直接设置ownerRef为cluster if r.shouldAdopt(m) { m.SetOwnerReferences(util.EnsureOwnerRef(m.GetOwnerReferences(), metav1.OwnerReference{ APIVersion: clusterv1.GroupVersion.String(), Kind: "Cluster", Name: cluster.Name, UID: cluster.UID, })) } // 定义协调流程 phases := []func(context.Context, *clusterv1.Cluster, *clusterv1.Machine) (ctrl.Result, error){ r.reconcileBootstrap, r.reconcileInfrastructure, r.reconcileNode, r.reconcileInterruptibleNodeLabel, r.reconcileCertificateExpiry, } res := ctrl.Result{} errs := []error{} for _, phase := range phases { // 依次执行协调流程 phaseResult, err := phase(ctx, cluster, m) if err != nil { errs = append(errs, err) } if len(errs) > 0 { continue } res = util.LowestNonZeroResult(res, phaseResult) } return res, kerrors.NewAggregate(errs) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 3.2.reconcileBootstrap
func (r *Reconciler) reconcileBootstrap(ctx context.Context, cluster *clusterv1.Cluster, m *clusterv1.Machine) (ctrl.Result, error) { log := ctrl.LoggerFrom(ctx) // machine对象未定义spec.bootstrap.configRef,停止协调 if m.Spec.Bootstrap.ConfigRef == nil { return ctrl.Result{}, nil } // 获取外部定义的bootstrap对象 externalResult, err := r.reconcileExternal(ctx, cluster, m, m.Spec.Bootstrap.ConfigRef) if err != nil { return ctrl.Result{}, err } // 如果外部对象暂停,停止协调 if externalResult.Paused { return ctrl.Result{}, nil } if externalResult.RequeueAfter > 0 { return ctrl.Result{RequeueAfter: externalResult.RequeueAfter}, nil } // 如果bootstrap的dataSecret存在,更新bootstrap协调状态 if m.Spec.Bootstrap.DataSecretName != nil { m.Status.BootstrapReady = true conditions.MarkTrue(m, clusterv1.BootstrapReadyCondition) return ctrl.Result{}, nil } bootstrapConfig := externalResult.Result // 判断bootstrap是否删除 if !bootstrapConfig.GetDeletionTimestamp().IsZero() { return ctrl.Result{}, nil } // 判断bootstrap是否ready ready, err := external.IsReady(bootstrapConfig) if err != nil { return ctrl.Result{}, err } // 汇总当前机器关于bootstrap的状态 conditions.SetMirror(m, clusterv1.BootstrapReadyCondition, conditions.UnstructuredGetter(bootstrapConfig), conditions.WithFallbackValue(ready, clusterv1.WaitingForDataSecretFallbackReason, clusterv1.ConditionSeverityInfo, ""), ) // 如果bootstrap没有ready,停止协调(r.reconcileExternal已经加了外部监听器) if !ready { log.Info("Waiting for bootstrap provider to generate data secret and report status.ready", bootstrapConfig.GetKind(), klog.KObj(bootstrapConfig)) return ctrl.Result{RequeueAfter: externalReadyWait}, nil } // 拿到bootstrap的dataSecretName secretName, _, err := unstructured.NestedString(bootstrapConfig.Object, "status", "dataSecretName") if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve dataSecretName from bootstrap provider for Machine %q in namespace %q", m.Name, m.Namespace) } else if secretName == "" { return ctrl.Result{}, errors.Errorf("retrieved empty dataSecretName from bootstrap provider for Machine %q in namespace %q", m.Name, m.Namespace) } // machine回填dataSecretName m.Spec.Bootstrap.DataSecretName = pointer.String(secretName) if !m.Status.BootstrapReady { log.Info("Bootstrap provider generated data secret and reports status.ready", bootstrapConfig.GetKind(), klog.KObj(bootstrapConfig), "Secret", klog.KRef(m.Namespace, secretName)) } m.Status.BootstrapReady = true return ctrl.Result{}, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69reconcileBootstrap主要用于监听bootstrap状态,然后回填数据到machine对象。
# 3.3.reconcileInfrastructure
func (r *Reconciler) reconcileInfrastructure(ctx context.Context, cluster *clusterv1.Cluster, m *clusterv1.Machine) (ctrl.Result, error) { log := ctrl.LoggerFrom(ctx) // 获取外部的upimachine设施 infraReconcileResult, err := r.reconcileExternal(ctx, cluster, m, &m.Spec.InfrastructureRef) if err != nil { return ctrl.Result{}, err } if infraReconcileResult.RequeueAfter > 0 { ... } // 外部upimachine暂停,停止协调 if infraReconcileResult.Paused { return ctrl.Result{}, nil } infraConfig := infraReconcileResult.Result // 外部upimachine删除,停止协调 if !infraConfig.GetDeletionTimestamp().IsZero() { return ctrl.Result{}, nil } // 如果upimachine就绪,回填状态 ready, err := external.IsReady(infraConfig) if err != nil { return ctrl.Result{}, err } if ready && !m.Status.InfrastructureReady { log.Info("Infrastructure provider has completed machine infrastructure provisioning and reports status.ready", infraConfig.GetKind(), klog.KObj(infraConfig)) } m.Status.InfrastructureReady = ready // 汇总当前机器关于infrastructure的就绪状态 conditions.SetMirror(m, clusterv1.InfrastructureReadyCondition, conditions.UnstructuredGetter(infraConfig), conditions.WithFallbackValue(ready, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, ""), ) // 如果upimachine不是ready的,停止协调(获取外部对象时已经额外加了监听) if !ready { log.Info("Waiting for infrastructure provider to create machine infrastructure and report status.ready", infraConfig.GetKind(), klog.KObj(infraConfig)) return ctrl.Result{RequeueAfter: externalReadyWait}, nil } // 获取upimachine的providerID var providerID string if err := util.UnstructuredUnmarshalField(infraConfig, &providerID, "spec", "providerID"); err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Spec.ProviderID from infrastructure provider for Machine %q in namespace %q", m.Name, m.Namespace) } else if providerID == "" { return ctrl.Result{}, errors.Errorf("retrieved empty Spec.ProviderID from infrastructure provider for Machine %q in namespace %q", m.Name, m.Namespace) } // 获取upimachine的address及回填 err = util.UnstructuredUnmarshalField(infraConfig, &m.Status.Addresses, "status", "addresses") if err != nil && err != util.ErrUnstructuredFieldNotFound { return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve addresses from infrastructure provider for Machine %q in namespace %q", m.Name, m.Namespace) } // 获取故障域及回填 var failureDomain string err = util.UnstructuredUnmarshalField(infraConfig, &failureDomain, "spec", "failureDomain") switch { case err == util.ErrUnstructuredFieldNotFound: // no-op case err != nil: return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve failure domain from infrastructure provider for Machine %q in namespace %q", m.Name, m.Namespace) default: m.Spec.FailureDomain = pointer.String(failureDomain) } // 回填providerID m.Spec.ProviderID = pointer.String(providerID) return ctrl.Result{}, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72reconcileInfrastructure主要用于监听upimachine的状态并向machine回填数据,如果upimachine未就绪的话则会停止协调,等待upimachine状态变化。其中,worker节点的machine、upimachine及bootstrap都是machineset创建的,master节点的machine、upimachine及bootstrap创建则由controlplane管理,而非machinedeployment或machineset。
# 3.4.reconcileNode
func (r *Reconciler) reconcileNode(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) (ctrl.Result, error) { log := ctrl.LoggerFrom(ctx) // 添加业务集群的node监听器 if err := r.watchClusterNodes(ctx, cluster); err != nil { return ctrl.Result{}, err } // machine缺失providerID说明upimachine未就绪 if machine.Spec.ProviderID == nil || *machine.Spec.ProviderID == "" { log.Info("Waiting for infrastructure provider to report spec.providerID", machine.Spec.InfrastructureRef.Kind, klog.KRef(machine.Spec.InfrastructureRef.Namespace, machine.Spec.InfrastructureRef.Name)) conditions.MarkFalse(machine, clusterv1.MachineNodeHealthyCondition, clusterv1.WaitingForNodeRefReason, clusterv1.ConditionSeverityInfo, "") return ctrl.Result{}, nil } // 获取业务集群的client-go客户端,用于获取集群对象 remoteClient, err := r.Tracker.GetClient(ctx, util.ObjectKey(cluster)) if err != nil { return ctrl.Result{}, err } // 获取业务集群的node node, err := r.getNode(ctx, remoteClient, *machine.Spec.ProviderID) if err != nil { ... return ctrl.Result{}, err } // machine对象的nodeRef为空,设置nodeRef if machine.Status.NodeRef == nil { machine.Status.NodeRef = &corev1.ObjectReference{ Kind: node.Kind, APIVersion: node.APIVersion, Name: node.Name, UID: node.UID, } ... r.recorder.Event(machine, corev1.EventTypeNormal, "SuccessfulSetNodeRef", machine.Status.NodeRef.Name) } // 设置nodeInfo machine.Status.NodeInfo = &node.Status.NodeInfo // 设置node的label和annotation nodeAnnotations := map[string]string{ clusterv1.ClusterNameAnnotation: machine.Spec.ClusterName, clusterv1.ClusterNamespaceAnnotation: machine.GetNamespace(), clusterv1.MachineAnnotation: machine.Name, } if owner := metav1.GetControllerOfNoCopy(machine); owner != nil { nodeAnnotations[clusterv1.OwnerKindAnnotation] = owner.Kind nodeAnnotations[clusterv1.OwnerNameAnnotation] = owner.Name } nodeLabels := getManagedLabels(machine.Labels) if err := r.patchNode(ctx, remoteClient, node, nodeLabels, nodeAnnotations); err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to reconcile Node %s", klog.KObj(node)) } // 检查node健康状态,回填到machine的condition status, message := summarizeNodeConditions(node) if status == corev1.ConditionFalse { conditions.MarkFalse(machine, clusterv1.MachineNodeHealthyCondition, clusterv1.NodeConditionsFailedReason, clusterv1.ConditionSeverityWarning, message) return ctrl.Result{}, nil } if status == corev1.ConditionUnknown { conditions.MarkUnknown(machine, clusterv1.MachineNodeHealthyCondition, clusterv1.NodeConditionsFailedReason, message) return ctrl.Result{}, nil } conditions.MarkTrue(machine, clusterv1.MachineNodeHealthyCondition) return ctrl.Result{}, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73reconcileNode用于在集群控制平面初始化完成后监听业务集群Node对象状态,然后将Node引用相关信息回填到machine上。其中,watchClusterNodes会添加Node监听器,machine、Node状态变化都会触发machine入队。func (r *Reconciler) watchClusterNodes(ctx context.Context, cluster *clusterv1.Cluster) error { log := ctrl.LoggerFrom(ctx) if !conditions.IsTrue(cluster, clusterv1.ControlPlaneInitializedCondition) { log.V(5).Info("Skipping node watching setup because control plane is not initialized") return nil } // 协调器初始化时未保存manager资源,无法注册监听器,停止协调 if r.Tracker == nil { return nil } return r.Tracker.Watch(ctx, remote.WatchInput{ Name: "machine-watchNodes", Cluster: util.ObjectKey(cluster), Watcher: r.controller, Kind: &corev1.Node{}, EventHandler: handler.EnqueueRequestsFromMapFunc(r.nodeToMachine), }) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.5.reconcileInterruptibleNodeLabel
func (r *Reconciler) reconcileInterruptibleNodeLabel(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) (ctrl.Result, error) { // 检查machine是否删除,是否关联Node信息,否则停止协调 if !machine.DeletionTimestamp.IsZero() || machine.Status.NodeRef == nil { return ctrl.Result{}, nil } // 获取upimachine对象 infra, err := external.Get(ctx, r.Client, &machine.Spec.InfrastructureRef, machine.Namespace) if err != nil { return ctrl.Result{}, err } log := ctrl.LoggerFrom(ctx) // 获取upimachine上的中断状态 interruptible, _, err := unstructured.NestedBool(infra.Object, "status", "interruptible") if err != nil { ... return ctrl.Result{}, nil } // upimachine没有中断状态或未中断,结束协调 if !interruptible { return ctrl.Result{}, nil } // 否则获取业务集群的client-go客户端 remoteClient, err := r.Tracker.GetClient(ctx, util.ObjectKey(cluster)) if err != nil { return ctrl.Result{}, err } // 向业务集群对应node设置中断状态 if err := r.setInterruptibleNodeLabel(ctx, remoteClient, machine.Status.NodeRef.Name); err != nil { return ctrl.Result{}, err } r.recorder.Event(machine, corev1.EventTypeNormal, "SuccessfulSetInterruptibleNodeLabel", machine.Status.NodeRef.Name) return ctrl.Result{}, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 3.6.reconcileCertificateExpiry
func (r *Reconciler) reconcileCertificateExpiry(ctx context.Context, _ *clusterv1.Cluster, m *clusterv1.Machine) (ctrl.Result, error) { var annotations map[string]string // 如果当前机器不属于控制节点,停止处理证书相关 if !util.IsControlPlaneMachine(m) { // If the machine is not a control plane machine, return early. return ctrl.Result{}, nil } var expiryInfoFound bool // 检查证书过期时间 annotations = m.GetAnnotations() if expiry, ok := annotations[clusterv1.MachineCertificatesExpiryDateAnnotation]; ok { expiryInfoFound = true expiryTime, err := time.Parse(time.RFC3339, expiry) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to reconcile certificates expiry: failed to parse expiry date from annotation on %s", klog.KObj(m)) } // 更新过期时间为当前时间 expTime := metav1.NewTime(expiryTime) m.Status.CertificatesExpiryDate = &expTime } else if m.Spec.Bootstrap.ConfigRef != nil { // 获取bootstrap对象 bootstrapConfig, err := external.Get(ctx, r.Client, m.Spec.Bootstrap.ConfigRef, m.Namespace) if err != nil { return ctrl.Result{}, errors.Wrap(err, "failed to reconcile certificates expiry") } // 从bootstrap的annotation检查证书过期时间并更新到machine annotations = bootstrapConfig.GetAnnotations() if expiry, ok := annotations[clusterv1.MachineCertificatesExpiryDateAnnotation]; ok { expiryInfoFound = true expiryTime, err := time.Parse(time.RFC3339, expiry) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to reconcile certificates expiry: failed to parse expiry date from annotation on %s", klog.KObj(bootstrapConfig)) } expTime := metav1.NewTime(expiryTime) m.Status.CertificatesExpiryDate = &expTime } } // 如果没找到证书过期时间相关信息则置空 if !expiryInfoFound { m.Status.CertificatesExpiryDate = nil } return ctrl.Result{}, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 4.总结
--- upicluster spec.ControlPlaneEndpoint定义供cluster读取 status.failureDomains定义供cluster读取 status.ready定义供cluster读取 --- upi controlplane status.ready定义供cluster读取 controlplane必须管理自己的kubeconfig --- upibootstrap status.ready定义供machine读取 status.dataSecretName定义供machine读取 annotation包含证书过期时间供machine回填 --- upimachine status.ready定义供machine读取 spec.providerID定义供machine读取 status.addresses定义供machine读取 spec.failureDomain定义供machine读取 status.interruptible(bool)定义供machine读取1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20