Skip to content

Feature Request: Support fixed private IP (IPAM) for private Load Balancers #224

@fabcdl

Description

@fabcdl

Summary

When using a private Load Balancer in a Scaleway Kapsule cluster (via the service.beta.kubernetes.io/scw-loadbalancer-private: "true" annotation), there is currently no way to assign a fixed private IP address to the Load Balancer's Private Network interface. The CCM always lets Scaleway allocate a random IP from the Private Network's DHCP pool.

This is a blocking limitation for hybrid connectivity scenarios where an on-premises datacenter (connected via Scaleway InterLink or VPN site-to-site) needs to reach services inside the Kapsule cluster through a stable, predictable private IP — for example, to configure static routes, firewall rules, or DNS records on the datacenter side.

Context

Use case

In a typical hybrid architecture:

  • Kapsule → Datacenter: straightforward — CoreDNS forward zones can resolve datacenter hostnames.
  • Datacenter → Kapsule: requires a private Load Balancer with a stable IP that the datacenter can target. A DHCP-assigned IP that may change when the LB is recreated (e.g. during a Helm chart upgrade or a service re-creation) is not acceptable in production.

What the Scaleway API already supports

The Scaleway LB API already supports assigning a pre-reserved IPAM IP to a Load Balancer's Private Network attachment via the ipam_ids field of the AttachPrivateNetwork endpoint:

IPAM ID of a pre-reserved IP address to assign to the Load Balancer on this Private Network.
When null, a new private IP address is created for the Load Balancer on this Private Network.

This means no API-side changes are required — the capability exists today. Only the CCM needs to be updated to expose and use it.

Industry context

For reference, all three major cloud providers natively support fixed private IPs on internal/private load balancers from Kubernetes service annotations, with full dynamic backend management:

Provider Mechanism
AWS (EKS) service.beta.kubernetes.io/aws-load-balancer-private-ipv4-addresses
GCP (GKE) spec.loadBalancerIP or networking.gke.io/load-balancer-ip-addresses
Azure (AKS) service.beta.kubernetes.io/azure-load-balancer-ipv4 or spec.loadBalancerIP

Current behavior

In EnsureLoadBalancer() (scaleway/loadbalancers.go), the following guard clause explicitly rejects any combination of private LB + static IP:

if lbPrivate && hasLoadBalancerStaticIPs(service) {
    return nil, fmt.Errorf("scaleway-cloud-controller-manager can only handle static IPs for public load balancers. Unsetting the static IP can result in the loss of the IP")
}

Additionally, in createLoadBalancer(), the call to getLoadBalancerStaticIPIDs() is gated behind if !lbPrivate, and the attachPrivateNetworks() function never populates the IpamIds field of ZonedAPIAttachPrivateNetworkRequest.

Proposed solution

Introduce a new annotation, for example:

service.beta.kubernetes.io/scw-loadbalancer-private-ipam-id: "<ipam-ip-uuid>"

This annotation would accept the UUID of an IP pre-reserved in Scaleway IPAM (from the Private Network's subnet).

Required changes in loadbalancers.go

The changes are minimal and localized to a small number of well-identified locations:

# Location Change
1 EnsureLoadBalancer() — guard clause Replace hard rejection with a branch: if the annotation is a private IPAM ID → allow, otherwise keep existing error for public flexible IPs on private LBs
2 createLoadBalancer() Add a branch for lbPrivate case to read the new annotation and propagate the IPAM ID to the next step
3 attachPrivateNetworks() Populate IpamIds: []string{ipamID} in ZonedAPIAttachPrivateNetworkRequest when the annotation is set
4 deleteLoadBalancer() Extend ReleaseIP logic: do not release the IPAM IP when the new annotation is present (mirrors existing behavior for public flex IPs)
5 EnsureLoadBalancer() — drift detection Add a privateIPAMMismatch check analogous to reservedIPMismatch, to trigger LB recreation if the IPAM IP has changed
6 Annotations Add the new constant and document it in docs/loadbalancer-annotations.md

Notes

  • The spec.loadBalancerIP field is not a viable alternative here: the CCM source code explicitly restricts it to public flexible IPs only (see guard clause above).
  • The proposed annotation follows the existing naming convention used by other scw-loadbalancer-* annotations.
  • The Scaleway API already handles IPAM IP assignment at AttachPrivateNetwork time — this is not a new API capability, just an unexposed one.

I'm raising this as a feature request based on a concrete production need in a Kapsule-based hybrid infrastructure project. I don't have deep expertise in this codebase, so I may have missed edge cases or implementation subtleties — I hope this analysis is useful as a starting point for the maintainers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions