Skip to content

Commit 29b38e8

Browse files
authored
feat: add http protocol and certificate IDs for LB (#29) (#33)
Signed-off-by: Patrik Cyvoct <[email protected]>
1 parent e4110d3 commit 29b38e8

File tree

2 files changed

+106
-12
lines changed

2 files changed

+106
-12
lines changed

docs/loadbalancer-annotations.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,14 @@ Normally, the cloud controller manager use ExternalIP to be nodes region-free (o
113113
This is the annotation that force the use of the LB hostname instead of the public IP.
114114
This is useful when it it needed to not bypass the LoadBalacer for traffic coming from the cluster.
115115

116+
### `service.beta.kubernetes.io/scw-loadbalancer-protocol-http`
117+
This is the annotation to set the forward protocol of the LB to HTTP.
118+
The possible values are `false`, `true` or `*` for all ports or a comma delimited list of the service port (for instance `80,443`).
119+
NB: forwarding HTTPS traffic with HTTP protocol enabled will work only if using a certificate, and the LB will send HTTP traffic to the backend.
120+
121+
### `service.beta.kubernetes.io/scw-loadbalancer-certificate-ids`
122+
This is the annotation to choose the the certificate IDs to associate with this LoadBalancer.
123+
The possible format are:
124+
- `<certificate-id>`: will use this certificate for all frontends
125+
- `<certificate-id>,<certificate-id>` will use these certificates for all frontends
126+
- `<port1>:<certificate1-id>,<certificate2-id>;<port2>,<port3>:<certificate3-id>` will use certificate 1 and 2 for frontend with port port1 and certificate3 for frotend with port port2 and port3

scaleway/loadbalancers.go

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ const (
127127
// serviceAnnotationLoadBalancerUseHostname is the annotation that force the use of the LB hostname instead of the public IP.
128128
// This is useful when it it needed to not bypass the LoadBalacer for traffic coming from the cluster
129129
serviceAnnotationLoadBalancerUseHostname = "service.beta.kubernetes.io/scw-loadbalancer-use-hostname"
130+
131+
// serviceAnnotationLoadBalancerProtocolHTTP is the annotation to set the forward protocol of the LB to HTTP
132+
// The possible values are "false", "true" or "*" for all ports or a comma delimited list of the service port
133+
// (for instance "80,443")
134+
serviceAnnotationLoadBalancerProtocolHTTP = "service.beta.kubernetes.io/scw-loadbalancer-protocol-http"
135+
136+
// serviceAnnotationLoadBalancerCertificateIDs is the annotation to choose the the certificate IDS to associate
137+
// with this LoadBalancer.
138+
// The possible format are:
139+
// "<certificate-id>": will use this certificate for all frontends
140+
// "<certificate-id>,<certificate-id>" will use these certificates for all frontends
141+
// "<port1>:<certificate1-id>,<certificate2-id>;<port2>,<port3>:<certificate3-id>" will use certificate 1 and 2 for frontend with port port1
142+
// and certificate3 for frotend with port port2 and port3
143+
serviceAnnotationLoadBalancerCertificateIDs = "service.beta.kubernetes.io/scw-loadbalancer-certificate-ids"
130144
)
131145

132146
type loadbalancers struct {
@@ -650,14 +664,19 @@ func (l *loadbalancers) updateLoadBalancer(ctx context.Context, loadbalancer *sc
650664

651665
for _, port := range service.Spec.Ports {
652666
var frontendID string
667+
certificateIDs, err := getCertificateIDs(service, port.Port)
668+
if err != nil {
669+
return fmt.Errorf("error getting certificate IDs for loadbalancer %s: %v", loadbalancer.ID, err)
670+
}
653671
// if the frontend exists for the port, update it
654672
if frontend, ok := portFrontends[port.Port]; ok {
655673
_, err := l.api.UpdateFrontend(&scwlb.UpdateFrontendRequest{
656-
FrontendID: frontend.ID,
657-
Name: frontend.Name,
658-
InboundPort: frontend.InboundPort,
659-
BackendID: portBackends[port.NodePort].ID,
660-
TimeoutClient: frontend.TimeoutClient,
674+
FrontendID: frontend.ID,
675+
Name: frontend.Name,
676+
InboundPort: frontend.InboundPort,
677+
BackendID: portBackends[port.NodePort].ID,
678+
TimeoutClient: frontend.TimeoutClient,
679+
CertificateIDs: scw.StringsPtr(certificateIDs),
661680
})
662681

663682
if err != nil {
@@ -669,11 +688,12 @@ func (l *loadbalancers) updateLoadBalancer(ctx context.Context, loadbalancer *sc
669688
} else { // if the frontend for this port does not exist, create it
670689
timeoutClient := time.Minute * 10
671690
resp, err := l.api.CreateFrontend(&scwlb.CreateFrontendRequest{
672-
LBID: loadbalancer.ID,
673-
Name: fmt.Sprintf("%s_tcp_%d", string(service.UID), port.Port),
674-
InboundPort: port.Port,
675-
BackendID: portBackends[port.NodePort].ID,
676-
TimeoutClient: &timeoutClient, // TODO use annotation?
691+
LBID: loadbalancer.ID,
692+
Name: fmt.Sprintf("%s_tcp_%d", string(service.UID), port.Port),
693+
InboundPort: port.Port,
694+
BackendID: portBackends[port.NodePort].ID,
695+
TimeoutClient: &timeoutClient, // TODO use annotation?
696+
CertificateIDs: scw.StringsPtr(certificateIDs),
677697
})
678698

679699
if err != nil {
@@ -768,10 +788,15 @@ func (l *loadbalancers) updateLoadBalancer(ctx context.Context, loadbalancer *sc
768788
}
769789

770790
func (l *loadbalancers) makeUpdateBackendRequest(backend *scwlb.Backend, service *v1.Service, nodes []*v1.Node) (*scwlb.UpdateBackendRequest, error) {
791+
protocol, err := getForwardProtocol(service, backend.ForwardPort)
792+
if err != nil {
793+
return nil, err
794+
}
795+
771796
request := &scwlb.UpdateBackendRequest{
772797
BackendID: backend.ID,
773798
Name: backend.Name,
774-
ForwardProtocol: scwlb.ProtocolTCP,
799+
ForwardProtocol: protocol,
775800
}
776801

777802
forwardPortAlgorithm, err := getForwardPortAlgorithm(service)
@@ -906,6 +931,10 @@ func (l *loadbalancers) makeUpdateHealthCheckRequest(backend *scwlb.Backend, nod
906931
}
907932

908933
func (l *loadbalancers) makeCreateBackendRequest(loadbalancer *scwlb.LB, nodePort int32, service *v1.Service, nodes []*v1.Node) (*scwlb.CreateBackendRequest, error) {
934+
protocol, err := getForwardProtocol(service, nodePort)
935+
if err != nil {
936+
return nil, err
937+
}
909938
var serverIPs []string
910939
if getForceInternalIP(service) {
911940
serverIPs = extractNodesInternalIps(nodes)
@@ -916,7 +945,7 @@ func (l *loadbalancers) makeCreateBackendRequest(loadbalancer *scwlb.LB, nodePor
916945
LBID: loadbalancer.ID,
917946
Name: fmt.Sprintf("%s_tcp_%d", string(service.UID), nodePort),
918947
ServerIP: serverIPs,
919-
ForwardProtocol: scwlb.ProtocolTCP,
948+
ForwardProtocol: protocol,
920949
ForwardPort: nodePort,
921950
}
922951

@@ -1402,3 +1431,57 @@ func getUseHostname(service *v1.Service) bool {
14021431
}
14031432
return value
14041433
}
1434+
1435+
func getForwardProtocol(service *v1.Service, nodePort int32) (scwlb.Protocol, error) {
1436+
httpProtocol := service.Annotations[serviceAnnotationLoadBalancerProtocolHTTP]
1437+
1438+
var svcPort int32 = -1
1439+
for _, p := range service.Spec.Ports {
1440+
if p.NodePort == nodePort {
1441+
svcPort = p.Port
1442+
}
1443+
}
1444+
if svcPort == -1 {
1445+
klog.Errorf("no valid port found")
1446+
return "", errLoadBalancerInvalidAnnotation
1447+
}
1448+
1449+
isHTTP, err := isPortInRange(httpProtocol, svcPort)
1450+
if err != nil {
1451+
klog.Errorf("unable to check if port %d is in range %s", svcPort, httpProtocol)
1452+
return "", err
1453+
}
1454+
1455+
if isHTTP {
1456+
return scwlb.ProtocolHTTP, nil
1457+
}
1458+
1459+
return scwlb.ProtocolTCP, nil
1460+
}
1461+
1462+
func getCertificateIDs(service *v1.Service, port int32) ([]string, error) {
1463+
certificates := service.Annotations[serviceAnnotationLoadBalancerCertificateIDs]
1464+
if certificates == "" {
1465+
return nil, nil
1466+
}
1467+
1468+
ids := []string{}
1469+
1470+
for _, perPortCertificate := range strings.Split(certificates, ";") {
1471+
split := strings.Split(perPortCertificate, ":")
1472+
if len(split) == 1 {
1473+
ids = append(ids, strings.Split(split[0], ",")...)
1474+
continue
1475+
}
1476+
inRange, err := isPortInRange(split[0], port)
1477+
if err != nil {
1478+
klog.Errorf("unable to check if port %d is in range %s", port, split[0])
1479+
return nil, err
1480+
}
1481+
if inRange {
1482+
ids = append(ids, strings.Split(split[1], ",")...)
1483+
}
1484+
}
1485+
1486+
return ids, nil
1487+
}

0 commit comments

Comments
 (0)