Skip to content

Commit 069977a

Browse files
committed
create ip holder per cluster + tests, update docs
Signed-off-by: Ross Kirkpatrick <[email protected]>
1 parent 9212805 commit 069977a

9 files changed

+368
-131
lines changed

README.md

+41-40
Large diffs are not rendered by default.

cloud/linode/cilium_loadbalancers.go

+63-11
Original file line numberDiff line numberDiff line change
@@ -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+
355407
func (l *loadbalancers) retrieveCiliumClientset() error {
356408
if l.ciliumClient != nil {
357409
return nil

0 commit comments

Comments
 (0)