Skip to content

Commit 969cafd

Browse files
committed
Support explicit target protocol annotation for load balancers
1 parent 9c05307 commit 969cafd

File tree

4 files changed

+168
-9
lines changed

4 files changed

+168
-9
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
## unreleased
22

3+
* When using the LoadBalancer `service.beta.kubernetes.io/do-loadbalancer-target-protocol` annotation with any of `http`,
4+
`https`, `http2` or `http3` specification, the forwarding target protocol is overridden from the implicit default `http`
5+
protocol.
6+
37
## v0.1.55 (beta) - July 29, 2024
48

59
* When using the LoadBalancer `service.beta.kubernetes.io/do-loadbalancer-type=REGIONAL_NETWORK` (under closed beta), firewall rules
6-
are added to open up the underlying health check port and all the defined (port, protocols) defined on the service. This is to
7-
permit traffic to arrive directly on the underlying worker nodes.
10+
are added to open up the underlying health check port and all the defined (port, protocols) defined on the service. This is to
11+
permit traffic to arrive directly on the underlying worker nodes.
812

913
## v0.1.54 (beta) - June 12, 2024
1014

cloud-controller-manager/do/lb_annotations.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ const (
3232
// is overwritten to https. Options are tcp, http and https. Defaults to tcp.
3333
annDOProtocol = annDOLoadBalancerBase + "protocol"
3434

35+
// annDOTargetProtocol is the annotation used to specify the target protocol
36+
// for DO load balancers. If unspecified, load balancers are configured with
37+
// annDOProtocol specification. Options are http, https, http2 and http3.
38+
annDOTargetProtocol = annDOLoadBalancerBase + "target-protocol"
39+
3540
// annDOHealthCheckPath is the annotation used to specify the health check path
3641
// for DO load balancers. Defaults to '/'.
3742
annDOHealthCheckPath = annDOLoadBalancerBase + "healthcheck-path"

cloud-controller-manager/do/loadbalancers.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -733,13 +733,24 @@ func buildHTTP3ForwardingRule(ctx context.Context, service *v1.Service, godoClie
733733
return nil, errors.New("certificate ID is required for HTTP3")
734734
}
735735

736+
var targetProtocol string
737+
if _, err := getProtocol(service, annDOTargetProtocol, func(protocol string) { targetProtocol = protocol }); err != nil {
738+
return nil, err
739+
}
740+
if targetProtocol == protocolTCP || targetProtocol == protocolUDP {
741+
return nil, fmt.Errorf("target protocol annotation is not supported for TCP or UDP")
742+
}
743+
if targetProtocol == "" {
744+
targetProtocol = protocolHTTP
745+
}
746+
736747
for _, port := range service.Spec.Ports {
737748
if port.Port == int32(http3Port) {
738749
return &godo.ForwardingRule{
739750
EntryProtocol: protocolHTTP3,
740751
EntryPort: http3Port,
741752
CertificateID: certificateID,
742-
TargetProtocol: protocolHTTP,
753+
TargetProtocol: targetProtocol,
743754
TargetPort: int(port.NodePort),
744755
}, nil
745756
}
@@ -753,7 +764,7 @@ func buildHTTP3ForwardingRule(ctx context.Context, service *v1.Service, godoClie
753764
func buildForwardingRules(ctx context.Context, service *v1.Service, godoClient *godo.Client) ([]godo.ForwardingRule, error) {
754765
var forwardingRules []godo.ForwardingRule
755766

756-
defaultProtocol, err := getProtocol(service)
767+
defaultProtocol, err := getProtocol(service, annDOProtocol)
757768
if err != nil {
758769
return nil, err
759770
}
@@ -928,6 +939,14 @@ func buildTLSForwardingRule(forwardingRule *godo.ForwardingRule, service *v1.Ser
928939
return errors.New("either certificate id should be set or tls pass through enabled, not both")
929940
}
930941

942+
var targetProtocol string
943+
if _, err := getProtocol(service, annDOTargetProtocol, func(protocol string) { targetProtocol = protocol }); err != nil {
944+
return err
945+
}
946+
if targetProtocol == protocolTCP || targetProtocol == protocolUDP {
947+
return fmt.Errorf("target protocol annotation is not supported for TCP or UDP")
948+
}
949+
931950
if tlsPassThrough {
932951
forwardingRule.TlsPassthrough = tlsPassThrough
933952
// We don't explicitly set the TargetProtocol here since in buildForwardingRule
@@ -936,7 +955,11 @@ func buildTLSForwardingRule(forwardingRule *godo.ForwardingRule, service *v1.Ser
936955
// to match the EntryProtocol.
937956
} else {
938957
forwardingRule.CertificateID = certificateID
939-
forwardingRule.TargetProtocol = protocolHTTP
958+
if targetProtocol != "" {
959+
forwardingRule.TargetProtocol = targetProtocol
960+
} else {
961+
forwardingRule.TargetProtocol = protocolHTTP
962+
}
940963
}
941964

942965
return nil
@@ -968,9 +991,10 @@ func buildStickySessions(service *v1.Service) (*godo.StickySessions, error) {
968991
}, nil
969992
}
970993

971-
// getProtocol returns the desired protocol of service.
972-
func getProtocol(service *v1.Service) (string, error) {
973-
protocol, ok := service.Annotations[annDOProtocol]
994+
// getProtocol returns the service protocol as per the passed annotation,
995+
// additionally invokes list of callback functions if provided.
996+
func getProtocol(service *v1.Service, annotation string, opts ...func(protocol string)) (string, error) {
997+
protocol, ok := service.Annotations[annotation]
974998
if !ok {
975999
return protocolTCP, nil
9761000
}
@@ -981,6 +1005,10 @@ func getProtocol(service *v1.Service) (string, error) {
9811005
return "", fmt.Errorf("invalid protocol %q specified in annotation %q", protocol, annDOProtocol)
9821006
}
9831007

1008+
for _, opt := range opts {
1009+
opt(protocol)
1010+
}
1011+
9841012
return protocol, nil
9851013
}
9861014

cloud-controller-manager/do/loadbalancers_test.go

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ func Test_getProtocol(t *testing.T) {
915915

916916
for _, test := range testcases {
917917
t.Run(test.name, func(t *testing.T) {
918-
protocol, err := getProtocol(test.service)
918+
protocol, err := getProtocol(test.service, annDOProtocol)
919919
if protocol != test.protocol {
920920
t.Error("unexpected protocol")
921921
t.Logf("expected: %q", test.protocol)
@@ -2161,6 +2161,128 @@ func Test_buildForwardingRules(t *testing.T) {
21612161
},
21622162
nil,
21632163
},
2164+
{
2165+
"invalid target protocol annotation returns error",
2166+
&v1.Service{
2167+
ObjectMeta: metav1.ObjectMeta{
2168+
Name: "test",
2169+
UID: "abc123",
2170+
Annotations: map[string]string{
2171+
annDOHTTP2Ports: "443",
2172+
annDOTargetProtocol: "abcd",
2173+
annDOCertificateID: "test-certificate",
2174+
},
2175+
},
2176+
Spec: v1.ServiceSpec{
2177+
Ports: []v1.ServicePort{
2178+
{
2179+
Name: "test",
2180+
Protocol: "TCP",
2181+
Port: int32(443),
2182+
NodePort: int32(18080),
2183+
},
2184+
},
2185+
},
2186+
},
2187+
nil,
2188+
errors.New("failed to build TLS part(s) of forwarding rule: invalid protocol \"abcd\" specified in annotation \"service.beta.kubernetes.io/do-loadbalancer-protocol\""),
2189+
},
2190+
{
2191+
"unsupported target protocol annotation value returns error",
2192+
&v1.Service{
2193+
ObjectMeta: metav1.ObjectMeta{
2194+
Name: "test",
2195+
UID: "abc123",
2196+
Annotations: map[string]string{
2197+
annDOHTTP2Ports: "443",
2198+
annDOTargetProtocol: "tcp",
2199+
annDOCertificateID: "test-certificate",
2200+
},
2201+
},
2202+
Spec: v1.ServiceSpec{
2203+
Ports: []v1.ServicePort{
2204+
{
2205+
Name: "test",
2206+
Protocol: "TCP",
2207+
Port: int32(443),
2208+
NodePort: int32(18080),
2209+
},
2210+
},
2211+
},
2212+
},
2213+
nil,
2214+
errors.New("failed to build TLS part(s) of forwarding rule: target protocol annotation is not supported for TCP or UDP"),
2215+
},
2216+
{
2217+
"support same SSL terminated forwarding protocols (http2 -> http2)",
2218+
&v1.Service{
2219+
ObjectMeta: metav1.ObjectMeta{
2220+
Name: "test",
2221+
UID: "abc123",
2222+
Annotations: map[string]string{
2223+
annDOHTTP2Ports: "443",
2224+
annDOTargetProtocol: "http2",
2225+
annDOCertificateID: "test-certificate",
2226+
},
2227+
},
2228+
Spec: v1.ServiceSpec{
2229+
Ports: []v1.ServicePort{
2230+
{
2231+
Name: "test",
2232+
Protocol: "TCP",
2233+
Port: int32(443),
2234+
NodePort: int32(18080),
2235+
},
2236+
},
2237+
},
2238+
},
2239+
[]godo.ForwardingRule{
2240+
{
2241+
EntryProtocol: "http2",
2242+
EntryPort: 443,
2243+
TargetProtocol: "http2",
2244+
TargetPort: 18080,
2245+
CertificateID: "test-certificate",
2246+
TlsPassthrough: false,
2247+
},
2248+
},
2249+
nil,
2250+
},
2251+
{
2252+
"support different SSL terminated forwarding protocols (http2 -> https)",
2253+
&v1.Service{
2254+
ObjectMeta: metav1.ObjectMeta{
2255+
Name: "test",
2256+
UID: "abc123",
2257+
Annotations: map[string]string{
2258+
annDOHTTP2Ports: "443",
2259+
annDOTargetProtocol: "https",
2260+
annDOCertificateID: "test-certificate",
2261+
},
2262+
},
2263+
Spec: v1.ServiceSpec{
2264+
Ports: []v1.ServicePort{
2265+
{
2266+
Name: "test",
2267+
Protocol: "TCP",
2268+
Port: int32(443),
2269+
NodePort: int32(18080),
2270+
},
2271+
},
2272+
},
2273+
},
2274+
[]godo.ForwardingRule{
2275+
{
2276+
EntryProtocol: "http2",
2277+
EntryPort: 443,
2278+
TargetProtocol: "https",
2279+
TargetPort: 18080,
2280+
CertificateID: "test-certificate",
2281+
TlsPassthrough: false,
2282+
},
2283+
},
2284+
nil,
2285+
},
21642286
}
21652287

21662288
for _, test := range testcases {

0 commit comments

Comments
 (0)