Skip to content

Commit 63e5be0

Browse files
authored
Merge pull request #802 from backjo/master
Resolve NodePort / TargetPort when a port name is passed in as a service health check port
2 parents aeb86a3 + d4d35cb commit 63e5be0

File tree

5 files changed

+374
-24
lines changed

5 files changed

+374
-24
lines changed

docs/guide/ingress/annotation.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ Health check on target groups can be controlled with following annotations:
180180
```
181181
alb.ingress.kubernetes.io/healthcheck-port: traffic-port
182182
```
183+
- set the healthcheck port to the NodePort(when target-type=instance) or TargetPort(when target-type=ip) of a named port
184+
```
185+
alb.ingress.kubernetes.io/healthcheck-port: my-port
186+
```
183187
- set the healthcheck port to 80/tcp
184188
```
185189
alb.ingress.kubernetes.io/healthcheck-port: '80'

internal/alb/tg/targetgroup.go

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@ package tg
33
import (
44
"context"
55
"fmt"
6-
7-
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/albctx"
8-
9-
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/k8s"
6+
"strconv"
107

118
"github.com/aws/aws-sdk-go/service/elbv2"
129
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/alb/tags"
10+
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/albctx"
1311
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/aws"
1412
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations"
13+
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations/healthcheck"
1514
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/backend"
1615
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/controller/store"
16+
"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/k8s"
1717
util "github.com/kubernetes-sigs/aws-alb-ingress-controller/pkg/util/types"
18+
"github.com/pkg/errors"
1819
extensions "k8s.io/api/extensions/v1beta1"
1920
"k8s.io/apimachinery/pkg/types"
21+
"k8s.io/apimachinery/pkg/util/intstr"
2022
)
2123

2224
// The port used when creating targetGroup serves as a default value for targets registered without port specified.
@@ -67,19 +69,27 @@ func (controller *defaultController) Reconcile(ctx context.Context, ingress *ext
6769
if err != nil {
6870
return TargetGroup{}, fmt.Errorf("failed to load serviceAnnotation due to %v", err)
6971
}
72+
7073
protocol := aws.StringValue(serviceAnnos.TargetGroup.BackendProtocol)
7174
targetType := aws.StringValue(serviceAnnos.TargetGroup.TargetType)
75+
76+
healthCheckPort, err := controller.resolveServiceHealthCheckPort(ingress.Namespace, backend.ServiceName, intstr.Parse(*serviceAnnos.HealthCheck.Port), targetType)
77+
78+
if err != nil {
79+
return TargetGroup{}, fmt.Errorf("failed to resolve healthcheck port due to %v", err)
80+
}
81+
7282
tgName := controller.nameTagGen.NameTG(ingress.Namespace, ingress.Name, backend.ServiceName, backend.ServicePort.String(), targetType, protocol)
7383
tgInstance, err := controller.findExistingTGInstance(ctx, tgName)
7484
if err != nil {
7585
return TargetGroup{}, fmt.Errorf("failed to find existing targetGroup due to %v", err)
7686
}
7787
if tgInstance == nil {
78-
if tgInstance, err = controller.newTGInstance(ctx, tgName, serviceAnnos); err != nil {
88+
if tgInstance, err = controller.newTGInstance(ctx, tgName, serviceAnnos, healthCheckPort); err != nil {
7989
return TargetGroup{}, fmt.Errorf("failed to create targetGroup due to %v", err)
8090
}
8191
} else {
82-
if tgInstance, err = controller.reconcileTGInstance(ctx, tgInstance, serviceAnnos); err != nil {
92+
if tgInstance, err = controller.reconcileTGInstance(ctx, tgInstance, serviceAnnos, healthCheckPort); err != nil {
8393
return TargetGroup{}, fmt.Errorf("failed to modify targetGroup due to %v", err)
8494
}
8595
}
@@ -97,20 +107,21 @@ func (controller *defaultController) Reconcile(ctx context.Context, ingress *ext
97107
if err = controller.targetsController.Reconcile(ctx, tgTargets); err != nil {
98108
return TargetGroup{}, fmt.Errorf("failed to reconcile targetGroup targets due to %v", err)
99109
}
110+
100111
return TargetGroup{
101112
Arn: tgArn,
102113
TargetType: targetType,
103114
Targets: tgTargets.Targets,
104115
}, nil
105116
}
106117

107-
func (controller *defaultController) newTGInstance(ctx context.Context, name string, serviceAnnos *annotations.Service) (*elbv2.TargetGroup, error) {
118+
func (controller *defaultController) newTGInstance(ctx context.Context, name string, serviceAnnos *annotations.Service, healthCheckPort string) (*elbv2.TargetGroup, error) {
108119
albctx.GetLogger(ctx).Infof("creating target group %v", name)
109120
resp, err := controller.cloud.CreateTargetGroupWithContext(ctx, &elbv2.CreateTargetGroupInput{
110121
Name: aws.String(name),
111122
HealthCheckPath: serviceAnnos.HealthCheck.Path,
112123
HealthCheckIntervalSeconds: serviceAnnos.HealthCheck.IntervalSeconds,
113-
HealthCheckPort: serviceAnnos.HealthCheck.Port,
124+
HealthCheckPort: aws.String(healthCheckPort),
114125
HealthCheckProtocol: serviceAnnos.HealthCheck.Protocol,
115126
HealthCheckTimeoutSeconds: serviceAnnos.HealthCheck.TimeoutSeconds,
116127
TargetType: serviceAnnos.TargetGroup.TargetType,
@@ -128,14 +139,15 @@ func (controller *defaultController) newTGInstance(ctx context.Context, name str
128139
return tgInstance, nil
129140
}
130141

131-
func (controller *defaultController) reconcileTGInstance(ctx context.Context, instance *elbv2.TargetGroup, serviceAnnos *annotations.Service) (*elbv2.TargetGroup, error) {
142+
func (controller *defaultController) reconcileTGInstance(ctx context.Context, instance *elbv2.TargetGroup, serviceAnnos *annotations.Service, healthCheckPort string) (*elbv2.TargetGroup, error) {
132143
if controller.TGInstanceNeedsModification(ctx, instance, serviceAnnos) {
133144
albctx.GetLogger(ctx).Infof("modify target group %v", aws.StringValue(instance.TargetGroupArn))
145+
134146
output, err := controller.cloud.ModifyTargetGroupWithContext(ctx, &elbv2.ModifyTargetGroupInput{
135147
TargetGroupArn: instance.TargetGroupArn,
136148
HealthCheckPath: serviceAnnos.HealthCheck.Path,
137149
HealthCheckIntervalSeconds: serviceAnnos.HealthCheck.IntervalSeconds,
138-
HealthCheckPort: serviceAnnos.HealthCheck.Port,
150+
HealthCheckPort: aws.String(healthCheckPort),
139151
HealthCheckProtocol: serviceAnnos.HealthCheck.Protocol,
140152
HealthCheckTimeoutSeconds: serviceAnnos.HealthCheck.TimeoutSeconds,
141153
Matcher: &elbv2.Matcher{HttpCode: serviceAnnos.TargetGroup.SuccessCodes},
@@ -150,6 +162,43 @@ func (controller *defaultController) reconcileTGInstance(ctx context.Context, in
150162
return instance, nil
151163
}
152164

165+
// resolveServiceHealthCheckPort checks if the service-port annotation is a string. If so, it tries to look up a port with the same name
166+
// on the service and use that port's NodePort as the health check port.
167+
func (controller *defaultController) resolveServiceHealthCheckPort(namespace string, serviceName string, servicePortAnnotation intstr.IntOrString, targetType string) (string, error) {
168+
169+
if servicePortAnnotation.Type == intstr.Int {
170+
//Nothing to do if it's an Int - return original value
171+
return servicePortAnnotation.String(), nil
172+
}
173+
174+
servicePort := servicePortAnnotation.String()
175+
176+
//If the annotation uses the default port ("traffic-port"), do not try to look up a port by that name.
177+
if servicePort == healthcheck.DefaultPort {
178+
return servicePort, nil
179+
}
180+
181+
serviceKey := namespace + "/" + serviceName
182+
service, err := controller.store.GetService(serviceKey)
183+
184+
if err != nil {
185+
return servicePort, errors.Wrap(err, "failed to resolve healthcheck service name")
186+
}
187+
188+
resolvedServicePort, err := k8s.LookupServicePort(service, servicePortAnnotation)
189+
if err != nil {
190+
return servicePort, errors.Wrap(err, "failed to resolve healthcheck port for service")
191+
}
192+
if targetType == elbv2.TargetTypeEnumInstance {
193+
if resolvedServicePort.NodePort == 0 {
194+
return servicePort, fmt.Errorf("failed to find valid NodePort for service %s with port %s", serviceName, resolvedServicePort.Name)
195+
}
196+
return strconv.Itoa(int(resolvedServicePort.NodePort)), nil
197+
}
198+
return resolvedServicePort.TargetPort.String(), nil
199+
200+
}
201+
153202
func (controller *defaultController) TGInstanceNeedsModification(ctx context.Context, instance *elbv2.TargetGroup, serviceAnnos *annotations.Service) bool {
154203
needsChange := false
155204
if !util.DeepEqual(instance.HealthCheckPath, serviceAnnos.HealthCheck.Path) {

0 commit comments

Comments
 (0)