sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/awsnode/cni.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 awsnode 18 19 import ( 20 "context" 21 "fmt" 22 23 amazoncni "github.com/aws/amazon-vpc-cni-k8s/pkg/apis/crd/v1alpha1" 24 appsv1 "k8s.io/api/apps/v1" 25 corev1 "k8s.io/api/core/v1" 26 apierrors "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/labels" 29 "k8s.io/apimachinery/pkg/types" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 33 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors" 34 "sigs.k8s.io/cluster-api-provider-aws/pkg/record" 35 ) 36 37 const ( 38 awsNodeName = "aws-node" 39 awsNodeNamespace = "kube-system" 40 ) 41 42 // ReconcileCNI will reconcile the CNI of a service. 43 func (s *Service) ReconcileCNI(ctx context.Context) error { 44 s.scope.Info("Reconciling aws-node DaemonSet in cluster", "cluster-name", s.scope.Name(), "cluster-namespace", s.scope.Namespace()) 45 46 remoteClient, err := s.scope.RemoteClient() 47 if err != nil { 48 s.scope.Error(err, "getting client for remote cluster") 49 return fmt.Errorf("getting client for remote cluster: %w", err) 50 } 51 52 if s.scope.DisableVPCCNI() { 53 if err := s.deleteCNI(ctx, remoteClient); err != nil { 54 return fmt.Errorf("disabling aws vpc cni: %w", err) 55 } 56 } 57 58 if s.scope.SecondaryCidrBlock() == nil { 59 return nil 60 } 61 62 var ds appsv1.DaemonSet 63 if err := remoteClient.Get(ctx, types.NamespacedName{Namespace: awsNodeNamespace, Name: awsNodeName}, &ds); err != nil { 64 if !apierrors.IsNotFound(err) { 65 return err 66 } 67 return ErrCNIMissing 68 } 69 70 sgs, err := s.getSecurityGroups() 71 if err != nil { 72 return err 73 } 74 75 metaLabels := map[string]string{ 76 "app.kubernetes.io/managed-by": "cluster-api-provider-aws", 77 "app.kubernetes.io/part-of": s.scope.Name(), 78 } 79 80 s.scope.Info("for each subnet", "cluster-name", s.scope.Name(), "cluster-namespace", s.scope.Namespace()) 81 for _, subnet := range s.secondarySubnets() { 82 var eniConfig amazoncni.ENIConfig 83 if err := remoteClient.Get(ctx, types.NamespacedName{Namespace: metav1.NamespaceSystem, Name: subnet.AvailabilityZone}, &eniConfig); err != nil { 84 if !apierrors.IsNotFound(err) { 85 return err 86 } 87 s.scope.Info("Creating ENIConfig", "cluster-name", s.scope.Name(), "cluster-namespace", s.scope.Namespace(), "subnet", subnet.ID, "availability-zone", subnet.AvailabilityZone) 88 eniConfig = amazoncni.ENIConfig{ 89 ObjectMeta: metav1.ObjectMeta{ 90 Namespace: metav1.NamespaceSystem, 91 Name: subnet.AvailabilityZone, 92 Labels: metaLabels, 93 }, 94 Spec: amazoncni.ENIConfigSpec{ 95 Subnet: subnet.ID, 96 SecurityGroups: sgs, 97 }, 98 } 99 100 if err := remoteClient.Create(ctx, &eniConfig, &client.CreateOptions{}); err != nil { 101 return err 102 } 103 } 104 105 s.scope.Info("Updating ENIConfig", "cluster-name", s.scope.Name(), "cluster-namespace", s.scope.Namespace(), "subnet", subnet.ID, "availability-zone", subnet.AvailabilityZone) 106 eniConfig.Spec = amazoncni.ENIConfigSpec{ 107 Subnet: subnet.ID, 108 SecurityGroups: sgs, 109 } 110 111 if err := remoteClient.Update(ctx, &eniConfig, &client.UpdateOptions{}); err != nil { 112 return err 113 } 114 } 115 116 // Removing any ENIConfig no longer needed 117 var eniConfigs amazoncni.ENIConfigList 118 err = remoteClient.List(ctx, &eniConfigs, &client.ListOptions{ 119 Namespace: metav1.NamespaceSystem, 120 LabelSelector: labels.SelectorFromSet(metaLabels), 121 }) 122 if err != nil { 123 return err 124 } 125 for _, eniConfig := range eniConfigs.Items { 126 matchFound := false 127 for _, subnet := range s.secondarySubnets() { 128 if eniConfig.Name == subnet.AvailabilityZone { 129 matchFound = true 130 break 131 } 132 } 133 134 if !matchFound { 135 oldEniConfig := eniConfig 136 s.scope.Info("Removing old ENIConfig", "cluster-name", s.scope.Name(), "cluster-namespace", s.scope.Namespace(), "eniConfig", oldEniConfig.Name) 137 if err := remoteClient.Delete(ctx, &oldEniConfig, &client.DeleteOptions{}); err != nil { 138 return err 139 } 140 } 141 } 142 143 s.scope.Info("updating containers", "cluster-name", s.scope.Name(), "cluster-namespace", s.scope.Namespace()) 144 for _, container := range ds.Spec.Template.Spec.Containers { 145 if container.Name == "aws-node" { 146 container.Env = append(s.filterEnv(container.Env), 147 corev1.EnvVar{ 148 Name: "AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG", 149 Value: "true", 150 }, 151 corev1.EnvVar{ 152 Name: "ENI_CONFIG_LABEL_DEF", 153 Value: "failure-domain.beta.kubernetes.io/zone", 154 }, 155 ) 156 } 157 } 158 159 return remoteClient.Update(ctx, &ds, &client.UpdateOptions{}) 160 } 161 162 func (s *Service) getSecurityGroups() ([]string, error) { 163 sgRoles := []infrav1.SecurityGroupRole{ 164 infrav1.SecurityGroupNode, 165 } 166 167 sgs := make([]string, 0, len(sgRoles)) 168 for _, sg := range sgRoles { 169 if _, ok := s.scope.SecurityGroups()[sg]; !ok { 170 return nil, awserrors.NewFailedDependency(fmt.Sprintf("%s security group not available", sg)) 171 } 172 sgs = append(sgs, s.scope.SecurityGroups()[sg].ID) 173 } 174 175 return sgs, nil 176 } 177 178 func (s *Service) filterEnv(env []corev1.EnvVar) []corev1.EnvVar { 179 var i int 180 for _, e := range env { 181 if e.Name == "ENI_CONFIG_LABEL_DEF" || e.Name == "AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG" { 182 continue 183 } 184 env[i] = e 185 i++ 186 } 187 return env[:i] 188 } 189 190 func (s *Service) deleteCNI(ctx context.Context, remoteClient client.Client) error { 191 s.scope.Info("Ensuring aws-node DaemonSet in cluster is deleted", "cluster-name", s.scope.Name(), "cluster-namespace", s.scope.Namespace()) 192 193 ds := &appsv1.DaemonSet{} 194 if err := remoteClient.Get(ctx, types.NamespacedName{Namespace: awsNodeNamespace, Name: awsNodeName}, ds); err != nil { 195 if apierrors.IsNotFound(err) { 196 s.scope.V(2).Info("The aws-node DaemonSet is not found, not action") 197 return nil 198 } 199 return fmt.Errorf("getting aws-node daemonset: %w", err) 200 } 201 202 s.scope.V(2).Info("The aws-node DaemonSet found, deleting") 203 if err := remoteClient.Delete(ctx, ds, &client.DeleteOptions{}); err != nil { 204 if apierrors.IsNotFound(err) { 205 s.scope.V(2).Info("The aws-node DaemonSet is not found, not deleted") 206 return nil 207 } 208 return fmt.Errorf("deleting aws-node DaemonSet: %w", err) 209 } 210 record.Eventf(s.scope.InfraCluster(), "DeletedVPCCNI", "The AWS VPC CNI has been removed from the cluster. Ensure you enable a CNI via another mechanism") 211 212 return nil 213 }