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 }