Figure 1 presents the sequence of actions involved in provisioning a cluster, highlighting the coordination required between the CAPI cluster controller and the provider infrastructure controller.
The creation of the Cluster and provider infrastructure objects are independent events. It is expected that the cluster infrastructure object to be created before the cluster object and the reference be set to in the cluster object at creation time.
When a provider infrastructure object is created, the provider’s controller will do nothing unless its owner reference is set to a cluster object.
When the cluster object is created, the cluster controller will retrieve the infrastructure object. If the object has not been seen before, it will start watching it. Also, if the object’s owner is not set, it will set to the Cluster object.
When an infrastructure object is updated, the provider controller will check the owner reference. If it is set, it will retrieve the cluster object to obtain the required cluster specification and starts the provisioning process. When the process finishes, it sets the Infrastructure.Status.Ready to true.
When the cluster controller detects the Infrastructure.Status.Ready is set to true, it updates Cluster.Status.APIEndpoints from Infrastructure.Status.APIEndpoints and sets Cluster.Status.InfrastructureReady to true.
// There's no need to go any further if the Cluster is marked for deletion. if !infraConfig.GetDeletionTimestamp().IsZero() { returnnil }
// Determine if the infrastructure provider is ready. if !cluster.Status.InfrastructureReady { ready, err := external.IsReady(infraConfig) if err != nil { return err } elseif !ready { logger.V(3).Info("Infrastructure provider is not ready yet") returnnil } cluster.Status.InfrastructureReady = true }
// Get and parse Status.APIEndpoint field from the infrastructure provider. iflen(cluster.Status.APIEndpoints) == 0 { if err := util.UnstructuredUnmarshalField(infraConfig, &cluster.Status.APIEndpoints, "status", "apiEndpoints"); err != nil { return errors.Wrapf(err, "failed to retrieve Status.APIEndpoints from infrastructure provider for Cluster %q in namespace %q", cluster.Name, cluster.Namespace) } elseiflen(cluster.Status.APIEndpoints) == 0 { return errors.Wrapf(err, "retrieved empty Status.APIEndpoints from infrastructure provider for Cluster %q in namespace %q", cluster.Name, cluster.Namespace) } }
// TODO(ncdc): should this be a function on ClusterScope? funcreconcileNormal(clusterScope *scope.ClusterScope) (reconcile.Result, error) { clusterScope.Info("Reconciling AWSCluster")
awsCluster := clusterScope.AWSCluster
// If the AWSCluster doesn't have our finalizer, add it. if !util.Contains(awsCluster.Finalizers, infrav1.ClusterFinalizer) { awsCluster.Finalizers = append(awsCluster.Finalizers, infrav1.ClusterFinalizer) }
if err := ec2Service.ReconcileNetwork(); err != nil { // 基础网络组网 return reconcile.Result{}, errors.Wrapf(err, "failed to reconcile network for AWSCluster %s/%s", awsCluster.Namespace, awsCluster.Name) }
if err := ec2Service.ReconcileBastion(); err != nil { // vpc 跳板机的创建 return reconcile.Result{}, errors.Wrapf(err, "failed to reconcile bastion host for AWSCluster %s/%s", awsCluster.Namespace, awsCluster.Name) }
if err := elbService.ReconcileLoadbalancers(); err != nil { // apiserver 的 lb 创建 return reconcile.Result{}, errors.Wrapf(err, "failed to reconcile load balancers for AWSCluster %s/%s", awsCluster.Namespace, awsCluster.Name) }
if awsCluster.Status.Network.APIServerELB.DNSName == "" { clusterScope.Info("Waiting on API server ELB DNS name") return reconcile.Result{RequeueAfter: 15 * time.Second}, nil }
// Set APIEndpoints so the Cluster API Cluster Controller can pull them // TODO: should we get the Port from the first listener on the ELB? awsCluster.Status.APIEndpoints = []infrav1.APIEndpoint{ { Host: awsCluster.Status.Network.APIServerELB.DNSName, Port: int(clusterScope.APIServerPort()), }, }
// No errors, so mark us ready so the Cluster API Cluster Controller can pull it awsCluster.Status.Ready = true