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  }