Skip to content

Commit 8795a44

Browse files
authored
Feature to delete cluster resources when TTL expires (#360)
* Feature to delete cluster resources when TTL expires * Fix k8s version 1.25 cluster creation
1 parent 06baa4e commit 8795a44

File tree

4 files changed

+82
-14
lines changed

4 files changed

+82
-14
lines changed

operator/charts/kit-operator/crds/control-plane-crd.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9634,6 +9634,8 @@ spec:
96349634
type: object
96359635
type: object
96369636
type: object
9637+
ttl:
9638+
type: string
96379639
type: object
96389640
status:
96399641
properties:

operator/pkg/apis/controlplane/v1alpha1/controlplane.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ type ControlPlaneList struct {
4646
// master and etcd are configured to run. By default, KIT uses all the default
4747
// values and ControlPlaneSpec can be empty.
4848
type ControlPlaneSpec struct {
49-
KubernetesVersion string `json:"kubernetesVersion,omitempty"`
50-
ColocateAPIServerWithEtcd bool `json:"colocateAPIServerWithEtcd,omitempty"`
51-
Master MasterSpec `json:"master,omitempty"`
52-
Etcd Etcd `json:"etcd,omitempty"`
49+
KubernetesVersion string `json:"kubernetesVersion,omitempty"`
50+
ColocateAPIServerWithEtcd bool `json:"colocateAPIServerWithEtcd,omitempty"`
51+
// TTL is the duration for which control plane resources are active, once expired resource will be automatically deleted by the operator
52+
TTL string `json:"ttl,omitempty"`
53+
Master MasterSpec `json:"master,omitempty"`
54+
Etcd Etcd `json:"etcd,omitempty"`
5355
}
5456

5557
type Etcd struct {

operator/pkg/controllers/controller.go

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"fmt"
2020
"reflect"
21+
"time"
2122

2223
"github.com/awslabs/kubernetes-iteration-toolkit/operator/pkg/apis/controlplane/v1alpha1"
2324
"github.com/awslabs/kubernetes-iteration-toolkit/operator/pkg/errors"
@@ -81,7 +82,26 @@ func (c *GenericController) reconcile(ctx context.Context, resource Object, pers
8182
existingFinalizers := resource.GetFinalizers()
8283
existingFinalizerSet := sets.NewString(existingFinalizers...)
8384
finalizerStr := sets.NewString(fmt.Sprintf(FinalizerForAWSResources, c.Name()))
84-
if resource.GetDeletionTimestamp() == nil {
85+
if resource.GetDeletionTimestamp() == nil && ttlExpiredForControlPlane(resource) {
86+
// when ttl expires for the cluster, finalize all sub resources
87+
if result, err = c.finalizeSubResources(ctx, resource, existingFinalizerSet.Difference(finalizerStr).UnsortedList()); err != nil {
88+
return *results.Failed, fmt.Errorf("finalizing resource controller %w", err)
89+
}
90+
if result.Requeue || result.RequeueAfter > 0 {
91+
return *result, nil
92+
}
93+
// delete the CP object since it doesn't get auto deleted.
94+
if err := c.Delete(ctx, resource); err != nil {
95+
return *results.Failed, fmt.Errorf("deleting CP object, %w", err)
96+
}
97+
// once the object is deleted we don't need to merge patch anymore
98+
return reconcile.Result{}, nil
99+
}
100+
if resource.GetDeletionTimestamp() != nil {
101+
if result, err = c.finalizeSubResources(ctx, resource, existingFinalizerSet.Difference(finalizerStr).UnsortedList()); err != nil {
102+
return *results.Failed, fmt.Errorf("finalizing resource controller %v, %w", c.Controller.Name(), err)
103+
}
104+
} else {
85105
// Add finalizer for this controller
86106
resource.SetFinalizers(existingFinalizerSet.Union(finalizerStr).UnsortedList())
87107
result, err = c.Controller.Reconcile(ctx, resource)
@@ -90,13 +110,6 @@ func (c *GenericController) reconcile(ctx context.Context, resource Object, pers
90110
return *results.Failed, fmt.Errorf("reconciling resource, %w", err)
91111
}
92112
resource.StatusConditions().MarkTrue(v1alpha1.Active)
93-
} else {
94-
if result, err = c.Controller.Finalize(ctx, resource); err != nil {
95-
return *results.Failed, fmt.Errorf("finalizing resource controller %v, %w", c.Controller.Name(), err)
96-
}
97-
// Remove finalizer for this controller
98-
resource.SetFinalizers(existingFinalizerSet.Difference(finalizerStr).UnsortedList())
99-
zap.S().Infof("[%s] Successfully deleted", resource.GetName())
100113
}
101114
// If the finalizers have changed merge patch the object
102115
if !reflect.DeepEqual(existingFinalizers, resource.GetFinalizers()) {
@@ -106,3 +119,34 @@ func (c *GenericController) reconcile(ctx context.Context, resource Object, pers
106119
}
107120
return *result, nil
108121
}
122+
123+
func (c *GenericController) finalizeSubResources(ctx context.Context, resource Object, finalizers []string) (*reconcile.Result, error) {
124+
result, err := c.Controller.Finalize(ctx, resource)
125+
if err != nil {
126+
return result, fmt.Errorf("finalizing resource controller %v, %w", c.Controller.Name(), err)
127+
}
128+
// Remove finalizer for this controller
129+
resource.SetFinalizers(finalizers)
130+
zap.S().Infof("[%s] Successfully deleted resources", resource.GetName())
131+
return result, nil
132+
}
133+
134+
func ttlExpiredForControlPlane(resource Object) bool {
135+
cp, ok := resource.(*v1alpha1.ControlPlane)
136+
if !ok {
137+
return false
138+
}
139+
if cp.Spec.TTL != "" {
140+
duration, err := time.ParseDuration(cp.Spec.TTL)
141+
if err != nil {
142+
zap.S().Errorf("parsing TTL duration, %w", err)
143+
return false
144+
}
145+
deleteAfter := resource.GetCreationTimestamp().Add(duration)
146+
if time.Now().After(deleteAfter) {
147+
zap.S().Infof("[%v] control plane TTL expired, deleting cluster resources", cp.ClusterName())
148+
return true
149+
}
150+
}
151+
return false
152+
}

operator/pkg/controllers/master/kubeapiserver.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package master
1717
import (
1818
"context"
1919
"fmt"
20+
"strings"
2021

2122
"github.com/aws/aws-sdk-go/aws"
2223
"github.com/awslabs/kubernetes-iteration-toolkit/operator/pkg/apis/controlplane/v1alpha1"
@@ -79,7 +80,7 @@ func APIServerLabels(clustername string) map[string]string {
7980
func apiServerPodSpecFor(controlPlane *v1alpha1.ControlPlane) v1.PodSpec {
8081
hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
8182
hostPathDirectory := v1.HostPathDirectory
82-
return v1.PodSpec{
83+
return apiServerPodSpecForVersion(controlPlane.Spec.KubernetesVersion, &v1.PodSpec{
8384
TerminationGracePeriodSeconds: aws.Int64(1),
8485
HostNetwork: true,
8586
DNSPolicy: v1.DNSClusterFirstWithHostNet,
@@ -425,7 +426,7 @@ func apiServerPodSpecFor(controlPlane *v1alpha1.ControlPlane) v1.PodSpec {
425426
},
426427
},
427428
}},
428-
}
429+
})
429430
}
430431

431432
func affinity(colocateAPIServerWithEtcd bool) *v1.Affinity {
@@ -449,3 +450,22 @@ func nodeSelector(clusterName string, colocateWithEtcd bool) map[string]string {
449450
}
450451
return selector
451452
}
453+
454+
var (
455+
disabledFlagsForAPIServer = map[string]struct{}{"--feature-gates": {}}
456+
)
457+
458+
func apiServerPodSpecForVersion(version string, defaultSpec *v1.PodSpec) v1.PodSpec {
459+
switch version {
460+
case "1.25":
461+
args := []string{}
462+
for _, arg := range defaultSpec.Containers[0].Args {
463+
if _, skip := disabledFlagsForAPIServer[strings.Split(arg, "=")[0]]; skip {
464+
continue
465+
}
466+
args = append(args, arg)
467+
}
468+
defaultSpec.Containers[0].Args = args
469+
}
470+
return *defaultSpec
471+
}

0 commit comments

Comments
 (0)