Skip to content

Commit d731781

Browse files
authored
Merge pull request #4293 from Nordix/kcp-rollout-strategy-backport
✨ Backport KCP Rollout Strategy to v1alpha3 api
2 parents 258684a + be7ea2f commit d731781

File tree

7 files changed

+208
-9
lines changed

7 files changed

+208
-9
lines changed

controlplane/kubeadm/api/v1alpha3/kubeadm_control_plane_types.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@ package v1alpha3
1919
import (
2020
corev1 "k8s.io/api/core/v1"
2121
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"k8s.io/apimachinery/pkg/util/intstr"
2223
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
2324

2425
cabpkv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha3"
2526
"sigs.k8s.io/cluster-api/errors"
2627
)
2728

29+
type RolloutStrategyType string
30+
31+
const (
32+
// Replace the old control planes by new one using rolling update
33+
// i.e. gradually scale up or down the old control planes and scale up or down the new one.
34+
RollingUpdateStrategyType RolloutStrategyType = "RollingUpdate"
35+
)
36+
2837
const (
2938
KubeadmControlPlaneFinalizer = "kubeadm.controlplane.cluster.x-k8s.io"
3039

@@ -72,6 +81,38 @@ type KubeadmControlPlaneSpec struct {
7281
// NOTE: NodeDrainTimeout is different from `kubectl drain --timeout`
7382
// +optional
7483
NodeDrainTimeout *metav1.Duration `json:"nodeDrainTimeout,omitempty"`
84+
85+
// The RolloutStrategy to use to replace control plane machines with
86+
// new ones.
87+
// +optional
88+
RolloutStrategy *RolloutStrategy `json:"rolloutStrategy,omitempty"`
89+
}
90+
91+
// RolloutStrategy describes how to replace existing machines
92+
// with new ones.
93+
type RolloutStrategy struct {
94+
// Type of rollout. Currently the only supported strategy is
95+
// "RollingUpdate".
96+
// Default is RollingUpdate.
97+
// +optional
98+
Type RolloutStrategyType `json:"type,omitempty"`
99+
100+
// Rolling update config params. Present only if
101+
// RolloutStrategyType = RollingUpdate.
102+
// +optional
103+
RollingUpdate *RollingUpdate `json:"rollingUpdate,omitempty"`
104+
}
105+
106+
// RollingUpdate is used to control the desired behavior of rolling update.
107+
type RollingUpdate struct {
108+
// The maximum number of control planes that can be scheduled above or under the
109+
// desired number of control planes.
110+
// Value can be an absolute number 1 or 0.
111+
// Defaults to 1.
112+
// Example: when this is set to 1, the control plane can be scaled
113+
// up immediately when the rolling update starts.
114+
// +optional
115+
MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty"`
75116
}
76117

77118
// KubeadmControlPlaneStatus defines the observed state of KubeadmControlPlane.

controlplane/kubeadm/api/v1alpha3/kubeadm_control_plane_webhook.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/pkg/errors"
2929
apierrors "k8s.io/apimachinery/pkg/api/errors"
3030
"k8s.io/apimachinery/pkg/runtime"
31+
"k8s.io/apimachinery/pkg/util/intstr"
3132
"k8s.io/apimachinery/pkg/util/validation/field"
3233
kubeadmv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta1"
3334
"sigs.k8s.io/cluster-api/util"
@@ -64,6 +65,25 @@ func (in *KubeadmControlPlane) Default() {
6465
if !strings.HasPrefix(in.Spec.Version, "v") {
6566
in.Spec.Version = "v" + in.Spec.Version
6667
}
68+
69+
ios1 := intstr.FromInt(1)
70+
71+
if in.Spec.RolloutStrategy == nil {
72+
in.Spec.RolloutStrategy = &RolloutStrategy{}
73+
}
74+
75+
// Enforce RollingUpdate strategy and default MaxSurge if not set.
76+
if in.Spec.RolloutStrategy != nil {
77+
if len(in.Spec.RolloutStrategy.Type) == 0 {
78+
in.Spec.RolloutStrategy.Type = RollingUpdateStrategyType
79+
}
80+
if in.Spec.RolloutStrategy.Type == RollingUpdateStrategyType {
81+
if in.Spec.RolloutStrategy.RollingUpdate == nil {
82+
in.Spec.RolloutStrategy.RollingUpdate = &RollingUpdate{}
83+
}
84+
in.Spec.RolloutStrategy.RollingUpdate.MaxSurge = intstr.ValueOrDefault(in.Spec.RolloutStrategy.RollingUpdate.MaxSurge, ios1)
85+
}
86+
}
6787
}
6888

6989
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
@@ -119,6 +139,7 @@ func (in *KubeadmControlPlane) ValidateUpdate(old runtime.Object) error {
119139
{spec, "version"},
120140
{spec, "upgradeAfter"},
121141
{spec, "nodeDrainTimeout"},
142+
{spec, "rolloutStrategy"},
122143
}
123144

124145
allErrs := in.validateCommon()
@@ -272,6 +293,41 @@ func (in *KubeadmControlPlane) validateCommon() (allErrs field.ErrorList) {
272293
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "version"), in.Spec.Version, "must be a valid semantic version"))
273294
}
274295

296+
if in.Spec.RolloutStrategy != nil {
297+
if in.Spec.RolloutStrategy.Type != RollingUpdateStrategyType {
298+
allErrs = append(
299+
allErrs,
300+
field.Required(
301+
field.NewPath("spec", "rolloutStrategy", "type"),
302+
"only RollingUpdateStrategyType is supported",
303+
),
304+
)
305+
}
306+
307+
ios1 := intstr.FromInt(1)
308+
ios0 := intstr.FromInt(0)
309+
310+
if *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge == ios0 && *in.Spec.Replicas < int32(3) {
311+
allErrs = append(
312+
allErrs,
313+
field.Required(
314+
field.NewPath("spec", "rolloutStrategy", "rollingUpdate"),
315+
"when KubeadmControlPlane is configured to scale-in, replica count needs to be at least 3",
316+
),
317+
)
318+
}
319+
320+
if *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge != ios1 && *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge != ios0 {
321+
allErrs = append(
322+
allErrs,
323+
field.Required(
324+
field.NewPath("spec", "rolloutStrategy", "rollingUpdate", "maxSurge"),
325+
"value must be 1 or 0",
326+
),
327+
)
328+
}
329+
}
330+
275331
allErrs = append(allErrs, in.validateCoreDNSImage()...)
276332

277333
return allErrs

controlplane/kubeadm/api/v1alpha3/kubeadm_control_plane_webhook_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
corev1 "k8s.io/api/core/v1"
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/util/intstr"
2728
"k8s.io/utils/pointer"
2829
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha3"
2930
kubeadmv1beta1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta1"
@@ -39,12 +40,15 @@ func TestKubeadmControlPlaneDefault(t *testing.T) {
3940
Spec: KubeadmControlPlaneSpec{
4041
InfrastructureTemplate: corev1.ObjectReference{},
4142
Version: "1.18.3",
43+
RolloutStrategy: &RolloutStrategy{},
4244
},
4345
}
4446
kcp.Default()
4547

4648
g.Expect(kcp.Spec.InfrastructureTemplate.Namespace).To(Equal(kcp.Namespace))
4749
g.Expect(kcp.Spec.Version).To(Equal("v1.18.3"))
50+
g.Expect(kcp.Spec.RolloutStrategy.Type).To(Equal(RollingUpdateStrategyType))
51+
g.Expect(kcp.Spec.RolloutStrategy.RollingUpdate.MaxSurge.IntVal).To(Equal(int32(1)))
4852
}
4953

5054
func TestKubeadmControlPlaneValidateCreate(t *testing.T) {
@@ -60,11 +64,22 @@ func TestKubeadmControlPlaneValidateCreate(t *testing.T) {
6064
},
6165
Replicas: pointer.Int32Ptr(1),
6266
Version: "v1.19.0",
67+
RolloutStrategy: &RolloutStrategy{
68+
Type: RollingUpdateStrategyType,
69+
RollingUpdate: &RollingUpdate{
70+
MaxSurge: &intstr.IntOrString{
71+
IntVal: 1,
72+
},
73+
},
74+
},
6375
},
6476
}
6577
invalidNamespace := valid.DeepCopy()
6678
invalidNamespace.Spec.InfrastructureTemplate.Namespace = "bar"
6779

80+
invalidMaxSurge := valid.DeepCopy()
81+
invalidMaxSurge.Spec.RolloutStrategy.RollingUpdate.MaxSurge.IntVal = int32(3)
82+
6883
missingReplicas := valid.DeepCopy()
6984
missingReplicas.Spec.Replicas = nil
7085

@@ -142,6 +157,11 @@ func TestKubeadmControlPlaneValidateCreate(t *testing.T) {
142157
expectErr: true,
143158
kcp: invalidVersion1,
144159
},
160+
{
161+
name: "should return error when maxSurge is not 1",
162+
expectErr: true,
163+
kcp: invalidMaxSurge,
164+
},
145165
}
146166

147167
for _, tt := range tests {

controlplane/kubeadm/api/v1alpha3/zz_generated.deepcopy.go

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,30 @@ spec:
10451045
This is a pointer to distinguish between explicit zero and not specified.
10461046
format: int32
10471047
type: integer
1048+
rolloutStrategy:
1049+
description: The RolloutStrategy to use to replace control plane machines
1050+
with new ones.
1051+
properties:
1052+
rollingUpdate:
1053+
description: Rolling update config params. Present only if RolloutStrategyType
1054+
= RollingUpdate.
1055+
properties:
1056+
maxSurge:
1057+
anyOf:
1058+
- type: integer
1059+
- type: string
1060+
description: 'The maximum number of control planes that can
1061+
be scheduled above or under the desired number of control
1062+
planes. Value can be an absolute number 1 or 0. Defaults
1063+
to 1. Example: when this is set to 1, the control plane
1064+
can be scaled up immediately when the rolling update starts.'
1065+
x-kubernetes-int-or-string: true
1066+
type: object
1067+
type:
1068+
description: Type of rollout. Currently the only supported strategy
1069+
is "RollingUpdate". Default is RollingUpdate.
1070+
type: string
1071+
type: object
10481072
upgradeAfter:
10491073
description: UpgradeAfter is a field to indicate an upgrade should
10501074
be performed after the specified time even if no changes have been

controlplane/kubeadm/controllers/controller_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3333
"k8s.io/apimachinery/pkg/runtime"
3434
"k8s.io/apimachinery/pkg/types"
35+
"k8s.io/apimachinery/pkg/util/intstr"
3536
"k8s.io/client-go/kubernetes/scheme"
3637
"k8s.io/client-go/tools/record"
3738
"k8s.io/klog/klogr"
@@ -1345,6 +1346,14 @@ func createClusterWithControlPlane() (*clusterv1.Cluster, *controlplanev1.Kubead
13451346
},
13461347
Replicas: pointer.Int32Ptr(int32(3)),
13471348
Version: "v1.16.6",
1349+
RolloutStrategy: &controlplanev1.RolloutStrategy{
1350+
Type: "RollingUpdate",
1351+
RollingUpdate: &controlplanev1.RollingUpdate{
1352+
MaxSurge: &intstr.IntOrString{
1353+
IntVal: 1,
1354+
},
1355+
},
1356+
},
13481357
},
13491358
}
13501359

controlplane/kubeadm/controllers/upgrade.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,17 @@ func (r *KubeadmControlPlaneReconciler) upgradeControlPlane(
100100
return ctrl.Result{}, errors.Wrap(err, "failed to upgrade kubelet config map")
101101
}
102102

103-
status, err := workloadCluster.ClusterStatus(ctx)
104-
if err != nil {
105-
return ctrl.Result{}, err
106-
}
107-
108-
if status.Nodes <= *kcp.Spec.Replicas {
109-
// scaleUp ensures that we don't continue scaling up while waiting for Machines to have NodeRefs
110-
return r.scaleUpControlPlane(ctx, cluster, kcp, controlPlane)
103+
switch kcp.Spec.RolloutStrategy.Type {
104+
case controlplanev1.RollingUpdateStrategyType:
105+
// RolloutStrategy is currently defaulted and validated to be RollingUpdate
106+
// We can ignore MaxUnavailable because we are enforcing health checks before we get here.
107+
maxNodes := *kcp.Spec.Replicas + int32(kcp.Spec.RolloutStrategy.RollingUpdate.MaxSurge.IntValue())
108+
if int32(controlPlane.Machines.Len()) < maxNodes {
109+
// scaleUp ensures that we don't continue scaling up while waiting for Machines to have NodeRefs
110+
return r.scaleUpControlPlane(ctx, cluster, kcp, controlPlane)
111+
}
112+
return r.scaleDownControlPlane(ctx, cluster, kcp, controlPlane, machinesRequireUpgrade)
113+
default:
114+
return ctrl.Result{}, errors.New("rolloutStrategy type is not set to rollingupdatestrategytype, unable to determine the strategy for rolling out machines")
111115
}
112-
return r.scaleDownControlPlane(ctx, cluster, kcp, controlPlane, machinesRequireUpgrade)
113116
}

0 commit comments

Comments
 (0)