diff --git a/docs/loadbalancer-annotations.md b/docs/loadbalancer-annotations.md
index cf657be..64c02c0 100644
--- a/docs/loadbalancer-annotations.md
+++ b/docs/loadbalancer-annotations.md
@@ -226,3 +226,16 @@ The accepted values are "Proxy" and "VIP". Please refer to this article for more
.
When proxy-protocol is enabled on ALL the ports of the service, the ipMode
is automatically set to "Proxy". You can use this annotation to override this.
+
+### `service.beta.kubernetes.io/scw-loadbalancer-pn-ids`
+
+This is the annotation to configure the Private Networks
+that will be attached to the load balancer. It is possible to provide a single
+Private Network ID, or a comma delimited list of Private Network IDs.
+If this annotation is not set or empty, the load balancer will be attached
+to the Private Network specified in the `PN_ID` environment variable.
+This annotation is ignored when `service.beta.kubernetes.io/scw-loadbalancer-externally-managed` is enabled.
+
+The possible formats are:
+- ``: will attach a single Private Network to the LB.
+- `,`: will attach the two Private Networks to the LB.
diff --git a/scaleway/loadbalancers.go b/scaleway/loadbalancers.go
index 620357d..59c5ed1 100644
--- a/scaleway/loadbalancers.go
+++ b/scaleway/loadbalancers.go
@@ -589,48 +589,8 @@ func (l *loadbalancers) updateLoadBalancer(ctx context.Context, loadbalancer *sc
return err
}
- if l.pnID != "" {
- respPN, err := l.api.ListLBPrivateNetworks(&scwlb.ZonedAPIListLBPrivateNetworksRequest{
- Zone: loadbalancer.Zone,
- LBID: loadbalancer.ID,
- })
- if err != nil {
- return fmt.Errorf("error listing private networks of load balancer %s: %v", loadbalancer.ID, err)
- }
-
- var pnNIC *scwlb.PrivateNetwork
- for _, pNIC := range respPN.PrivateNetwork {
- if pNIC.PrivateNetworkID == l.pnID {
- pnNIC = pNIC
- continue
- }
-
- // this PN should not be attached to this loadbalancer
- if !lbExternallyManaged {
- klog.V(3).Infof("detach extra private network %s from load balancer %s", pNIC.PrivateNetworkID, loadbalancer.ID)
- err = l.api.DetachPrivateNetwork(&scwlb.ZonedAPIDetachPrivateNetworkRequest{
- Zone: loadbalancer.Zone,
- LBID: loadbalancer.ID,
- PrivateNetworkID: pNIC.PrivateNetworkID,
- })
- if err != nil {
- return fmt.Errorf("unable to detach unmatched private network %s from %s: %v", pNIC.PrivateNetworkID, loadbalancer.ID, err)
- }
- }
- }
-
- if pnNIC == nil {
- klog.V(3).Infof("attach private network %s to load balancer %s", l.pnID, loadbalancer.ID)
- _, err = l.api.AttachPrivateNetwork(&scwlb.ZonedAPIAttachPrivateNetworkRequest{
- Zone: loadbalancer.Zone,
- LBID: loadbalancer.ID,
- PrivateNetworkID: l.pnID,
- DHCPConfig: &scwlb.PrivateNetworkDHCPConfig{},
- })
- if err != nil {
- return fmt.Errorf("unable to attach private network %s on %s: %v", l.pnID, loadbalancer.ID, err)
- }
- }
+ if err := l.attachPrivateNetworks(loadbalancer, service, lbExternallyManaged); err != nil {
+ return fmt.Errorf("failed to attach private networks: %w", err)
}
var targetIPs []string
@@ -819,6 +779,74 @@ func (l *loadbalancers) updateLoadBalancer(ctx context.Context, loadbalancer *sc
return nil
}
+func (l *loadbalancers) attachPrivateNetworks(loadbalancer *scwlb.LB, service *v1.Service, lbExternallyManaged bool) error {
+ if l.pnID == "" {
+ return nil
+ }
+
+ // maps pnID => attached
+ pnIDs := make(map[string]bool)
+
+ // Fetch user-specified PrivateNetworkIDs unless LB is externally managed.
+ if !lbExternallyManaged {
+ for _, pnID := range getPrivateNetworkIDs(service) {
+ pnIDs[pnID] = false
+ }
+ }
+
+ if len(pnIDs) == 0 {
+ pnIDs[l.pnID] = false
+ }
+
+ respPN, err := l.api.ListLBPrivateNetworks(&scwlb.ZonedAPIListLBPrivateNetworksRequest{
+ Zone: loadbalancer.Zone,
+ LBID: loadbalancer.ID,
+ })
+ if err != nil {
+ return fmt.Errorf("error listing private networks of load balancer %s: %v", loadbalancer.ID, err)
+ }
+
+ for _, pNIC := range respPN.PrivateNetwork {
+ if _, ok := pnIDs[pNIC.PrivateNetworkID]; ok {
+ // Mark this Private Network as attached.
+ pnIDs[pNIC.PrivateNetworkID] = true
+ continue
+ }
+
+ // this PN should not be attached to this loadbalancer
+ if !lbExternallyManaged {
+ klog.V(3).Infof("detach extra private network %s from load balancer %s", pNIC.PrivateNetworkID, loadbalancer.ID)
+ err = l.api.DetachPrivateNetwork(&scwlb.ZonedAPIDetachPrivateNetworkRequest{
+ Zone: loadbalancer.Zone,
+ LBID: loadbalancer.ID,
+ PrivateNetworkID: pNIC.PrivateNetworkID,
+ })
+ if err != nil {
+ return fmt.Errorf("unable to detach unmatched private network %s from %s: %v", pNIC.PrivateNetworkID, loadbalancer.ID, err)
+ }
+ }
+ }
+
+ for pnID, attached := range pnIDs {
+ if attached {
+ continue
+ }
+
+ klog.V(3).Infof("attach private network %s to load balancer %s", pnID, loadbalancer.ID)
+ _, err = l.api.AttachPrivateNetwork(&scwlb.ZonedAPIAttachPrivateNetworkRequest{
+ Zone: loadbalancer.Zone,
+ LBID: loadbalancer.ID,
+ PrivateNetworkID: pnID,
+ DHCPConfig: &scwlb.PrivateNetworkDHCPConfig{},
+ })
+ if err != nil {
+ return fmt.Errorf("unable to attach private network %s on %s: %v", pnID, loadbalancer.ID, err)
+ }
+ }
+
+ return nil
+}
+
// createPrivateServiceStatus creates a LoadBalancer status for services with private load balancers
func (l *loadbalancers) createPrivateServiceStatus(service *v1.Service, lb *scwlb.LB, ipMode *v1.LoadBalancerIPMode) (*v1.LoadBalancerStatus, error) {
if l.pnID == "" {
diff --git a/scaleway/loadbalancers_annotations.go b/scaleway/loadbalancers_annotations.go
index 1d03849..d10944b 100644
--- a/scaleway/loadbalancers_annotations.go
+++ b/scaleway/loadbalancers_annotations.go
@@ -224,6 +224,18 @@ const (
// When proxy-protocol is enabled on ALL the ports of the service, the ipMode
// is automatically set to "Proxy". You can use this annotation to override this.
serviceAnnotationLoadBalancerIPMode = "service.beta.kubernetes.io/scw-loadbalancer-ip-mode"
+
+ // serviceAnnotationPrivateNetworkIDs is the annotation to configure the Private Networks
+ // that will be attached to the load balancer. It is possible to provide a single
+ // Private Network ID, or a comma delimited list of Private Network IDs.
+ // If this annotation is not set or empty, the load balancer will be attached
+ // to the Private Network specified in the `PN_ID` environment variable.
+ // This annotation is ignored when service.beta.kubernetes.io/scw-loadbalancer-externally-managed is enabled.
+ //
+ // The possible formats are:
+ // - "": will attach a single Private Network to the LB.
+ // - ",": will attach the two Private Networks to the LB.
+ serviceAnnotationPrivateNetworkIDs = "service.beta.kubernetes.io/scw-loadbalancer-pn-ids"
)
func getLoadBalancerID(service *v1.Service) (scw.Zone, string, error) {
@@ -295,6 +307,15 @@ func getIPIDs(service *v1.Service) []string {
return strings.Split(ipIDs, ",")
}
+func getPrivateNetworkIDs(service *v1.Service) []string {
+ pnIDs := service.Annotations[serviceAnnotationPrivateNetworkIDs]
+ if pnIDs == "" {
+ return nil
+ }
+
+ return strings.Split(pnIDs, ",")
+}
+
func getSendProxyV2(service *v1.Service, nodePort int32) (scwlb.ProxyProtocol, error) {
sendProxyV2, ok := service.Annotations[serviceAnnotationLoadBalancerSendProxyV2]
if !ok {