Sitelet https://github.com/kubernetes/kops/commit/1c57cb29a66120ed54d922aa2cce9f28831b2f7a
Skip to content

Commit 1c57cb2

Browse files
authored
Merge pull request #18467 from hakman/aws-reconcile-tg-health-check
aws: Reconcile target group health check changes on existing target groups
2 parents 5af7a9f + 7cbe579 commit 1c57cb2

4 files changed

Lines changed: 157 additions & 0 deletions

File tree

‎cloudmock/aws/mockelbv2/targetgroups.go‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,37 @@ func (m *MockELBV2) ModifyTargetGroupAttributes(ctx context.Context, request *el
143143
m.TargetGroups[arn].attributes = request.Attributes
144144
return &elbv2.ModifyTargetGroupAttributesOutput{Attributes: request.Attributes}, nil
145145
}
146+
147+
func (m *MockELBV2) ModifyTargetGroup(ctx context.Context, request *elbv2.ModifyTargetGroupInput, optFns ...func(*elbv2.Options)) (*elbv2.ModifyTargetGroupOutput, error) {
148+
m.mutex.Lock()
149+
defer m.mutex.Unlock()
150+
151+
klog.Infof("ModifyTargetGroup %v", request)
152+
153+
// Layer 7 health checks require a path and a return code, matching the AWS API validation.
154+
proto := request.HealthCheckProtocol
155+
if proto == elbv2types.ProtocolEnumHttp || proto == elbv2types.ProtocolEnumHttps {
156+
if request.HealthCheckPath == nil || request.Matcher == nil {
157+
return nil, fmt.Errorf("ValidationError: Path and return code are required for layer 7 health checks")
158+
}
159+
}
160+
161+
arn := aws.ToString(request.TargetGroupArn)
162+
tg := m.TargetGroups[arn]
163+
if request.HealthCheckProtocol != "" {
164+
tg.description.HealthCheckProtocol = request.HealthCheckProtocol
165+
}
166+
if request.HealthCheckPath != nil {
167+
tg.description.HealthCheckPath = request.HealthCheckPath
168+
}
169+
if request.Matcher != nil {
170+
tg.description.Matcher = request.Matcher
171+
}
172+
if request.HealthyThresholdCount != nil {
173+
tg.description.HealthyThresholdCount = request.HealthyThresholdCount
174+
}
175+
if request.UnhealthyThresholdCount != nil {
176+
tg.description.UnhealthyThresholdCount = request.UnhealthyThresholdCount
177+
}
178+
return &elbv2.ModifyTargetGroupOutput{TargetGroups: []elbv2types.TargetGroup{tg.description}}, nil
179+
}

‎upup/pkg/fi/cloudup/awstasks/targetgroup.go‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,26 @@ func (_ *TargetGroup) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *TargetGrou
392392
if err := ModifyTargetGroupAttributes(ctx, t.Cloud, a.ARN, e.Attributes); err != nil {
393393
return err
394394
}
395+
// Health check settings are only applied on create, so reconcile them here for an existing target group.
396+
if changes.HealthCheckProtocol != "" || changes.HealthCheckPath != nil ||
397+
changes.HealthyThreshold != nil || changes.UnhealthyThreshold != nil {
398+
klog.V(2).Infof("Modifying Target Group health check for NLB")
399+
proto := e.HealthCheckProtocol
400+
request := &elbv2.ModifyTargetGroupInput{
401+
TargetGroupArn: a.ARN,
402+
HealthCheckProtocol: proto,
403+
HealthyThresholdCount: e.HealthyThreshold,
404+
UnhealthyThresholdCount: e.UnhealthyThreshold,
405+
}
406+
// HTTP/HTTPS health checks need a path and matcher, 200-399 matches the NLB create default.
407+
if proto == elbv2types.ProtocolEnumHttp || proto == elbv2types.ProtocolEnumHttps {
408+
request.HealthCheckPath = e.HealthCheckPath
409+
request.Matcher = &elbv2types.Matcher{HttpCode: fi.PtrTo("200-399")}
410+
}
411+
if _, err := t.Cloud.ELBV2().ModifyTargetGroup(ctx, request); err != nil {
412+
return fmt.Errorf("modifying NLB target group health check: %w", err)
413+
}
414+
}
395415
}
396416
}
397417
return nil
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
Copyright 2026 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package awstasks
18+
19+
import (
20+
"context"
21+
"testing"
22+
23+
"github.com/aws/aws-sdk-go-v2/aws"
24+
"github.com/aws/aws-sdk-go-v2/service/ec2"
25+
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
26+
elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
27+
"k8s.io/kops/cloudmock/aws/mockec2"
28+
"k8s.io/kops/cloudmock/aws/mockelbv2"
29+
"k8s.io/kops/upup/pkg/fi"
30+
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
31+
)
32+
33+
// TestTargetGroupHealthCheckChange verifies that a health check change on an existing target group is applied,
34+
// instead of recurring as an unapplied change on every run.
35+
func TestTargetGroupHealthCheckChange(t *testing.T) {
36+
ctx := context.TODO()
37+
38+
cloud := awsup.BuildMockAWSCloud("us-east-1", "abc")
39+
c := &mockec2.MockEC2{}
40+
cloud.MockEC2 = c
41+
cloud.MockELBV2 = &mockelbv2.MockELBV2{EC2: c}
42+
43+
// Pre-create the VPC
44+
vpc, err := c.CreateVpc(ctx, &ec2.CreateVpcInput{
45+
CidrBlock: aws.String("172.20.0.0/16"),
46+
})
47+
if err != nil {
48+
t.Fatalf("error creating test VPC: %v", err)
49+
}
50+
_, err = c.CreateTags(ctx, &ec2.CreateTagsInput{
51+
Resources: []string{aws.ToString(vpc.Vpc.VpcId)},
52+
Tags: []ec2types.Tag{
53+
{
54+
Key: aws.String("Name"),
55+
Value: aws.String("ExistingVPC"),
56+
},
57+
},
58+
})
59+
if err != nil {
60+
t.Fatalf("error tagging test vpc: %v", err)
61+
}
62+
63+
// We define a function so we can rebuild the tasks, because we modify in-place when running
64+
buildTasks := func(healthCheckProtocol elbv2types.ProtocolEnum, healthCheckPath *string) map[string]fi.CloudupTask {
65+
vpc1 := &VPC{
66+
Name: s("vpc1"),
67+
Lifecycle: fi.LifecycleSync,
68+
CIDR: s("172.20.0.0/16"),
69+
Tags: map[string]string{"kubernetes.io/cluster/cluster.example.com": "shared"},
70+
Shared: fi.PtrTo(true),
71+
ID: vpc.Vpc.VpcId,
72+
}
73+
tg1 := &TargetGroup{
74+
Name: s("tg1"),
75+
Lifecycle: fi.LifecycleSync,
76+
VPC: vpc1,
77+
Tags: map[string]string{"Name": "tg1"},
78+
Protocol: elbv2types.ProtocolEnumTcp,
79+
Port: fi.PtrTo(int32(3988)),
80+
Interval: fi.PtrTo(int32(10)),
81+
HealthyThreshold: fi.PtrTo(int32(2)),
82+
UnhealthyThreshold: fi.PtrTo(int32(2)),
83+
HealthCheckProtocol: healthCheckProtocol,
84+
HealthCheckPath: healthCheckPath,
85+
Shared: fi.PtrTo(false),
86+
}
87+
88+
return map[string]fi.CloudupTask{
89+
"vpc1": vpc1,
90+
"tg1": tg1,
91+
}
92+
}
93+
94+
// Create the target group with a TCP health check (as an older kOps version would).
95+
runTasks(t, cloud, buildTasks(elbv2types.ProtocolEnumTcp, nil))
96+
97+
// Upgrade to an HTTPS health check with a path.
98+
runTasks(t, cloud, buildTasks(elbv2types.ProtocolEnumHttps, s("/healthz")))
99+
100+
// The change must have been applied, so a subsequent run sees no changes.
101+
checkNoChanges(t, ctx, cloud, buildTasks(elbv2types.ProtocolEnumHttps, s("/healthz")))
102+
}

‎util/pkg/awsinterfaces/elbv2.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type ELBV2API interface {
3939
DescribeTargetGroups(ctx context.Context, input *elbv2.DescribeTargetGroupsInput, optFns ...func(*elbv2.Options)) (*elbv2.DescribeTargetGroupsOutput, error)
4040
DescribeTargetHealth(ctx context.Context, input *elbv2.DescribeTargetHealthInput, optFns ...func(*elbv2.Options)) (*elbv2.DescribeTargetHealthOutput, error)
4141
ModifyLoadBalancerAttributes(ctx context.Context, input *elbv2.ModifyLoadBalancerAttributesInput, optFns ...func(*elbv2.Options)) (*elbv2.ModifyLoadBalancerAttributesOutput, error)
42+
ModifyTargetGroup(ctx context.Context, input *elbv2.ModifyTargetGroupInput, optFns ...func(*elbv2.Options)) (*elbv2.ModifyTargetGroupOutput, error)
4243
ModifyTargetGroupAttributes(ctx context.Context, input *elbv2.ModifyTargetGroupAttributesInput, optFns ...func(*elbv2.Options)) (*elbv2.ModifyTargetGroupAttributesOutput, error)
4344
RemoveTags(ctx context.Context, input *elbv2.RemoveTagsInput, optFns ...func(*elbv2.Options)) (*elbv2.RemoveTagsOutput, error)
4445
SetIpAddressType(ctx context.Context, input *elbv2.SetIpAddressTypeInput, optFns ...func(*elbv2.Options)) (*elbv2.SetIpAddressTypeOutput, error)

0 commit comments

Comments
 (0)