Skip to content

Commit 9c4f8ad

Browse files
committed
AWS NLB/ELB health check config based on service annotations
1 parent c19e050 commit 9c4f8ad

File tree

4 files changed

+666
-97
lines changed

4 files changed

+666
-97
lines changed

staging/src/k8s.io/legacy-cloud-providers/aws/aws.go

Lines changed: 118 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,25 @@ const ServiceAnnotationLoadBalancerBEProtocol = "service.beta.kubernetes.io/aws-
187187
// For example: "Key1=Val1,Key2=Val2,KeyNoVal1=,KeyNoVal2"
188188
const ServiceAnnotationLoadBalancerAdditionalTags = "service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags"
189189

190+
// ServiceAnnotationLoadBalancerHealthCheckProtocol is the annotation used on the service to
191+
// specify the protocol used for the ELB health check. Supported values are TCP, HTTP, HTTPS
192+
// Default is TCP if externalTrafficPolicy is Cluster, HTTP if externalTrafficPolicy is Local
193+
const ServiceAnnotationLoadBalancerHealthCheckProtocol = "service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol"
194+
195+
// ServiceAnnotationLoadBalancerHealthCheckPort is the annotation used on the service to
196+
// specify the port used for ELB health check.
197+
// Default is traffic-port if externalTrafficPolicy is Cluster, healthCheckNodePort if externalTrafficPolicy is Local
198+
const ServiceAnnotationLoadBalancerHealthCheckPort = "service.beta.kubernetes.io/aws-load-balancer-healthcheck-port"
199+
200+
// ServiceAnnotationLoadBalancerHealthCheckPath is the annotation used on the service to
201+
// specify the path for the ELB health check when the health check protocol is HTTP/HTTPS
202+
// Defaults to /healthz if externalTrafficPolicy is Local, / otherwise
203+
const ServiceAnnotationLoadBalancerHealthCheckPath = "service.beta.kubernetes.io/aws-load-balancer-healthcheck-path"
204+
190205
// ServiceAnnotationLoadBalancerHCHealthyThreshold is the annotation used on
191206
// the service to specify the number of successive successful health checks
192-
// required for a backend to be considered healthy for traffic.
207+
// required for a backend to be considered healthy for traffic. For NLB, healthy-threshold
208+
// and unhealthy-threshold must be equal.
193209
const ServiceAnnotationLoadBalancerHCHealthyThreshold = "service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold"
194210

195211
// ServiceAnnotationLoadBalancerHCUnhealthyThreshold is the annotation used
@@ -3686,6 +3702,91 @@ func (c *Cloud) getSubnetCidrs(subnetIDs []string) ([]string, error) {
36863702
return cidrs, nil
36873703
}
36883704

3705+
func parseStringAnnotation(annotations map[string]string, annotation string, value *string) bool {
3706+
if v, ok := annotations[annotation]; ok {
3707+
*value = v
3708+
return true
3709+
}
3710+
return false
3711+
}
3712+
3713+
func parseInt64Annotation(annotations map[string]string, annotation string, value *int64) (bool, error) {
3714+
if v, ok := annotations[annotation]; ok {
3715+
parsed, err := strconv.ParseInt(v, 10, 0)
3716+
if err != nil {
3717+
return true, fmt.Errorf("failed to parse annotation %v=%v", annotation, v)
3718+
}
3719+
*value = parsed
3720+
return true, nil
3721+
}
3722+
return false, nil
3723+
}
3724+
3725+
func (c *Cloud) buildNLBHealthCheckConfiguration(svc *v1.Service) (healthCheckConfig, error) {
3726+
hc := healthCheckConfig{
3727+
Port: defaultHealthCheckPort,
3728+
Path: defaultHealthCheckPath,
3729+
Protocol: elbv2.ProtocolEnumTcp,
3730+
Interval: defaultNlbHealthCheckInterval,
3731+
Timeout: defaultNlbHealthCheckTimeout,
3732+
HealthyThreshold: defaultNlbHealthCheckThreshold,
3733+
UnhealthyThreshold: defaultNlbHealthCheckThreshold,
3734+
}
3735+
if svc.Spec.ExternalTrafficPolicy == v1.ServiceExternalTrafficPolicyTypeLocal {
3736+
path, port := servicehelpers.GetServiceHealthCheckPathPort(svc)
3737+
hc = healthCheckConfig{
3738+
Port: strconv.Itoa(int(port)),
3739+
Path: path,
3740+
Protocol: elbv2.ProtocolEnumHttp,
3741+
Interval: 10,
3742+
Timeout: 10,
3743+
HealthyThreshold: 2,
3744+
UnhealthyThreshold: 2,
3745+
}
3746+
}
3747+
if parseStringAnnotation(svc.Annotations, ServiceAnnotationLoadBalancerHealthCheckProtocol, &hc.Protocol) {
3748+
hc.Protocol = strings.ToUpper(hc.Protocol)
3749+
}
3750+
switch hc.Protocol {
3751+
case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps:
3752+
parseStringAnnotation(svc.Annotations, ServiceAnnotationLoadBalancerHealthCheckPath, &hc.Path)
3753+
case elbv2.ProtocolEnumTcp:
3754+
hc.Path = ""
3755+
default:
3756+
return healthCheckConfig{}, fmt.Errorf("Unsupported health check protocol %v", hc.Protocol)
3757+
}
3758+
3759+
parseStringAnnotation(svc.Annotations, ServiceAnnotationLoadBalancerHealthCheckPort, &hc.Port)
3760+
3761+
if _, err := parseInt64Annotation(svc.Annotations, ServiceAnnotationLoadBalancerHCInterval, &hc.Interval); err != nil {
3762+
return healthCheckConfig{}, err
3763+
}
3764+
if _, err := parseInt64Annotation(svc.Annotations, ServiceAnnotationLoadBalancerHCTimeout, &hc.Timeout); err != nil {
3765+
return healthCheckConfig{}, err
3766+
}
3767+
if _, err := parseInt64Annotation(svc.Annotations, ServiceAnnotationLoadBalancerHCHealthyThreshold, &hc.HealthyThreshold); err != nil {
3768+
return healthCheckConfig{}, err
3769+
}
3770+
if _, err := parseInt64Annotation(svc.Annotations, ServiceAnnotationLoadBalancerHCUnhealthyThreshold, &hc.UnhealthyThreshold); err != nil {
3771+
return healthCheckConfig{}, err
3772+
}
3773+
3774+
if hc.HealthyThreshold != hc.UnhealthyThreshold {
3775+
return healthCheckConfig{}, fmt.Errorf("Health check healthy threshold and unhealthy threshold must be equal")
3776+
}
3777+
3778+
if hc.Interval != 10 && hc.Interval != 30 {
3779+
return healthCheckConfig{}, fmt.Errorf("Invalid health check interval '%v', must be either 10 or 30", hc.Interval)
3780+
}
3781+
3782+
if hc.Port != defaultHealthCheckPort {
3783+
if _, err := strconv.ParseInt(hc.Port, 10, 0); err != nil {
3784+
return healthCheckConfig{}, fmt.Errorf("Invalid health check port '%v'", hc.Port)
3785+
}
3786+
}
3787+
return hc, nil
3788+
}
3789+
36893790
// EnsureLoadBalancer implements LoadBalancer.EnsureLoadBalancer
36903791
func (c *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, apiService *v1.Service, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) {
36913792
annotations := apiService.Annotations
@@ -3724,11 +3825,10 @@ func (c *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, apiS
37243825
FrontendProtocol: string(port.Protocol),
37253826
TrafficPort: int64(port.NodePort),
37263827
TrafficProtocol: string(port.Protocol),
3727-
3728-
// if externalTrafficPolicy == "Local", we'll override the
3729-
// health check later
3730-
HealthCheckPort: int64(port.NodePort),
3731-
HealthCheckProtocol: elbv2.ProtocolEnumTcp,
3828+
}
3829+
var err error
3830+
if portMapping.HealthCheckConfig, err = c.buildNLBHealthCheckConfiguration(apiService); err != nil {
3831+
return nil, err
37323832
}
37333833

37343834
certificateARN := annotations[ServiceAnnotationLoadBalancerCertificate]
@@ -3776,15 +3876,6 @@ func (c *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, apiS
37763876
}
37773877

37783878
if isNLB(annotations) {
3779-
3780-
if path, healthCheckNodePort := servicehelpers.GetServiceHealthCheckPathPort(apiService); path != "" {
3781-
for i := range v2Mappings {
3782-
v2Mappings[i].HealthCheckPort = int64(healthCheckNodePort)
3783-
v2Mappings[i].HealthCheckPath = path
3784-
v2Mappings[i].HealthCheckProtocol = elbv2.ProtocolEnumHttp
3785-
}
3786-
}
3787-
37883879
// Find the subnets that the ELB will live in
37893880
subnetIDs, err := c.findELBSubnets(internalELB)
37903881
if err != nil {
@@ -4041,23 +4132,26 @@ func (c *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, apiS
40414132
}
40424133
}
40434134

4135+
// We only configure a TCP health-check on the first port
4136+
var tcpHealthCheckPort int32
4137+
for _, listener := range listeners {
4138+
if listener.InstancePort == nil {
4139+
continue
4140+
}
4141+
tcpHealthCheckPort = int32(*listener.InstancePort)
4142+
break
4143+
}
40444144
if path, healthCheckNodePort := servicehelpers.GetServiceHealthCheckPathPort(apiService); path != "" {
40454145
klog.V(4).Infof("service %v (%v) needs health checks on :%d%s)", apiService.Name, loadBalancerName, healthCheckNodePort, path)
4146+
if annotations[ServiceAnnotationLoadBalancerHealthCheckPort] == defaultHealthCheckPort {
4147+
healthCheckNodePort = tcpHealthCheckPort
4148+
}
40464149
err = c.ensureLoadBalancerHealthCheck(loadBalancer, "HTTP", healthCheckNodePort, path, annotations)
40474150
if err != nil {
40484151
return nil, fmt.Errorf("Failed to ensure health check for localized service %v on node port %v: %q", loadBalancerName, healthCheckNodePort, err)
40494152
}
40504153
} else {
40514154
klog.V(4).Infof("service %v does not need custom health checks", apiService.Name)
4052-
// We only configure a TCP health-check on the first port
4053-
var tcpHealthCheckPort int32
4054-
for _, listener := range listeners {
4055-
if listener.InstancePort == nil {
4056-
continue
4057-
}
4058-
tcpHealthCheckPort = int32(*listener.InstancePort)
4059-
break
4060-
}
40614155
annotationProtocol := strings.ToLower(annotations[ServiceAnnotationLoadBalancerBEProtocol])
40624156
var hcProtocol string
40634157
if annotationProtocol == "https" || annotationProtocol == "ssl" {

0 commit comments

Comments
 (0)