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 {