k8s.io/kubernetes@v1.29.3/pkg/controller/nodeipam/ipam/cloud_cidr_allocator.go (about)

     1  //go:build !providerless
     2  // +build !providerless
     3  
     4  /*
     5  Copyright 2016 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package ipam
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"math/rand"
    26  	"net"
    27  	"sync"
    28  	"time"
    29  
    30  	"github.com/google/go-cmp/cmp"
    31  
    32  	"k8s.io/klog/v2"
    33  
    34  	v1 "k8s.io/api/core/v1"
    35  	"k8s.io/apimachinery/pkg/api/errors"
    36  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    37  	"k8s.io/apimachinery/pkg/types"
    38  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    39  	informers "k8s.io/client-go/informers/core/v1"
    40  	corelisters "k8s.io/client-go/listers/core/v1"
    41  	"k8s.io/client-go/tools/cache"
    42  	"k8s.io/client-go/tools/record"
    43  
    44  	clientset "k8s.io/client-go/kubernetes"
    45  	"k8s.io/client-go/kubernetes/scheme"
    46  	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
    47  	cloudprovider "k8s.io/cloud-provider"
    48  	nodeutil "k8s.io/component-helpers/node/util"
    49  	controllerutil "k8s.io/kubernetes/pkg/controller/util/node"
    50  	utiltaints "k8s.io/kubernetes/pkg/util/taints"
    51  	"k8s.io/legacy-cloud-providers/gce"
    52  	netutils "k8s.io/utils/net"
    53  )
    54  
    55  // nodeProcessingInfo tracks information related to current nodes in processing
    56  type nodeProcessingInfo struct {
    57  	retries int
    58  }
    59  
    60  // cloudCIDRAllocator allocates node CIDRs according to IP address aliases
    61  // assigned by the cloud provider. In this case, the allocation and
    62  // deallocation is delegated to the external provider, and the controller
    63  // merely takes the assignment and updates the node spec.
    64  type cloudCIDRAllocator struct {
    65  	client clientset.Interface
    66  	cloud  *gce.Cloud
    67  
    68  	// nodeLister is able to list/get nodes and is populated by the shared informer passed to
    69  	// NewCloudCIDRAllocator.
    70  	nodeLister corelisters.NodeLister
    71  	// nodesSynced returns true if the node shared informer has been synced at least once.
    72  	nodesSynced cache.InformerSynced
    73  
    74  	// Channel that is used to pass updating Nodes to the background.
    75  	// This increases the throughput of CIDR assignment by parallelization
    76  	// and not blocking on long operations (which shouldn't be done from
    77  	// event handlers anyway).
    78  	nodeUpdateChannel chan string
    79  	broadcaster       record.EventBroadcaster
    80  	recorder          record.EventRecorder
    81  
    82  	// Keep a set of nodes that are currectly being processed to avoid races in CIDR allocation
    83  	lock              sync.Mutex
    84  	nodesInProcessing map[string]*nodeProcessingInfo
    85  }
    86  
    87  var _ CIDRAllocator = (*cloudCIDRAllocator)(nil)
    88  
    89  // NewCloudCIDRAllocator creates a new cloud CIDR allocator.
    90  func NewCloudCIDRAllocator(logger klog.Logger, client clientset.Interface, cloud cloudprovider.Interface, nodeInformer informers.NodeInformer) (CIDRAllocator, error) {
    91  	if client == nil {
    92  		logger.Error(nil, "kubeClient is nil when starting cloud CIDR allocator")
    93  		klog.FlushAndExit(klog.ExitFlushTimeout, 1)
    94  	}
    95  
    96  	eventBroadcaster := record.NewBroadcaster()
    97  	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cidrAllocator"})
    98  
    99  	gceCloud, ok := cloud.(*gce.Cloud)
   100  	if !ok {
   101  		err := fmt.Errorf("cloudCIDRAllocator does not support %v provider", cloud.ProviderName())
   102  		return nil, err
   103  	}
   104  
   105  	ca := &cloudCIDRAllocator{
   106  		client:            client,
   107  		cloud:             gceCloud,
   108  		nodeLister:        nodeInformer.Lister(),
   109  		nodesSynced:       nodeInformer.Informer().HasSynced,
   110  		nodeUpdateChannel: make(chan string, cidrUpdateQueueSize),
   111  		broadcaster:       eventBroadcaster,
   112  		recorder:          recorder,
   113  		nodesInProcessing: map[string]*nodeProcessingInfo{},
   114  	}
   115  
   116  	nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
   117  		AddFunc: controllerutil.CreateAddNodeHandler(
   118  			func(node *v1.Node) error {
   119  				return ca.AllocateOrOccupyCIDR(logger, node)
   120  			}),
   121  		UpdateFunc: controllerutil.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error {
   122  			if newNode.Spec.PodCIDR == "" {
   123  				return ca.AllocateOrOccupyCIDR(logger, newNode)
   124  			}
   125  			// Even if PodCIDR is assigned, but NetworkUnavailable condition is
   126  			// set to true, we need to process the node to set the condition.
   127  			networkUnavailableTaint := &v1.Taint{Key: v1.TaintNodeNetworkUnavailable, Effect: v1.TaintEffectNoSchedule}
   128  			_, cond := controllerutil.GetNodeCondition(&newNode.Status, v1.NodeNetworkUnavailable)
   129  			if cond == nil || cond.Status != v1.ConditionFalse || utiltaints.TaintExists(newNode.Spec.Taints, networkUnavailableTaint) {
   130  				return ca.AllocateOrOccupyCIDR(logger, newNode)
   131  			}
   132  			return nil
   133  		}),
   134  		DeleteFunc: controllerutil.CreateDeleteNodeHandler(logger, func(node *v1.Node) error {
   135  			return ca.ReleaseCIDR(logger, node)
   136  		}),
   137  	})
   138  	logger.Info("Using cloud CIDR allocator", "provider", cloud.ProviderName())
   139  	return ca, nil
   140  }
   141  
   142  func (ca *cloudCIDRAllocator) Run(ctx context.Context) {
   143  	defer utilruntime.HandleCrash()
   144  
   145  	// Start event processing pipeline.
   146  	ca.broadcaster.StartStructuredLogging(0)
   147  	logger := klog.FromContext(ctx)
   148  	logger.Info("Sending events to api server")
   149  	ca.broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: ca.client.CoreV1().Events("")})
   150  	defer ca.broadcaster.Shutdown()
   151  
   152  	logger.Info("Starting cloud CIDR allocator")
   153  	defer logger.Info("Shutting down cloud CIDR allocator")
   154  
   155  	if !cache.WaitForNamedCacheSync("cidrallocator", ctx.Done(), ca.nodesSynced) {
   156  		return
   157  	}
   158  
   159  	for i := 0; i < cidrUpdateWorkers; i++ {
   160  		go ca.worker(ctx)
   161  	}
   162  
   163  	<-ctx.Done()
   164  }
   165  
   166  func (ca *cloudCIDRAllocator) worker(ctx context.Context) {
   167  	logger := klog.FromContext(ctx)
   168  	for {
   169  		select {
   170  		case workItem, ok := <-ca.nodeUpdateChannel:
   171  			if !ok {
   172  				logger.Info("Channel nodeCIDRUpdateChannel was unexpectedly closed")
   173  				return
   174  			}
   175  			if err := ca.updateCIDRAllocation(logger, workItem); err == nil {
   176  				logger.V(3).Info("Updated CIDR", "workItem", workItem)
   177  			} else {
   178  				logger.Error(err, "Error updating CIDR", "workItem", workItem)
   179  				if canRetry, timeout := ca.retryParams(logger, workItem); canRetry {
   180  					logger.V(2).Info("Retrying update on next period", "workItem", workItem, "timeout", timeout)
   181  					time.AfterFunc(timeout, func() {
   182  						// Requeue the failed node for update again.
   183  						ca.nodeUpdateChannel <- workItem
   184  					})
   185  					continue
   186  				}
   187  				logger.Error(nil, "Exceeded retry count, dropping from queue", "workItem", workItem)
   188  			}
   189  			ca.removeNodeFromProcessing(workItem)
   190  		case <-ctx.Done():
   191  			return
   192  		}
   193  	}
   194  }
   195  
   196  func (ca *cloudCIDRAllocator) insertNodeToProcessing(nodeName string) bool {
   197  	ca.lock.Lock()
   198  	defer ca.lock.Unlock()
   199  	if _, found := ca.nodesInProcessing[nodeName]; found {
   200  		return false
   201  	}
   202  	ca.nodesInProcessing[nodeName] = &nodeProcessingInfo{}
   203  	return true
   204  }
   205  
   206  func (ca *cloudCIDRAllocator) retryParams(logger klog.Logger, nodeName string) (bool, time.Duration) {
   207  	ca.lock.Lock()
   208  	defer ca.lock.Unlock()
   209  
   210  	entry, ok := ca.nodesInProcessing[nodeName]
   211  	if !ok {
   212  		logger.Error(nil, "Cannot get retryParams for node as entry does not exist", "node", klog.KRef("", nodeName))
   213  		return false, 0
   214  	}
   215  
   216  	count := entry.retries + 1
   217  	if count > updateMaxRetries {
   218  		return false, 0
   219  	}
   220  	ca.nodesInProcessing[nodeName].retries = count
   221  
   222  	return true, nodeUpdateRetryTimeout(count)
   223  }
   224  
   225  func nodeUpdateRetryTimeout(count int) time.Duration {
   226  	timeout := updateRetryTimeout
   227  	for i := 0; i < count && timeout < maxUpdateRetryTimeout; i++ {
   228  		timeout *= 2
   229  	}
   230  	if timeout > maxUpdateRetryTimeout {
   231  		timeout = maxUpdateRetryTimeout
   232  	}
   233  	return time.Duration(timeout.Nanoseconds()/2 + rand.Int63n(timeout.Nanoseconds()))
   234  }
   235  
   236  func (ca *cloudCIDRAllocator) removeNodeFromProcessing(nodeName string) {
   237  	ca.lock.Lock()
   238  	defer ca.lock.Unlock()
   239  	delete(ca.nodesInProcessing, nodeName)
   240  }
   241  
   242  // WARNING: If you're adding any return calls or defer any more work from this
   243  // function you have to make sure to update nodesInProcessing properly with the
   244  // disposition of the node when the work is done.
   245  func (ca *cloudCIDRAllocator) AllocateOrOccupyCIDR(logger klog.Logger, node *v1.Node) error {
   246  	if node == nil {
   247  		return nil
   248  	}
   249  	if !ca.insertNodeToProcessing(node.Name) {
   250  		logger.V(2).Info("Node is already in a process of CIDR assignment", "node", klog.KObj(node))
   251  		return nil
   252  	}
   253  
   254  	logger.V(4).Info("Putting node into the work queue", "node", klog.KObj(node))
   255  	ca.nodeUpdateChannel <- node.Name
   256  	return nil
   257  }
   258  
   259  // updateCIDRAllocation assigns CIDR to Node and sends an update to the API server.
   260  func (ca *cloudCIDRAllocator) updateCIDRAllocation(logger klog.Logger, nodeName string) error {
   261  	node, err := ca.nodeLister.Get(nodeName)
   262  	if err != nil {
   263  		if errors.IsNotFound(err) {
   264  			return nil // node no longer available, skip processing
   265  		}
   266  		logger.Error(err, "Failed while getting the node for updating Node.Spec.PodCIDR", "node", klog.KRef("", nodeName))
   267  		return err
   268  	}
   269  	if node.Spec.ProviderID == "" {
   270  		return fmt.Errorf("node %s doesn't have providerID", nodeName)
   271  	}
   272  
   273  	cidrStrings, err := ca.cloud.AliasRangesByProviderID(node.Spec.ProviderID)
   274  	if err != nil {
   275  		controllerutil.RecordNodeStatusChange(logger, ca.recorder, node, "CIDRNotAvailable")
   276  		return fmt.Errorf("failed to get cidr(s) from provider: %v", err)
   277  	}
   278  	if len(cidrStrings) == 0 {
   279  		controllerutil.RecordNodeStatusChange(logger, ca.recorder, node, "CIDRNotAvailable")
   280  		return fmt.Errorf("failed to allocate cidr: Node %v has no CIDRs", node.Name)
   281  	}
   282  	//Can have at most 2 ips (one for v4 and one for v6)
   283  	if len(cidrStrings) > 2 {
   284  		logger.Info("Got more than 2 ips, truncating to 2", "cidrStrings", cidrStrings)
   285  		cidrStrings = cidrStrings[:2]
   286  	}
   287  
   288  	cidrs, err := netutils.ParseCIDRs(cidrStrings)
   289  	if err != nil {
   290  		return fmt.Errorf("failed to parse strings %v as CIDRs: %v", cidrStrings, err)
   291  	}
   292  
   293  	needUpdate, err := needPodCIDRsUpdate(logger, node, cidrs)
   294  	if err != nil {
   295  		return fmt.Errorf("err: %v, CIDRS: %v", err, cidrStrings)
   296  	}
   297  	if needUpdate {
   298  		if node.Spec.PodCIDR != "" {
   299  			logger.Error(nil, "PodCIDR being reassigned", "node", klog.KObj(node), "podCIDRs", node.Spec.PodCIDRs, "cidrStrings", cidrStrings)
   300  			// We fall through and set the CIDR despite this error. This
   301  			// implements the same logic as implemented in the
   302  			// rangeAllocator.
   303  			//
   304  			// See https://github.com/kubernetes/kubernetes/pull/42147#discussion_r103357248
   305  		}
   306  		for i := 0; i < cidrUpdateRetries; i++ {
   307  			if err = nodeutil.PatchNodeCIDRs(ca.client, types.NodeName(node.Name), cidrStrings); err == nil {
   308  				logger.Info("Set the node PodCIDRs", "node", klog.KObj(node), "cidrStrings", cidrStrings)
   309  				break
   310  			}
   311  		}
   312  	}
   313  	if err != nil {
   314  		controllerutil.RecordNodeStatusChange(logger, ca.recorder, node, "CIDRAssignmentFailed")
   315  		logger.Error(err, "Failed to update the node PodCIDR after multiple attempts", "node", klog.KObj(node), "cidrStrings", cidrStrings)
   316  		return err
   317  	}
   318  
   319  	err = nodeutil.SetNodeCondition(ca.client, types.NodeName(node.Name), v1.NodeCondition{
   320  		Type:               v1.NodeNetworkUnavailable,
   321  		Status:             v1.ConditionFalse,
   322  		Reason:             "RouteCreated",
   323  		Message:            "NodeController create implicit route",
   324  		LastTransitionTime: metav1.Now(),
   325  	})
   326  	if err != nil {
   327  		logger.Error(err, "Error setting route status for the node", "node", klog.KObj(node))
   328  	}
   329  	return err
   330  }
   331  
   332  func needPodCIDRsUpdate(logger klog.Logger, node *v1.Node, podCIDRs []*net.IPNet) (bool, error) {
   333  	if node.Spec.PodCIDR == "" {
   334  		return true, nil
   335  	}
   336  	_, nodePodCIDR, err := netutils.ParseCIDRSloppy(node.Spec.PodCIDR)
   337  	if err != nil {
   338  		logger.Error(err, "Found invalid node.Spec.PodCIDR", "podCIDR", node.Spec.PodCIDR)
   339  		// We will try to overwrite with new CIDR(s)
   340  		return true, nil
   341  	}
   342  	nodePodCIDRs, err := netutils.ParseCIDRs(node.Spec.PodCIDRs)
   343  	if err != nil {
   344  		logger.Error(err, "Found invalid node.Spec.PodCIDRs", "podCIDRs", node.Spec.PodCIDRs)
   345  		// We will try to overwrite with new CIDR(s)
   346  		return true, nil
   347  	}
   348  
   349  	if len(podCIDRs) == 1 {
   350  		if cmp.Equal(nodePodCIDR, podCIDRs[0]) {
   351  			logger.V(4).Info("Node already has allocated CIDR. It matches the proposed one", "node", klog.KObj(node), "podCIDR", podCIDRs[0])
   352  			return false, nil
   353  		}
   354  	} else if len(nodePodCIDRs) == len(podCIDRs) {
   355  		if dualStack, _ := netutils.IsDualStackCIDRs(podCIDRs); !dualStack {
   356  			return false, fmt.Errorf("IPs are not dual stack")
   357  		}
   358  		for idx, cidr := range podCIDRs {
   359  			if !cmp.Equal(nodePodCIDRs[idx], cidr) {
   360  				return true, nil
   361  			}
   362  		}
   363  		logger.V(4).Info("Node already has allocated CIDRs. It matches the proposed one", "node", klog.KObj(node), "podCIDRs", podCIDRs)
   364  		return false, nil
   365  	}
   366  
   367  	return true, nil
   368  }
   369  
   370  func (ca *cloudCIDRAllocator) ReleaseCIDR(logger klog.Logger, node *v1.Node) error {
   371  	logger.V(2).Info("Node's PodCIDR will be released by external cloud provider (not managed by controller)",
   372  		"node", klog.KObj(node), "podCIDR", node.Spec.PodCIDR)
   373  	return nil
   374  }