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 }