Skip to content

Commit 70e3003

Browse files
committed
Support EKS upgrade policy
1 parent e9f2823 commit 70e3003

File tree

7 files changed

+201
-0
lines changed

7 files changed

+201
-0
lines changed

config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -2982,6 +2982,16 @@ spec:
29822982
- iam-authenticator
29832983
- aws-cli
29842984
type: string
2985+
upgradePolicy:
2986+
description: |-
2987+
The support policy to use for the cluster.
2988+
Extended support allows you to remain on specific Kubernetes versions for longer.
2989+
Clusters in extended support have higher costs.
2990+
The default value is EXTENDED. Use STANDARD to disable extended support.
2991+
enum:
2992+
- EXTENDED
2993+
- STANDARD
2994+
type: string
29852995
version:
29862996
description: |-
29872997
Version defines the desired Kubernetes version. If no version number

controlplane/eks/api/v1beta1/conversion.go

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func (r *AWSManagedControlPlane) ConvertTo(dstRaw conversion.Hub) error {
4646
dst.Spec.RolePermissionsBoundary = restored.Spec.RolePermissionsBoundary
4747
dst.Status.Version = restored.Status.Version
4848
dst.Spec.BootstrapSelfManagedAddons = restored.Spec.BootstrapSelfManagedAddons
49+
dst.Spec.UpgradePolicy = restored.Spec.UpgradePolicy
4950
return nil
5051
}
5152

controlplane/eks/api/v1beta1/zz_generated.conversion.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go

+8
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ type AWSManagedControlPlaneSpec struct { //nolint: maligned
209209

210210
// KubeProxy defines managed attributes of the kube-proxy daemonset
211211
KubeProxy KubeProxy `json:"kubeProxy,omitempty"`
212+
213+
// The support policy to use for the cluster.
214+
// Extended support allows you to remain on specific Kubernetes versions for longer.
215+
// Clusters in extended support have higher costs.
216+
// The default value is EXTENDED. Use STANDARD to disable extended support.
217+
// +kubebuilder:validation:Enum=EXTENDED;STANDARD
218+
// +optional
219+
UpgradePolicy *string `json:"upgradePolicy,omitempty"`
212220
}
213221

214222
// KubeProxy specifies how the kube-proxy daemonset is managed.

controlplane/eks/api/v1beta2/zz_generated.deepcopy.go

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cloud/services/eks/cluster.go

+71
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ func (s *Service) reconcileCluster(ctx context.Context) error {
139139
return errors.Wrap(err, "failed reconciling OIDC provider for cluster")
140140
}
141141

142+
if err := s.reconcileClusterUpgradePolicy(cluster); err != nil {
143+
return errors.Wrap(err, "failed reconciling cluster version")
144+
}
145+
142146
return nil
143147
}
144148

@@ -463,6 +467,15 @@ func (s *Service) createCluster(eksClusterName string) (*eks.Cluster, error) {
463467
v := versionToEKS(specVersion)
464468
eksVersion = &v
465469
}
470+
471+
var upgradePolicy *eks.UpgradePolicyRequest
472+
473+
if s.scope.ControlPlane.Spec.UpgradePolicy != nil {
474+
upgradePolicy = &eks.UpgradePolicyRequest{
475+
SupportType: s.scope.ControlPlane.Spec.UpgradePolicy,
476+
}
477+
}
478+
466479
input := &eks.CreateClusterInput{
467480
Name: aws.String(eksClusterName),
468481
Version: eksVersion,
@@ -472,6 +485,7 @@ func (s *Service) createCluster(eksClusterName string) (*eks.Cluster, error) {
472485
RoleArn: role.Arn,
473486
Tags: tags,
474487
KubernetesNetworkConfig: netConfig,
488+
UpgradePolicy: upgradePolicy,
475489
}
476490
// Only set BootstrapSelfManagedAddons if it's explicitly set to false in the spec
477491
// Default is true, so we don't need to set it in that case
@@ -725,6 +739,63 @@ func (s *Service) reconcileClusterVersion(cluster *eks.Cluster) error {
725739
return nil
726740
}
727741

742+
func (s *Service) reconcileClusterUpgradePolicy(cluster *eks.Cluster) error {
743+
s.Info("reconciling upgrade policy")
744+
745+
clusterUpgradePolicy := cluster.UpgradePolicy.SupportType
746+
747+
if s.scope.ControlPlane.Spec.UpgradePolicy == nil {
748+
s.Debug("upgrade policy not given, no action")
749+
return nil
750+
}
751+
752+
if clusterUpgradePolicy == nil {
753+
s.Debug("cannot get cluster upgrade policy, no action")
754+
return nil
755+
}
756+
757+
if *clusterUpgradePolicy == *s.scope.ControlPlane.Spec.UpgradePolicy {
758+
s.Debug("upgrade policy unchanged, no action")
759+
return nil
760+
}
761+
762+
input := &eks.UpdateClusterConfigInput{
763+
Name: aws.String(s.scope.KubernetesClusterName()),
764+
UpgradePolicy: &eks.UpgradePolicyRequest{
765+
SupportType: s.scope.ControlPlane.Spec.UpgradePolicy,
766+
},
767+
}
768+
769+
if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) {
770+
if _, err := s.EKSClient.UpdateClusterConfig(input); err != nil {
771+
if aerr, ok := err.(awserr.Error); ok {
772+
return false, aerr
773+
}
774+
return false, err
775+
}
776+
777+
// Wait until status transitions to UPDATING because there's a short
778+
// window after UpdateClusterConfig returns where the cluster
779+
// status is ACTIVE and the update would be tried again
780+
if err := s.EKSClient.WaitUntilClusterUpdating(
781+
&eks.DescribeClusterInput{Name: aws.String(s.scope.KubernetesClusterName())},
782+
request.WithWaiterLogger(&awslog{s.GetLogger()}),
783+
); err != nil {
784+
return false, err
785+
}
786+
787+
conditions.MarkTrue(s.scope.ControlPlane, ekscontrolplanev1.EKSControlPlaneUpdatingCondition)
788+
record.Eventf(s.scope.ControlPlane, "InitiatedUpdateEKSControlPlane", "Initiated update of EKS control plane %s to upgrade policy %s", s.scope.KubernetesClusterName(), s.scope.ControlPlane.Spec.UpgradePolicy)
789+
790+
return true, nil
791+
}); err != nil {
792+
record.Warnf(s.scope.ControlPlane, "FailedUpdateEKSControlPlane", "failed to update the EKS control plane: %v", err)
793+
return errors.Wrapf(err, "failed to update EKS cluster")
794+
}
795+
796+
return nil
797+
}
798+
728799
func (s *Service) describeEKSCluster(eksClusterName string) (*eks.Cluster, error) {
729800
input := &eks.DescribeClusterInput{
730801
Name: aws.String(eksClusterName),

pkg/cloud/services/eks/cluster_test.go

+105
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ func TestCreateCluster(t *testing.T) {
524524
RoleName: tc.role,
525525
NetworkSpec: infrav1.NetworkSpec{Subnets: tc.subnets},
526526
BootstrapSelfManagedAddons: false,
527+
UpgradePolicy: aws.String(eks.SupportTypeStandard),
527528
},
528529
},
529530
})
@@ -546,6 +547,9 @@ func TestCreateCluster(t *testing.T) {
546547
Tags: tc.tags,
547548
Version: version,
548549
BootstrapSelfManagedAddons: aws.Bool(false),
550+
UpgradePolicy: &eks.UpgradePolicyRequest{
551+
SupportType: aws.String(eks.SupportTypeStandard),
552+
},
549553
}).Return(&eks.CreateClusterOutput{}, nil)
550554
}
551555
s := NewService(scope)
@@ -675,6 +679,107 @@ func TestReconcileEKSEncryptionConfig(t *testing.T) {
675679
}
676680
}
677681

682+
func TestReconcileEKSUpgradePolicy(t *testing.T) {
683+
clusterName := "default.cluster"
684+
tests := []struct {
685+
name string
686+
oldUpgradePolicy *string
687+
newUpgradePolicy *string
688+
expect func(m *mock_eksiface.MockEKSAPIMockRecorder)
689+
expectError bool
690+
}{
691+
{
692+
name: "no update necessary - no upgrade policy specified",
693+
oldUpgradePolicy: aws.String(eks.SupportTypeStandard),
694+
expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {},
695+
expectError: false,
696+
},
697+
{
698+
name: "no update necessary - cannot get cluster upgrade policy",
699+
newUpgradePolicy: aws.String(eks.SupportTypeStandard),
700+
expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {},
701+
expectError: false,
702+
},
703+
{
704+
name: "no update necessary - upgrade policy unchanged",
705+
oldUpgradePolicy: aws.String(eks.SupportTypeStandard),
706+
newUpgradePolicy: aws.String(eks.SupportTypeStandard),
707+
expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {},
708+
expectError: false,
709+
},
710+
{
711+
name: "needs update",
712+
oldUpgradePolicy: aws.String(eks.SupportTypeStandard),
713+
newUpgradePolicy: aws.String(eks.SupportTypeExtended),
714+
expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
715+
m.WaitUntilClusterUpdating(
716+
gomock.AssignableToTypeOf(&eks.DescribeClusterInput{}), gomock.Any(),
717+
).Return(nil)
718+
m.UpdateClusterConfig(gomock.AssignableToTypeOf(&eks.UpdateClusterConfigInput{})).Return(&eks.UpdateClusterConfigOutput{}, nil)
719+
},
720+
expectError: false,
721+
},
722+
{
723+
name: "api error",
724+
oldUpgradePolicy: aws.String(eks.SupportTypeExtended),
725+
newUpgradePolicy: aws.String(eks.SupportTypeStandard),
726+
expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
727+
m.
728+
UpdateClusterConfig(gomock.AssignableToTypeOf(&eks.UpdateClusterConfigInput{})).
729+
Return(&eks.UpdateClusterConfigOutput{}, errors.New(""))
730+
},
731+
expectError: true,
732+
},
733+
}
734+
735+
for _, tc := range tests {
736+
t.Run(tc.name, func(t *testing.T) {
737+
g := NewWithT(t)
738+
739+
mockControl := gomock.NewController(t)
740+
defer mockControl.Finish()
741+
742+
eksMock := mock_eksiface.NewMockEKSAPI(mockControl)
743+
744+
scheme := runtime.NewScheme()
745+
_ = infrav1.AddToScheme(scheme)
746+
_ = ekscontrolplanev1.AddToScheme(scheme)
747+
client := fake.NewClientBuilder().WithScheme(scheme).Build()
748+
scope, err := scope.NewManagedControlPlaneScope(scope.ManagedControlPlaneScopeParams{
749+
Client: client,
750+
Cluster: &clusterv1.Cluster{
751+
ObjectMeta: metav1.ObjectMeta{
752+
Namespace: "ns",
753+
Name: clusterName,
754+
},
755+
},
756+
ControlPlane: &ekscontrolplanev1.AWSManagedControlPlane{
757+
Spec: ekscontrolplanev1.AWSManagedControlPlaneSpec{
758+
Version: aws.String("1.16"),
759+
UpgradePolicy: tc.newUpgradePolicy,
760+
},
761+
},
762+
})
763+
g.Expect(err).To(BeNil())
764+
765+
tc.expect(eksMock.EXPECT())
766+
s := NewService(scope)
767+
s.EKSClient = eksMock
768+
769+
err = s.reconcileClusterUpgradePolicy(&eks.Cluster{
770+
UpgradePolicy: &eks.UpgradePolicyResponse{
771+
SupportType: tc.oldUpgradePolicy,
772+
},
773+
})
774+
if tc.expectError {
775+
g.Expect(err).To(HaveOccurred())
776+
return
777+
}
778+
g.Expect(err).To(BeNil())
779+
})
780+
}
781+
}
782+
678783
func TestCreateIPv6Cluster(t *testing.T) {
679784
g := NewWithT(t)
680785

0 commit comments

Comments
 (0)