sigs.k8s.io/cluster-api-provider-aws@v1.5.5/controlplane/eks/controllers/awsmanagedcontrolplane_controller.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controllers
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/pkg/errors"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	"k8s.io/client-go/tools/record"
    27  	ctrl "sigs.k8s.io/controller-runtime"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  	"sigs.k8s.io/controller-runtime/pkg/controller"
    30  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    31  	"sigs.k8s.io/controller-runtime/pkg/handler"
    32  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    33  	"sigs.k8s.io/controller-runtime/pkg/source"
    34  
    35  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    36  	ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1"
    37  	expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1"
    38  	"sigs.k8s.io/cluster-api-provider-aws/feature"
    39  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
    40  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/awsnode"
    41  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/ec2"
    42  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/eks"
    43  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/gc"
    44  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/iamauth"
    45  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/kubeproxy"
    46  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/network"
    47  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/securitygroup"
    48  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    49  	"sigs.k8s.io/cluster-api/util"
    50  	capiannotations "sigs.k8s.io/cluster-api/util/annotations"
    51  	"sigs.k8s.io/cluster-api/util/conditions"
    52  	"sigs.k8s.io/cluster-api/util/predicates"
    53  )
    54  
    55  const (
    56  	// deleteRequeueAfter is how long to wait before checking again to see if the control plane still
    57  	// has dependencies during deletion.
    58  	deleteRequeueAfter = 20 * time.Second
    59  )
    60  
    61  var defaultEKSSecurityGroupRoles = []infrav1.SecurityGroupRole{
    62  	infrav1.SecurityGroupEKSNodeAdditional,
    63  }
    64  
    65  // securityGroupRolesForControlPlane returns the security group roles determined by the control plane configuration.
    66  func securityGroupRolesForControlPlane(scope *scope.ManagedControlPlaneScope) []infrav1.SecurityGroupRole {
    67  	// Copy to ensure we do not modify the package-level variable.
    68  	roles := make([]infrav1.SecurityGroupRole, len(defaultEKSSecurityGroupRoles))
    69  	copy(roles, defaultEKSSecurityGroupRoles)
    70  
    71  	if scope.Bastion().Enabled {
    72  		roles = append(roles, infrav1.SecurityGroupBastion)
    73  	}
    74  	return roles
    75  }
    76  
    77  // AWSManagedControlPlaneReconciler reconciles a AWSManagedControlPlane object.
    78  type AWSManagedControlPlaneReconciler struct {
    79  	client.Client
    80  	Recorder  record.EventRecorder
    81  	Endpoints []scope.ServiceEndpoint
    82  
    83  	EnableIAM            bool
    84  	AllowAdditionalRoles bool
    85  	WatchFilterValue     string
    86  	ExternalResourceGC   bool
    87  }
    88  
    89  // SetupWithManager is used to setup the controller.
    90  func (r *AWSManagedControlPlaneReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
    91  	log := ctrl.LoggerFrom(ctx)
    92  
    93  	awsManagedControlPlane := &ekscontrolplanev1.AWSManagedControlPlane{}
    94  	c, err := ctrl.NewControllerManagedBy(mgr).
    95  		For(awsManagedControlPlane).
    96  		WithOptions(options).
    97  		WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue)).
    98  		Build(r)
    99  
   100  	if err != nil {
   101  		return fmt.Errorf("failed setting up the AWSManagedControlPlane controller manager: %w", err)
   102  	}
   103  
   104  	if err = c.Watch(
   105  		&source.Kind{Type: &clusterv1.Cluster{}},
   106  		handler.EnqueueRequestsFromMapFunc(util.ClusterToInfrastructureMapFunc(awsManagedControlPlane.GroupVersionKind())),
   107  		predicates.ClusterUnpausedAndInfrastructureReady(log),
   108  	); err != nil {
   109  		return fmt.Errorf("failed adding a watch for ready clusters: %w", err)
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  // +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;patch
   116  // +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;delete;patch
   117  // +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch
   118  // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch
   119  // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinedeployments,verbs=get;list;watch
   120  // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinepools,verbs=get;list;watch
   121  // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsmachines;awsmachines/status,verbs=get;list;watch
   122  // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsmachinetemplates,verbs=get;list;watch
   123  // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsmanagedmachinepools;awsmanagedmachinepools/status,verbs=get;list;watch
   124  // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsmachinepools;awsmachinepools/status,verbs=get;list;watch
   125  // +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=awsmanagedcontrolplanes,verbs=get;list;watch;create;update;patch;delete
   126  // +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=awsmanagedcontrolplanes/status,verbs=get;update;patch
   127  // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsclusterroleidentities;awsclusterstaticidentities;awsclustercontrolleridentities,verbs=get;list;watch
   128  
   129  // Reconcile will reconcile AWSManagedControlPlane Resources.
   130  func (r *AWSManagedControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, reterr error) {
   131  	log := ctrl.LoggerFrom(ctx)
   132  
   133  	// Get the control plane instance
   134  	awsControlPlane := &ekscontrolplanev1.AWSManagedControlPlane{}
   135  	if err := r.Client.Get(ctx, req.NamespacedName, awsControlPlane); err != nil {
   136  		if apierrors.IsNotFound(err) {
   137  			return ctrl.Result{}, nil
   138  		}
   139  		return ctrl.Result{Requeue: true}, nil
   140  	}
   141  
   142  	// Get the cluster
   143  	cluster, err := util.GetOwnerCluster(ctx, r.Client, awsControlPlane.ObjectMeta)
   144  	if err != nil {
   145  		log.Error(err, "Failed to retrieve owner Cluster from the API Server")
   146  		return ctrl.Result{}, err
   147  	}
   148  	if cluster == nil {
   149  		log.Info("Cluster Controller has not yet set OwnerRef")
   150  		return ctrl.Result{}, nil
   151  	}
   152  
   153  	if capiannotations.IsPaused(cluster, awsControlPlane) {
   154  		log.Info("Reconciliation is paused for this object")
   155  		return ctrl.Result{}, nil
   156  	}
   157  
   158  	managedScope, err := scope.NewManagedControlPlaneScope(scope.ManagedControlPlaneScopeParams{
   159  		Client:               r.Client,
   160  		Cluster:              cluster,
   161  		ControlPlane:         awsControlPlane,
   162  		ControllerName:       "awsmanagedcontrolplane",
   163  		EnableIAM:            r.EnableIAM,
   164  		AllowAdditionalRoles: r.AllowAdditionalRoles,
   165  		Endpoints:            r.Endpoints,
   166  	})
   167  	if err != nil {
   168  		return reconcile.Result{}, fmt.Errorf("failed to create scope: %w", err)
   169  	}
   170  
   171  	// Always close the scope
   172  	defer func() {
   173  		applicableConditions := []clusterv1.ConditionType{
   174  			ekscontrolplanev1.EKSControlPlaneReadyCondition,
   175  			ekscontrolplanev1.IAMControlPlaneRolesReadyCondition,
   176  			ekscontrolplanev1.IAMAuthenticatorConfiguredCondition,
   177  			ekscontrolplanev1.EKSAddonsConfiguredCondition,
   178  			infrav1.VpcReadyCondition,
   179  			infrav1.SubnetsReadyCondition,
   180  			infrav1.ClusterSecurityGroupsReadyCondition,
   181  		}
   182  
   183  		if managedScope.VPC().IsManaged(managedScope.Name()) {
   184  			applicableConditions = append(applicableConditions,
   185  				infrav1.InternetGatewayReadyCondition,
   186  				infrav1.NatGatewaysReadyCondition,
   187  				infrav1.RouteTablesReadyCondition,
   188  			)
   189  			if managedScope.Bastion().Enabled {
   190  				applicableConditions = append(applicableConditions, infrav1.BastionHostReadyCondition)
   191  			}
   192  		}
   193  
   194  		conditions.SetSummary(managedScope.ControlPlane, conditions.WithConditions(applicableConditions...), conditions.WithStepCounter())
   195  
   196  		if err := managedScope.Close(); err != nil && reterr == nil {
   197  			reterr = err
   198  		}
   199  	}()
   200  
   201  	if !awsControlPlane.ObjectMeta.DeletionTimestamp.IsZero() {
   202  		// Handle deletion reconciliation loop.
   203  		return r.reconcileDelete(ctx, managedScope)
   204  	}
   205  
   206  	// Handle normal reconciliation loop.
   207  	return r.reconcileNormal(ctx, managedScope)
   208  }
   209  
   210  func (r *AWSManagedControlPlaneReconciler) reconcileNormal(ctx context.Context, managedScope *scope.ManagedControlPlaneScope) (res ctrl.Result, reterr error) {
   211  	managedScope.Info("Reconciling AWSManagedControlPlane")
   212  
   213  	awsManagedControlPlane := managedScope.ControlPlane
   214  
   215  	controllerutil.AddFinalizer(managedScope.ControlPlane, ekscontrolplanev1.ManagedControlPlaneFinalizer)
   216  	if err := managedScope.PatchObject(); err != nil {
   217  		return ctrl.Result{}, err
   218  	}
   219  
   220  	ec2Service := ec2.NewService(managedScope)
   221  	networkSvc := network.NewService(managedScope)
   222  	ekssvc := eks.NewService(managedScope)
   223  	sgService := securitygroup.NewService(managedScope, securityGroupRolesForControlPlane(managedScope))
   224  	authService := iamauth.NewService(managedScope, iamauth.BackendTypeConfigMap, managedScope.Client)
   225  	awsnodeService := awsnode.NewService(managedScope)
   226  	kubeproxyService := kubeproxy.NewService(managedScope)
   227  
   228  	if err := networkSvc.ReconcileNetwork(); err != nil {
   229  		return reconcile.Result{}, fmt.Errorf("failed to reconcile network for AWSManagedControlPlane %s/%s: %w", awsManagedControlPlane.Namespace, awsManagedControlPlane.Name, err)
   230  	}
   231  
   232  	if err := sgService.ReconcileSecurityGroups(); err != nil {
   233  		conditions.MarkFalse(awsManagedControlPlane, infrav1.ClusterSecurityGroupsReadyCondition, infrav1.ClusterSecurityGroupReconciliationFailedReason, clusterv1.ConditionSeverityError, err.Error())
   234  		return reconcile.Result{}, errors.Wrapf(err, "failed to reconcile general security groups for AWSManagedControlPlane %s/%s", awsManagedControlPlane.Namespace, awsManagedControlPlane.Name)
   235  	}
   236  
   237  	if err := ec2Service.ReconcileBastion(); err != nil {
   238  		conditions.MarkFalse(awsManagedControlPlane, infrav1.BastionHostReadyCondition, infrav1.BastionHostFailedReason, clusterv1.ConditionSeverityError, err.Error())
   239  		return reconcile.Result{}, fmt.Errorf("failed to reconcile bastion host for AWSManagedControlPlane %s/%s: %w", awsManagedControlPlane.Namespace, awsManagedControlPlane.Name, err)
   240  	}
   241  
   242  	if err := ekssvc.ReconcileControlPlane(ctx); err != nil {
   243  		return reconcile.Result{}, fmt.Errorf("failed to reconcile control plane for AWSManagedControlPlane %s/%s: %w", awsManagedControlPlane.Namespace, awsManagedControlPlane.Name, err)
   244  	}
   245  
   246  	if err := awsnodeService.ReconcileCNI(ctx); err != nil {
   247  		conditions.MarkFalse(managedScope.InfraCluster(), infrav1.SecondaryCidrsReadyCondition, infrav1.SecondaryCidrReconciliationFailedReason, clusterv1.ConditionSeverityError, err.Error())
   248  		return reconcile.Result{}, fmt.Errorf("failed to reconcile control plane for AWSManagedControlPlane %s/%s: %w", awsManagedControlPlane.Namespace, awsManagedControlPlane.Name, err)
   249  	}
   250  
   251  	if err := kubeproxyService.ReconcileKubeProxy(ctx); err != nil {
   252  		return reconcile.Result{}, fmt.Errorf("failed to reconcile control plane for AWSManagedControlPlane %s/%s: %w", awsManagedControlPlane.Namespace, awsManagedControlPlane.Name, err)
   253  	}
   254  
   255  	if err := authService.ReconcileIAMAuthenticator(ctx); err != nil {
   256  		conditions.MarkFalse(awsManagedControlPlane, ekscontrolplanev1.IAMAuthenticatorConfiguredCondition, ekscontrolplanev1.IAMAuthenticatorConfigurationFailedReason, clusterv1.ConditionSeverityError, err.Error())
   257  		return reconcile.Result{}, errors.Wrapf(err, "failed to reconcile aws-iam-authenticator config for AWSManagedControlPlane %s/%s", awsManagedControlPlane.Namespace, awsManagedControlPlane.Name)
   258  	}
   259  	conditions.MarkTrue(awsManagedControlPlane, ekscontrolplanev1.IAMAuthenticatorConfiguredCondition)
   260  
   261  	for _, subnet := range managedScope.Subnets().FilterPrivate() {
   262  		managedScope.SetFailureDomain(subnet.AvailabilityZone, clusterv1.FailureDomainSpec{
   263  			ControlPlane: true,
   264  		})
   265  	}
   266  
   267  	return reconcile.Result{}, nil
   268  }
   269  
   270  func (r *AWSManagedControlPlaneReconciler) reconcileDelete(ctx context.Context, managedScope *scope.ManagedControlPlaneScope) (_ ctrl.Result, reterr error) {
   271  	log := ctrl.LoggerFrom(ctx)
   272  
   273  	managedScope.Info("Reconciling AWSManagedControlPlane delete")
   274  
   275  	controlPlane := managedScope.ControlPlane
   276  
   277  	numDependencies, err := r.dependencyCount(ctx, managedScope)
   278  	if err != nil {
   279  		log.Error(err, "error getting controlplane dependencies", "namespace", controlPlane.Namespace, "name", controlPlane.Name)
   280  		return reconcile.Result{}, err
   281  	}
   282  	if numDependencies > 0 {
   283  		log.Info("EKS cluster still has dependencies - requeue needed", "dependencyCount", numDependencies)
   284  		return reconcile.Result{RequeueAfter: deleteRequeueAfter}, nil
   285  	}
   286  	log.Info("EKS cluster has no dependencies")
   287  
   288  	ekssvc := eks.NewService(managedScope)
   289  	ec2svc := ec2.NewService(managedScope)
   290  	networkSvc := network.NewService(managedScope)
   291  	sgService := securitygroup.NewService(managedScope, securityGroupRolesForControlPlane(managedScope))
   292  
   293  	if err := ekssvc.DeleteControlPlane(); err != nil {
   294  		log.Error(err, "error deleting EKS cluster for EKS control plane", "namespace", controlPlane.Namespace, "name", controlPlane.Name)
   295  		return reconcile.Result{}, err
   296  	}
   297  
   298  	if err := ec2svc.DeleteBastion(); err != nil {
   299  		log.Error(err, "error deleting bastion for AWSManagedControlPlane", "namespace", controlPlane.Namespace, "name", controlPlane.Name)
   300  		return reconcile.Result{}, err
   301  	}
   302  
   303  	if err := sgService.DeleteSecurityGroups(); err != nil {
   304  		log.Error(err, "error deleting general security groups for AWSManagedControlPlane", "namespace", controlPlane.Namespace, "name", controlPlane.Name)
   305  		return reconcile.Result{}, err
   306  	}
   307  
   308  	if r.ExternalResourceGC {
   309  		gcSvc := gc.NewService(managedScope)
   310  		if gcErr := gcSvc.ReconcileDelete(ctx); gcErr != nil {
   311  			return reconcile.Result{}, fmt.Errorf("failed delete reconcile for gc service: %w", gcErr)
   312  		}
   313  	}
   314  
   315  	if err := networkSvc.DeleteNetwork(); err != nil {
   316  		log.Error(err, "error deleting network for AWSManagedControlPlane", "namespace", controlPlane.Namespace, "name", controlPlane.Name)
   317  		return reconcile.Result{}, err
   318  	}
   319  
   320  	controllerutil.RemoveFinalizer(controlPlane, ekscontrolplanev1.ManagedControlPlaneFinalizer)
   321  
   322  	return reconcile.Result{}, nil
   323  }
   324  
   325  // ClusterToAWSManagedControlPlane is a handler.ToRequestsFunc to be used to enqueue requests for reconciliation
   326  // for AWSManagedControlPlane based on updates to a Cluster.
   327  func (r *AWSManagedControlPlaneReconciler) ClusterToAWSManagedControlPlane(o client.Object) []ctrl.Request {
   328  	c, ok := o.(*clusterv1.Cluster)
   329  	if !ok {
   330  		panic(fmt.Sprintf("Expected a Cluster but got a %T", o))
   331  	}
   332  
   333  	if !c.ObjectMeta.DeletionTimestamp.IsZero() {
   334  		return nil
   335  	}
   336  
   337  	controlPlaneRef := c.Spec.ControlPlaneRef
   338  	if controlPlaneRef != nil && controlPlaneRef.Kind == "AWSManagedControlPlane" {
   339  		return []ctrl.Request{{NamespacedName: client.ObjectKey{Namespace: controlPlaneRef.Namespace, Name: controlPlaneRef.Name}}}
   340  	}
   341  
   342  	return nil
   343  }
   344  
   345  func (r *AWSManagedControlPlaneReconciler) dependencyCount(ctx context.Context, managedScope *scope.ManagedControlPlaneScope) (int, error) {
   346  	log := ctrl.LoggerFrom(ctx)
   347  
   348  	clusterName := managedScope.Name()
   349  	namespace := managedScope.Namespace()
   350  	log.Info("looking for EKS cluster dependencies", "cluster", clusterName, "namespace", namespace)
   351  
   352  	listOptions := []client.ListOption{
   353  		client.InNamespace(namespace),
   354  		client.MatchingLabels(map[string]string{clusterv1.ClusterLabelName: clusterName}),
   355  	}
   356  
   357  	dependencies := 0
   358  
   359  	machines := &infrav1.AWSMachineList{}
   360  	if err := r.Client.List(ctx, machines, listOptions...); err != nil {
   361  		return dependencies, fmt.Errorf("failed to list machines for cluster %s/%s: %w", namespace, clusterName, err)
   362  	}
   363  	log.V(2).Info("tested for AWSMachine dependencies", "count", len(machines.Items))
   364  	dependencies += len(machines.Items)
   365  
   366  	if feature.Gates.Enabled(feature.MachinePool) {
   367  		managedMachinePools := &expinfrav1.AWSManagedMachinePoolList{}
   368  		if err := r.Client.List(ctx, managedMachinePools, listOptions...); err != nil {
   369  			return dependencies, fmt.Errorf("failed to list managed machine pools for cluster %s/%s: %w", namespace, clusterName, err)
   370  		}
   371  		log.V(2).Info("tested for AWSManagedMachinePool dependencies", "count", len(managedMachinePools.Items))
   372  		dependencies += len(managedMachinePools.Items)
   373  
   374  		machinePools := &expinfrav1.AWSMachinePoolList{}
   375  		if err := r.Client.List(ctx, machinePools, listOptions...); err != nil {
   376  			return dependencies, fmt.Errorf("failed to list machine pools for cluster %s/%s: %w", namespace, clusterName, err)
   377  		}
   378  		log.V(2).Info("tested for AWSMachinePool dependencies", "count", len(machinePools.Items))
   379  		dependencies += len(machinePools.Items)
   380  	}
   381  
   382  	return dependencies, nil
   383  }