Skip to content

Commit daab273

Browse files
authored
Merge pull request #3610 from richardcase/1718_gc_new_service
feat: external load balancer garbage collection (part 2) - new gc service
2 parents 87d8405 + c25778c commit daab273

24 files changed

+37157
-15
lines changed

controlplane/eks/controllers/awsmanagedcontrolplane_controller_unit_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,

controlplane/eks/controllers/helpers_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,

exp/api/v1beta1/awsfargateprofile_types.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ import (
2727
"sigs.k8s.io/cluster-api/errors"
2828
)
2929

30-
const (
31-
// FargateProfileFinalizer allows the controller to clean up resources on delete.
32-
FargateProfileFinalizer = "awsfargateprofile.infrastructure.cluster.x-k8s.io"
33-
)
34-
3530
var (
3631
// DefaultEKSFargateRole is the name of the default IAM role to use for fargate
3732
// profiles if no other role is supplied in the spec and if iam role creation

exp/api/v1beta1/awsmachinepool_types.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ import (
2727

2828
// Constants block.
2929
const (
30-
// MachinePoolFinalizer is the finalizer for the machine pool.
31-
MachinePoolFinalizer = "awsmachinepool.infrastructure.cluster.x-k8s.io"
32-
3330
// LaunchTemplateLatestVersion defines the launching of the latest version of the template.
3431
LaunchTemplateLatestVersion = "$Latest"
3532
)

exp/api/v1beta1/awsmanagedmachinepool_types.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ import (
2727
"sigs.k8s.io/cluster-api/errors"
2828
)
2929

30-
const (
31-
// ManagedMachinePoolFinalizer allows the controller to clean up resources on delete.
32-
ManagedMachinePoolFinalizer = "awsmanagedmachinepools.infrastructure.cluster.x-k8s.io"
33-
)
34-
3530
// ManagedMachineAMIType specifies which AWS AMI to use for a managed MachinePool.
3631
type ManagedMachineAMIType string
3732

exp/api/v1beta1/finalizers.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2022 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 v1beta1
18+
19+
const (
20+
// FargateProfileFinalizer allows the controller to clean up resources on delete.
21+
FargateProfileFinalizer = "awsfargateprofile.infrastructure.cluster.x-k8s.io"
22+
23+
// MachinePoolFinalizer is the finalizer for the machine pool.
24+
MachinePoolFinalizer = "awsmachinepool.infrastructure.cluster.x-k8s.io"
25+
26+
// ManagedMachinePoolFinalizer allows the controller to clean up resources on delete.
27+
ManagedMachinePoolFinalizer = "awsmanagedmachinepools.infrastructure.cluster.x-k8s.io"
28+
)

exp/api/v1beta1/types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import (
2222
infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
2323
)
2424

25+
const (
26+
// ExternalResourceGCAnnotation is the name of an annotation that indicates if
27+
// external resources should be garbage collected for the cluster.
28+
ExternalResourceGCAnnotation = "aws.cluster.x-k8s.io/external-resource-gc"
29+
)
30+
2531
// EBS can be used to automatically set up EBS volumes when an instance is launched.
2632
type EBS struct {
2733
// Encrypted is whether the volume should be encrypted or not.

pkg/annotations/annotations.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Copyright 2022 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 annotations
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
// Set will set the value of an annotation on the supplied object. If there is no annotation it will be created.
24+
func Set(obj metav1.Object, name, value string) {
25+
annotations := obj.GetAnnotations()
26+
if annotations == nil {
27+
annotations = map[string]string{}
28+
}
29+
annotations[name] = value
30+
obj.SetAnnotations(annotations)
31+
}
32+
33+
// Get will get the value of the supplied annotation.
34+
func Get(obj metav1.Object, name string) (value string, found bool) {
35+
annotations := obj.GetAnnotations()
36+
if len(annotations) == 0 {
37+
return "", false
38+
}
39+
40+
value, found = annotations[name]
41+
42+
return
43+
}
44+
45+
// Has returns true if the supplied object has the supplied annotation.
46+
func Has(obj metav1.Object, name string) bool {
47+
annotations := obj.GetAnnotations()
48+
if len(annotations) == 0 {
49+
return false
50+
}
51+
52+
_, found := annotations[name]
53+
54+
return found
55+
}

pkg/cloud/scope/clients.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828
"github.com/aws/aws-sdk-go/service/eks/eksiface"
2929
"github.com/aws/aws-sdk-go/service/elb"
3030
"github.com/aws/aws-sdk-go/service/elb/elbiface"
31+
"github.com/aws/aws-sdk-go/service/elbv2"
32+
"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
3133
"github.com/aws/aws-sdk-go/service/eventbridge"
3234
"github.com/aws/aws-sdk-go/service/eventbridge/eventbridgeiface"
3335
"github.com/aws/aws-sdk-go/service/iam"
@@ -91,6 +93,18 @@ func NewELBClient(scopeUser cloud.ScopeUsage, session cloud.Session, logger clou
9193
return elbClient
9294
}
9395

96+
// NewELBv2Client creates a new ELB v2 API client for a given session.
97+
func NewELBv2Client(scopeUser cloud.ScopeUsage, session cloud.Session, logger cloud.Logger, target runtime.Object) elbv2iface.ELBV2API {
98+
elbClient := elbv2.New(session.Session(), aws.NewConfig().WithLogLevel(awslogs.GetAWSLogLevel(logger)).WithLogger(awslogs.NewWrapLogr(logger)))
99+
elbClient.Handlers.Build.PushFrontNamed(getUserAgentHandler())
100+
elbClient.Handlers.Sign.PushFront(session.ServiceLimiter(elbv2.ServiceID).LimitRequest)
101+
elbClient.Handlers.CompleteAttempt.PushFront(awsmetrics.CaptureRequestMetrics(scopeUser.ControllerName()))
102+
elbClient.Handlers.CompleteAttempt.PushFront(session.ServiceLimiter(elbv2.ServiceID).ReviewResponse)
103+
elbClient.Handlers.Complete.PushBack(recordAWSPermissionsIssue(target))
104+
105+
return elbClient
106+
}
107+
94108
// NewEventBridgeClient creates a new EventBridge API client for a given session.
95109
func NewEventBridgeClient(scopeUser cloud.ScopeUsage, session cloud.Session, target runtime.Object) eventbridgeiface.EventBridgeAPI {
96110
eventBridgeClient := eventbridge.New(session.Session())

pkg/cloud/scope/session.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/aws/aws-sdk-go/aws/session"
2828
"github.com/aws/aws-sdk-go/service/ec2"
2929
"github.com/aws/aws-sdk-go/service/elb"
30+
"github.com/aws/aws-sdk-go/service/elbv2"
3031
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
3132
"github.com/aws/aws-sdk-go/service/secretsmanager"
3233
"github.com/go-logr/logr"
@@ -189,6 +190,7 @@ func newServiceLimiters() throttle.ServiceLimiters {
189190
return throttle.ServiceLimiters{
190191
ec2.ServiceID: newEC2ServiceLimiter(),
191192
elb.ServiceID: newGenericServiceLimiter(),
193+
elbv2.ServiceID: newGenericServiceLimiter(),
192194
resourcegroupstaggingapi.ServiceID: newGenericServiceLimiter(),
193195
secretsmanager.ServiceID: newGenericServiceLimiter(),
194196
}

pkg/cloud/services/awsnode/cni_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
Copyright 2022 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+
117
package awsnode
218

319
import (

pkg/cloud/services/gc/cleanup.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
Copyright 2022 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 gc
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strconv"
23+
"strings"
24+
25+
"github.com/aws/aws-sdk-go/aws"
26+
"github.com/aws/aws-sdk-go/aws/arn"
27+
rgapi "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
28+
29+
infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
30+
expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1"
31+
"sigs.k8s.io/cluster-api-provider-aws/pkg/annotations"
32+
)
33+
34+
const (
35+
serviceNameTag = "kubernetes.io/service-name"
36+
eksClusterNameTag = "aws:eks:cluster-name"
37+
)
38+
39+
// ReconcileDelete is responsible for determining if the infra cluster needs to be garbage collected. If
40+
// does then it will perform garbage collection. For example, it will delete the ELB/NLBs that where created
41+
// as a result of Services of type load balancer.
42+
func (s *Service) ReconcileDelete(ctx context.Context) error {
43+
s.scope.Info("reconciling deletion for garbage collection")
44+
45+
val, found := annotations.Get(s.scope.InfraCluster(), expinfrav1.ExternalResourceGCAnnotation)
46+
if !found {
47+
val = "true"
48+
}
49+
50+
shouldGC, err := strconv.ParseBool(val)
51+
if err != nil {
52+
return fmt.Errorf("converting value %s of annotation %s to bool: %w", val, expinfrav1.ExternalResourceGCAnnotation, err)
53+
}
54+
55+
if !shouldGC {
56+
s.scope.Info("cluster opted-out of garbage collection")
57+
58+
return nil
59+
}
60+
61+
return s.deleteResources(ctx)
62+
}
63+
64+
func (s *Service) deleteResources(ctx context.Context) error {
65+
s.scope.Info("deleting aws resources created by tenant cluster")
66+
67+
serviceTag := infrav1.ClusterAWSCloudProviderTagKey(s.scope.KubernetesClusterName())
68+
awsInput := rgapi.GetResourcesInput{
69+
ResourceTypeFilters: nil,
70+
TagFilters: []*rgapi.TagFilter{
71+
{
72+
Key: aws.String(serviceTag),
73+
Values: []*string{aws.String(string(infrav1.ResourceLifecycleOwned))},
74+
},
75+
},
76+
}
77+
78+
awsOutput, err := s.resourceTaggingClient.GetResourcesWithContext(ctx, &awsInput)
79+
if err != nil {
80+
return fmt.Errorf("getting tagged resources: %w", err)
81+
}
82+
83+
resources := []*AWSResource{}
84+
85+
for i := range awsOutput.ResourceTagMappingList {
86+
mapping := awsOutput.ResourceTagMappingList[i]
87+
parsedArn, err := arn.Parse(*mapping.ResourceARN)
88+
if err != nil {
89+
return fmt.Errorf("parsing resource arn %s: %w", *mapping.ResourceARN, err)
90+
}
91+
92+
tags := map[string]string{}
93+
for _, rgTag := range mapping.Tags {
94+
tags[*rgTag.Key] = *rgTag.Value
95+
}
96+
97+
resources = append(resources, &AWSResource{
98+
ARN: &parsedArn,
99+
Tags: tags,
100+
})
101+
}
102+
103+
if deleteErr := s.cleanupFuncs.Execute(ctx, resources); deleteErr != nil {
104+
return fmt.Errorf("deleting resources: %w", deleteErr)
105+
}
106+
107+
return nil
108+
}
109+
110+
func (s *Service) isMatchingResource(resource *AWSResource, serviceName, resourceName string) bool {
111+
if resource.ARN.Service != serviceName {
112+
s.scope.V(5).Info("Resource not for service", "arn", resource.ARN.String(), "service_name", serviceName, "resource_name", resourceName)
113+
return false
114+
}
115+
if !strings.HasPrefix(resource.ARN.Resource, resourceName+"/") {
116+
s.scope.V(5).Info("Resource type does not match", "arn", resource.ARN.String(), "service_name", serviceName, "resource_name", resourceName)
117+
return false
118+
}
119+
120+
return true
121+
}

0 commit comments

Comments
 (0)