Skip to content

Commit 0150bf5

Browse files
authored
Add hostname annotation to set lb ingress hostname (#48)
This fixes problems with kube-proxy in ipvs mode considering the lb IP as local to the node See kubernetes/kubernetes#66607 This can also be used to access PROXY proto service from the inside
1 parent 910f558 commit 0150bf5

File tree

2 files changed

+62
-19
lines changed

2 files changed

+62
-19
lines changed

cloudstack_loadbalancer.go

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,19 @@ import (
3232
cloudprovider "k8s.io/cloud-provider"
3333
)
3434

35-
// defaultAllowedCIDR is the network range that is allowed on the firewall
36-
// by default when no explicit CIDR list is given on a LoadBalancer.
37-
const defaultAllowedCIDR = "0.0.0.0/0"
35+
const (
36+
// defaultAllowedCIDR is the network range that is allowed on the firewall
37+
// by default when no explicit CIDR list is given on a LoadBalancer.
38+
defaultAllowedCIDR = "0.0.0.0/0"
39+
40+
// ServiceAnnotationLoadBalancerProxyProtocol is the annotation used on the
41+
// service to enable the proxy protocol on a CloudStack load balancer.
42+
// Note that this protocol only applies to TCP service ports and
43+
// CloudStack >= 4.6 is required for it to work.
44+
ServiceAnnotationLoadBalancerProxyProtocol = "service.beta.kubernetes.io/cloudstack-load-balancer-proxy-protocol"
45+
46+
ServiceAnnotationLoadBalancerLoadbalancerHostname = "service.beta.kubernetes.io/cloudstack-load-balancer-hostname"
47+
)
3848

3949
type loadBalancer struct {
4050
*cloudstack.CloudStackClient
@@ -123,7 +133,7 @@ func (cs *CSCloud) EnsureLoadBalancer(ctx context.Context, clusterName string, s
123133

124134
for _, port := range service.Spec.Ports {
125135
// Construct the protocol name first, we need it a few times
126-
protocol := ProtocolFromServicePort(port, service.Annotations)
136+
protocol := ProtocolFromServicePort(port, service)
127137
if protocol == LoadBalancerProtocolInvalid {
128138
return nil, fmt.Errorf("unsupported load balancer protocol: %v", port.Protocol)
129139
}
@@ -202,6 +212,13 @@ func (cs *CSCloud) EnsureLoadBalancer(ctx context.Context, clusterName string, s
202212
}
203213

204214
status = &corev1.LoadBalancerStatus{}
215+
// If hostname is explicitly set using service annotation
216+
// Workaround for https://github.com/kubernetes/kubernetes/issues/66607
217+
if hostname := getStringFromServiceAnnotation(service, ServiceAnnotationLoadBalancerLoadbalancerHostname, ""); hostname != "" {
218+
status.Ingress = []corev1.LoadBalancerIngress{{Hostname: hostname}}
219+
return status, nil
220+
}
221+
// Default to IP
205222
status.Ingress = []corev1.LoadBalancerIngress{{IP: lb.ipAddr}}
206223

207224
return status, nil
@@ -805,3 +822,41 @@ func (lb *loadBalancer) deleteFirewallRule(publicIpId string, publicPort int, pr
805822

806823
return deleted, err
807824
}
825+
826+
// getStringFromServiceAnnotation searches a given v1.Service for a specific annotationKey and either returns the annotation's value or a specified defaultSetting
827+
func getStringFromServiceAnnotation(service *corev1.Service, annotationKey string, defaultSetting string) string {
828+
klog.V(4).Infof("getStringFromServiceAnnotation(%s/%s, %v, %v)", service.Namespace, service.Name, annotationKey, defaultSetting)
829+
if annotationValue, ok := service.Annotations[annotationKey]; ok {
830+
//if there is an annotation for this setting, set the "setting" var to it
831+
// annotationValue can be empty, it is working as designed
832+
// it makes possible for instance provisioning loadbalancer without floatingip
833+
klog.V(4).Infof("Found a Service Annotation: %v = %v", annotationKey, annotationValue)
834+
return annotationValue
835+
}
836+
//if there is no annotation, set "settings" var to the value from cloud config
837+
if defaultSetting != "" {
838+
klog.V(4).Infof("Could not find a Service Annotation; falling back on cloud-config setting: %v = %v", annotationKey, defaultSetting)
839+
}
840+
return defaultSetting
841+
}
842+
843+
// getBoolFromServiceAnnotation searches a given v1.Service for a specific annotationKey and either returns the annotation's boolean value or a specified defaultSetting
844+
func getBoolFromServiceAnnotation(service *corev1.Service, annotationKey string, defaultSetting bool) bool {
845+
klog.V(4).Infof("getBoolFromServiceAnnotation(%s/%s, %v, %v)", service.Namespace, service.Name, annotationKey, defaultSetting)
846+
if annotationValue, ok := service.Annotations[annotationKey]; ok {
847+
returnValue := false
848+
switch annotationValue {
849+
case "true":
850+
returnValue = true
851+
case "false":
852+
returnValue = false
853+
default:
854+
returnValue = defaultSetting
855+
}
856+
857+
klog.V(4).Infof("Found a Service Annotation: %v = %v", annotationKey, returnValue)
858+
return returnValue
859+
}
860+
klog.V(4).Infof("Could not find a Service Annotation; falling back to default setting: %v = %v", annotationKey, defaultSetting)
861+
return defaultSetting
862+
}

protocol.go

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
package cloudstack
2121

2222
import (
23-
"k8s.io/api/core/v1"
23+
v1 "k8s.io/api/core/v1"
2424
)
2525

2626
// LoadBalancerProtocol represents a specific network protocol supported by the CloudStack load balancer.
@@ -35,14 +35,6 @@ const (
3535
LoadBalancerProtocolInvalid
3636
)
3737

38-
// ServiceAnnotationLoadBalancerProxyProtocol is the annotation used on the
39-
// service to enable the proxy protocol on a CloudStack load balancer.
40-
// The value of this annotation is ignored, even if it is seemingly boolean.
41-
// Simple presence of the annotation will enable it.
42-
// Note that this protocol only applies to TCP service ports and
43-
// CloudStack 4.6 is required for it to work.
44-
const ServiceAnnotationLoadBalancerProxyProtocol = "service.beta.kubernetes.io/cloudstack-load-balancer-proxy-protocol"
45-
4638
// String returns the same value as CSProtocol.
4739
func (p LoadBalancerProtocol) String() string {
4840
return p.CSProtocol()
@@ -89,12 +81,8 @@ func (p LoadBalancerProtocol) IPProtocol() string {
8981
// -> "tcp-proxy" (CloudStack 4.6 and later)
9082
//
9183
// Other values return LoadBalancerProtocolInvalid.
92-
func ProtocolFromServicePort(port v1.ServicePort, annotations map[string]string) LoadBalancerProtocol {
93-
proxy := false
94-
// FIXME this accepts any value as true, even "false", 0 or other falsey stuff
95-
if _, ok := annotations[ServiceAnnotationLoadBalancerProxyProtocol]; ok {
96-
proxy = true
97-
}
84+
func ProtocolFromServicePort(port v1.ServicePort, service *v1.Service) LoadBalancerProtocol {
85+
proxy := getBoolFromServiceAnnotation(service, ServiceAnnotationLoadBalancerProxyProtocol, false)
9886
switch port.Protocol {
9987
case v1.ProtocolTCP:
10088
if proxy {

0 commit comments

Comments
 (0)