@@ -148,7 +148,7 @@ func (l *loadbalancers) shareIPs(ctx context.Context, addrs []string, node *v1.N
148
148
// perform IP sharing (via a specified node selector) have the expected IPs shared
149
149
// in the event that a Node joins the cluster after the LoadBalancer Service already
150
150
// exists
151
- func (l * loadbalancers ) handleIPSharing (ctx context.Context , node * v1.Node ) error {
151
+ func (l * loadbalancers ) handleIPSharing (ctx context.Context , node * v1.Node , ipHolderSuffix string ) error {
152
152
// ignore cases where the provider ID has been set
153
153
if node .Spec .ProviderID == "" {
154
154
klog .Info ("skipping IP while providerID is unset" )
@@ -182,7 +182,7 @@ func (l *loadbalancers) handleIPSharing(ctx context.Context, node *v1.Node) erro
182
182
// if any of the addrs don't exist on the ip-holder (e.g. someone manually deleted it outside the CCM),
183
183
// we need to exclude that from the list
184
184
// TODO: also clean up the CiliumLoadBalancerIPPool for that missing IP if that happens
185
- ipHolder , err := l .getIPHolder (ctx )
185
+ ipHolder , err := l .getIPHolder (ctx , ipHolderSuffix )
186
186
if err != nil {
187
187
return err
188
188
}
@@ -207,8 +207,8 @@ func (l *loadbalancers) handleIPSharing(ctx context.Context, node *v1.Node) erro
207
207
208
208
// createSharedIP requests an additional IP that can be shared on Nodes to support
209
209
// loadbalancing via Cilium LB IPAM + BGP Control Plane.
210
- func (l * loadbalancers ) createSharedIP (ctx context.Context , nodes []* v1.Node ) (string , error ) {
211
- ipHolder , err := l .ensureIPHolder (ctx )
210
+ func (l * loadbalancers ) createSharedIP (ctx context.Context , nodes []* v1.Node , ipHolderSuffix string ) (string , error ) {
211
+ ipHolder , err := l .ensureIPHolder (ctx , ipHolderSuffix )
212
212
if err != nil {
213
213
return "" , err
214
214
}
@@ -276,7 +276,20 @@ func (l *loadbalancers) deleteSharedIP(ctx context.Context, service *v1.Service)
276
276
return err
277
277
}
278
278
bgpNodes := nodeList .Items
279
- ipHolder , err := l .getIPHolder (ctx )
279
+
280
+ serviceNn := getServiceNn (service )
281
+ var ipHolderSuffix string
282
+ if Options .IpHolderSuffix != "" {
283
+ ipHolderSuffix = Options .IpHolderSuffix
284
+ klog .V (3 ).Infof ("using parameter-based IP Holder suffix %s for Service %s" , ipHolderSuffix , serviceNn )
285
+ } else {
286
+ if service .Namespace != "" {
287
+ ipHolderSuffix = service .Namespace
288
+ klog .V (3 ).Infof ("using service-based IP Holder suffix %s for Service %s" , ipHolderSuffix , serviceNn )
289
+ }
290
+ }
291
+
292
+ ipHolder , err := l .getIPHolder (ctx , ipHolderSuffix )
280
293
if err != nil {
281
294
// return error or nil if not found since no IP holder means there
282
295
// is no IP to reclaim
@@ -310,48 +323,87 @@ func (l *loadbalancers) deleteSharedIP(ctx context.Context, service *v1.Service)
310
323
311
324
// To hold the IP in lieu of a proper IP reservation system, a special Nanode is
312
325
// created but not booted and used to hold all shared IPs.
313
- func (l * loadbalancers ) ensureIPHolder (ctx context.Context ) (* linodego.Instance , error ) {
314
- ipHolder , err := l .getIPHolder (ctx )
326
+ func (l * loadbalancers ) ensureIPHolder (ctx context.Context , suffix string ) (* linodego.Instance , error ) {
327
+ ipHolder , err := l .getIPHolder (ctx , suffix )
315
328
if err != nil {
316
329
return nil , err
317
330
}
318
331
if ipHolder != nil {
319
332
return ipHolder , nil
320
333
}
321
-
334
+ label := generateClusterScopedIPHolderLinodeName ( l . zone , suffix )
322
335
ipHolder , err = l .client .CreateInstance (ctx , linodego.InstanceCreateOptions {
323
336
Region : l .zone ,
324
337
Type : "g6-nanode-1" ,
325
- Label : fmt . Sprintf ( "%s-%s" , ipHolderLabelPrefix , l . zone ) ,
338
+ Label : label ,
326
339
RootPass : uuid .NewString (),
327
340
Image : "linode/ubuntu22.04" ,
328
341
Booted : ptr .To (false ),
329
342
})
330
343
if err != nil {
344
+ lerr := linodego .NewError (err )
345
+ if lerr .Code == http .StatusConflict {
346
+ // TODO (rk): should we handle more status codes on error?
347
+ klog .Errorf ("failed to create new IP Holder instance %s since it already exists: %s" , label , err .Error ())
348
+ return nil , err
349
+ }
331
350
return nil , err
332
351
}
352
+ klog .Infof ("created new IP Holder instance %s" , label )
333
353
334
354
return ipHolder , nil
335
355
}
336
356
337
- func (l * loadbalancers ) getIPHolder (ctx context.Context ) (* linodego.Instance , error ) {
357
+ func (l * loadbalancers ) getIPHolder (ctx context.Context , suffix string ) (* linodego.Instance , error ) {
358
+ // even though we have updated the naming convention, leaving this in ensures we have backwards compatibility
338
359
filter := map [string ]string {"label" : fmt .Sprintf ("%s-%s" , ipHolderLabelPrefix , l .zone )}
339
360
rawFilter , err := json .Marshal (filter )
340
361
if err != nil {
341
362
panic ("this should not have failed" )
342
363
}
343
364
var ipHolder * linodego.Instance
365
+ // TODO (rk): should we switch to using GET instead of LIST? we would be able to wrap logic around errors
344
366
linodes , err := l .client .ListInstances (ctx , linodego .NewListOptions (1 , string (rawFilter )))
345
367
if err != nil {
346
368
return nil , err
347
369
}
370
+ if len (linodes ) == 0 {
371
+ // since a list that returns 0 results has a 200/OK status code (no error)
372
+
373
+ // we assume that either
374
+ // a) an ip holder instance does not exist yet
375
+ // or
376
+ // b) another cluster already holds the linode grant to an ip holder using the old naming convention
377
+ filter = map [string ]string {"label" : generateClusterScopedIPHolderLinodeName (l .zone , suffix )}
378
+ rawFilter , err = json .Marshal (filter )
379
+ if err != nil {
380
+ panic ("this should not have failed" )
381
+ }
382
+ linodes , err = l .client .ListInstances (ctx , linodego .NewListOptions (1 , string (rawFilter )))
383
+ if err != nil {
384
+ return nil , err
385
+ }
386
+ }
348
387
if len (linodes ) > 0 {
349
388
ipHolder = & linodes [0 ]
350
389
}
351
-
352
390
return ipHolder , nil
353
391
}
354
392
393
+ // generateClusterScopedIPHolderLinodeName attempts to generate a unique name for the IP Holder
394
+ // instance used alongside Cilium LoadBalancers and Shared IPs for Kubernetes Services.
395
+ // The `suffix` is set to either value of the `--ip-holder-suffix` parameter, if it is set.
396
+ // The backup method involves keying off the service namespace.
397
+ // While it _is_ possible to have an empty suffix passed in, it would mean that kubernetes
398
+ // allowed the creation of a service without a namespace, which is highly improbable.
399
+ func generateClusterScopedIPHolderLinodeName (zone , suffix string ) string {
400
+ // since Linode CCM consumers are varied, we require a method of providing a
401
+ // suffix that does not rely on the use of a specific product (ex. LKE) to
402
+ // have a specific piece of metadata (ex. annotation(s), label(s) ) present to key off of.
403
+ //
404
+ return fmt .Sprintf ("%s-%s-%s" , ipHolderLabelPrefix , zone , suffix )
405
+ }
406
+
355
407
func (l * loadbalancers ) retrieveCiliumClientset () error {
356
408
if l .ciliumClient != nil {
357
409
return nil
0 commit comments