@@ -148,7 +148,7 @@ func (l *loadbalancers) shareIPs(ctx context.Context, addrs []string, node *v1.N
148148// perform IP sharing (via a specified node selector) have the expected IPs shared
149149// in the event that a Node joins the cluster after the LoadBalancer Service already
150150// 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 {
152152 // ignore cases where the provider ID has been set
153153 if node .Spec .ProviderID == "" {
154154 klog .Info ("skipping IP while providerID is unset" )
@@ -182,7 +182,7 @@ func (l *loadbalancers) handleIPSharing(ctx context.Context, node *v1.Node) erro
182182 // if any of the addrs don't exist on the ip-holder (e.g. someone manually deleted it outside the CCM),
183183 // we need to exclude that from the list
184184 // 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 )
186186 if err != nil {
187187 return err
188188 }
@@ -207,8 +207,8 @@ func (l *loadbalancers) handleIPSharing(ctx context.Context, node *v1.Node) erro
207207
208208// createSharedIP requests an additional IP that can be shared on Nodes to support
209209// 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 )
212212 if err != nil {
213213 return "" , err
214214 }
@@ -276,7 +276,20 @@ func (l *loadbalancers) deleteSharedIP(ctx context.Context, service *v1.Service)
276276 return err
277277 }
278278 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 )
280293 if err != nil {
281294 // return error or nil if not found since no IP holder means there
282295 // is no IP to reclaim
@@ -310,48 +323,87 @@ func (l *loadbalancers) deleteSharedIP(ctx context.Context, service *v1.Service)
310323
311324// To hold the IP in lieu of a proper IP reservation system, a special Nanode is
312325// 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 )
315328 if err != nil {
316329 return nil , err
317330 }
318331 if ipHolder != nil {
319332 return ipHolder , nil
320333 }
321-
334+ label := generateClusterScopedIPHolderLinodeName ( l . zone , suffix )
322335 ipHolder , err = l .client .CreateInstance (ctx , linodego.InstanceCreateOptions {
323336 Region : l .zone ,
324337 Type : "g6-nanode-1" ,
325- Label : fmt . Sprintf ( "%s-%s" , ipHolderLabelPrefix , l . zone ) ,
338+ Label : label ,
326339 RootPass : uuid .NewString (),
327340 Image : "linode/ubuntu22.04" ,
328341 Booted : ptr .To (false ),
329342 })
330343 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+ }
331350 return nil , err
332351 }
352+ klog .Infof ("created new IP Holder instance %s" , label )
333353
334354 return ipHolder , nil
335355}
336356
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
338359 filter := map [string ]string {"label" : fmt .Sprintf ("%s-%s" , ipHolderLabelPrefix , l .zone )}
339360 rawFilter , err := json .Marshal (filter )
340361 if err != nil {
341362 panic ("this should not have failed" )
342363 }
343364 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
344366 linodes , err := l .client .ListInstances (ctx , linodego .NewListOptions (1 , string (rawFilter )))
345367 if err != nil {
346368 return nil , err
347369 }
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+ }
348387 if len (linodes ) > 0 {
349388 ipHolder = & linodes [0 ]
350389 }
351-
352390 return ipHolder , nil
353391}
354392
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+
355407func (l * loadbalancers ) retrieveCiliumClientset () error {
356408 if l .ciliumClient != nil {
357409 return nil
0 commit comments