Skip to content

Commit

Permalink
Add rollout strategy support for KCP
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-est committed Jan 25, 2021
1 parent 61dc332 commit 1c5dfbd
Show file tree
Hide file tree
Showing 9 changed files with 593 additions and 16 deletions.
37 changes: 35 additions & 2 deletions controlplane/kubeadm/api/v1alpha3/conversion.go
Expand Up @@ -17,18 +17,47 @@ limitations under the License.
package v1alpha3

import (
apiconversion "k8s.io/apimachinery/pkg/conversion"
"sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha4"
utilconversion "sigs.k8s.io/cluster-api/util/conversion"
"sigs.k8s.io/controller-runtime/pkg/conversion"
)

func (src *KubeadmControlPlane) ConvertTo(destRaw conversion.Hub) error {
dest := destRaw.(*v1alpha4.KubeadmControlPlane)
return Convert_v1alpha3_KubeadmControlPlane_To_v1alpha4_KubeadmControlPlane(src, dest, nil)

if err := Convert_v1alpha3_KubeadmControlPlane_To_v1alpha4_KubeadmControlPlane(src, dest, nil); err != nil {
return err
}

// Manually restore data.
restored := &v1alpha4.KubeadmControlPlane{}
if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
return err
}

if restored.Spec.RolloutStrategy != nil {
dest.Spec.RolloutStrategy.Type = &v1alpha4.RollingUpdateStrategyType
if restored.Spec.RolloutStrategy.RollingUpdate != nil {
dest.Spec.RolloutStrategy.RollingUpdate = restored.Spec.RolloutStrategy.RollingUpdate
}
}
return nil
}

func (dest *KubeadmControlPlane) ConvertFrom(srcRaw conversion.Hub) error {
src := srcRaw.(*v1alpha4.KubeadmControlPlane)
return Convert_v1alpha4_KubeadmControlPlane_To_v1alpha3_KubeadmControlPlane(src, dest, nil)

if err := Convert_v1alpha4_KubeadmControlPlane_To_v1alpha3_KubeadmControlPlane(src, dest, nil); err != nil {
return err
}

// Preserve Hub data on down-conversion except for metadata
if err := utilconversion.MarshalData(src, dest); err != nil {
return err
}

return nil
}

func (src *KubeadmControlPlaneList) ConvertTo(destRaw conversion.Hub) error {
Expand All @@ -40,3 +69,7 @@ func (dest *KubeadmControlPlaneList) ConvertFrom(srcRaw conversion.Hub) error {
src := srcRaw.(*v1alpha4.KubeadmControlPlaneList)
return Convert_v1alpha4_KubeadmControlPlaneList_To_v1alpha3_KubeadmControlPlaneList(src, dest, nil)
}

func Convert_v1alpha4_KubeadmControlPlaneSpec_To_v1alpha3_KubeadmControlPlaneSpec(in *v1alpha4.KubeadmControlPlaneSpec, out *KubeadmControlPlaneSpec, s apiconversion.Scope) error {
return autoConvert_v1alpha4_KubeadmControlPlaneSpec_To_v1alpha3_KubeadmControlPlaneSpec(in, out, s)
}
16 changes: 6 additions & 10 deletions controlplane/kubeadm/api/v1alpha3/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 54 additions & 0 deletions controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_types.go
Expand Up @@ -19,12 +19,21 @@ package v1alpha4
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4"

cabpkv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha4"
"sigs.k8s.io/cluster-api/errors"
)

type RolloutStrategyType string

const (
// Replace the old control planes by new one using rolling update
// i.e. gradually scale up or down the old control planes and scale up or down the new one.
RollingUpdateStrategyType RolloutStrategyType = "RollingUpdate"
)

const (
KubeadmControlPlaneFinalizer = "kubeadm.controlplane.cluster.x-k8s.io"

Expand Down Expand Up @@ -69,6 +78,50 @@ type KubeadmControlPlaneSpec struct {
// NOTE: NodeDrainTimeout is different from `kubectl drain --timeout`
// +optional
NodeDrainTimeout *metav1.Duration `json:"nodeDrainTimeout,omitempty"`

// The RolloutStrategy to use to replace control plane machines with
// new ones.
// +optional
RolloutStrategy *RolloutStrategy `json:"rolloutStrategy,omitempty"`
}

// RolloutStrategy describes how to replace existing machines
// with new ones.
type RolloutStrategy struct {
// Type of rollout. Currently the only supported strategy is
// "RollingUpdate".
// Default is RollingUpdate.
// +optional
Type RolloutStrategyType `json:"type,omitempty"`

// Rolling update config params. Present only if
// RolloutStrategyType = RollingUpdate.
// +optional
RollingUpdate *RollingUpdate `json:"rollingUpdate,omitempty"`
}

// RollingUpdate is used to control the desired behavior of rolling update.
type RollingUpdate struct {
// The maximum number of control planes that can be unavailable during the rollout.
// Value can be an absolute number 0 or 1.
// This needs to be 1 if MaxSurge is 0.
// Defaults to 0.
// Example: when this is set to 1 and MaxSurge is 0, the control planes can be scaled
// down one-by-one when the rolling update starts.
// Control plane scale down is disabled when desired number of control planes is 1.
// Scale down is possible only if desired number of control planes is 3 or more.
// +optional
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`

// The maximum number of control planes that can be scheduled above the
// desired number of control planes.
// Value can be an absolute number 1 or 0.
// This needs to be 1 if MaxUnavailable is 0.
// Defaults to 1.
// Example: when this is set to 1 and MaxUnavailable is 0, the control plane can be scaled
// up immediately when the rolling update starts.
// +optional
MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty"`
}

// KubeadmControlPlaneStatus defines the observed state of KubeadmControlPlane.
Expand Down Expand Up @@ -145,6 +198,7 @@ type KubeadmControlPlaneStatus struct {
// +kubebuilder:printcolumn:name="Ready",type=integer,JSONPath=".status.readyReplicas",description="Total number of fully running and ready control plane machines"
// +kubebuilder:printcolumn:name="Updated",type=integer,JSONPath=".status.updatedReplicas",description="Total number of non-terminated machines targeted by this control plane that have the desired template spec"
// +kubebuilder:printcolumn:name="Unavailable",type=integer,JSONPath=".status.unavailableReplicas",description="Total number of unavailable machines targeted by this control plane"
// +kubebuilder:printcolumn:name="Rollout Strategy",type=string,priority=1,JSONPath=".spec.rolloutStrategy.type",description="Type of RolloutStrategy"

// KubeadmControlPlane is the Schema for the KubeadmControlPlane API.
type KubeadmControlPlane struct {
Expand Down
79 changes: 79 additions & 0 deletions controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_webhook.go
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/validation/field"
kubeadmv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta1"
"sigs.k8s.io/cluster-api/util"
Expand Down Expand Up @@ -64,6 +65,26 @@ func (in *KubeadmControlPlane) Default() {
if !strings.HasPrefix(in.Spec.Version, "v") {
in.Spec.Version = "v" + in.Spec.Version
}

// Enforce RollingUpdate strategy and default MaxSurge and MaxUnavailable if not set.
if in.Spec.RolloutStrategy != nil {
if in.Spec.RolloutStrategy.Type != RollingUpdateStrategyType {
in.Spec.RolloutStrategy.Type = RollingUpdateStrategyType
}

if in.Spec.RolloutStrategy.RollingUpdate == nil {
in.Spec.RolloutStrategy.RollingUpdate = &RollingUpdate{}
}

if in.Spec.RolloutStrategy.RollingUpdate.MaxSurge == nil {
ios1 := intstr.FromInt(1)
in.Spec.RolloutStrategy.RollingUpdate.MaxSurge = &ios1
}
if in.Spec.RolloutStrategy.RollingUpdate.MaxUnavailable == nil {
ios0 := intstr.FromInt(0)
in.Spec.RolloutStrategy.RollingUpdate.MaxUnavailable = &ios0
}
}
}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
Expand Down Expand Up @@ -119,6 +140,7 @@ func (in *KubeadmControlPlane) ValidateUpdate(old runtime.Object) error {
{spec, "version"},
{spec, "upgradeAfter"},
{spec, "nodeDrainTimeout"},
{spec, "rolloutStrategy"},
}

allErrs := in.validateCommon()
Expand Down Expand Up @@ -272,6 +294,63 @@ func (in *KubeadmControlPlane) validateCommon() (allErrs field.ErrorList) {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "version"), in.Spec.Version, "must be a valid semantic version"))
}

if in.Spec.RolloutStrategy != nil {

ios1 := intstr.FromInt(1)
ios0 := intstr.FromInt(0)

if *in.Spec.RolloutStrategy.RollingUpdate.MaxUnavailable == ios1 && *in.Spec.Replicas < int32(3) {
allErrs = append(
allErrs,
field.Required(
field.NewPath("spec", "rolloutstrategy", "rollingupdate"),
"when kcp is configured to scale-in, minimum replica count needs to be 3",
),
)
}

if *in.Spec.RolloutStrategy.RollingUpdate.MaxUnavailable != ios1 && *in.Spec.RolloutStrategy.RollingUpdate.MaxUnavailable != ios0 {
allErrs = append(
allErrs,
field.Required(
field.NewPath("spec", "rolloutstrategy", "rollingupdate", "maxunavailable"),
"maxunavailable value must be 1 or 0",
),
)
}

if *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge != ios1 && *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge != ios0 {
allErrs = append(
allErrs,
field.Required(
field.NewPath("spec", "rolloutstrategy", "rollingupdate", "maxsurge"),
"maxsurge value must be 1 or 0",
),
)
}

if *in.Spec.RolloutStrategy.RollingUpdate.MaxUnavailable == ios0 && *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge != ios1 {
allErrs = append(
allErrs,
field.Required(
field.NewPath("spec", "rolloutstrategy", "rollingupdate", "maxunavailable"),
"maxunavailable value is set to 0, maxsurge value needs to be 1",
),
)
}

if *in.Spec.RolloutStrategy.RollingUpdate.MaxUnavailable == ios1 && *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge != ios0 {
allErrs = append(
allErrs,
field.Required(
field.NewPath("spec", "rolloutstrategy", "rollingupdate", "maxunavailable"),
"maxunavailable value is set to 1, maxsurge needs to be 0",
),
)
}

}

allErrs = append(allErrs, in.validateCoreDNSImage()...)

return allErrs
Expand Down
Expand Up @@ -24,6 +24,7 @@ import (

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha4"
kubeadmv1beta1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta1"
Expand All @@ -37,14 +38,22 @@ func TestKubeadmControlPlaneDefault(t *testing.T) {
Namespace: "foo",
},
Spec: KubeadmControlPlaneSpec{
InfrastructureTemplate: corev1.ObjectReference{},
Version: "1.18.3",
InfrastructureTemplate: corev1.ObjectReference{},
RolloutStrategy: &RolloutStrategy{
Type: "Sometype",
RollingUpdate: &RollingUpdate{},
},
},
}
kcp.Default()

g.Expect(kcp.Spec.InfrastructureTemplate.Namespace).To(Equal(kcp.Namespace))
g.Expect(kcp.Spec.Version).To(Equal("v1.18.3"))
g.Expect(kcp.Spec.RolloutStrategy.Type).To(Equal(RollingUpdateStrategyType))
g.Expect(kcp.Spec.RolloutStrategy.RollingUpdate.MaxUnavailable.IntVal).To(Equal(int32(0)))
g.Expect(kcp.Spec.RolloutStrategy.RollingUpdate.MaxSurge.IntVal).To(Equal(int32(1)))

}

func TestKubeadmControlPlaneValidateCreate(t *testing.T) {
Expand All @@ -60,8 +69,26 @@ func TestKubeadmControlPlaneValidateCreate(t *testing.T) {
},
Replicas: pointer.Int32Ptr(1),
Version: "v1.19.0",
RolloutStrategy: &RolloutStrategy{
Type: RollingUpdateStrategyType,
RollingUpdate: &RollingUpdate{
MaxUnavailable: &intstr.IntOrString{
IntVal: 0,
},
MaxSurge: &intstr.IntOrString{
IntVal: 1,
},
},
},
},
}

invalidMaxUnavailable := valid.DeepCopy()
invalidMaxUnavailable.Spec.RolloutStrategy.RollingUpdate.MaxUnavailable.IntVal = int32(5)

invalidMaxSurge := valid.DeepCopy()
invalidMaxSurge.Spec.RolloutStrategy.RollingUpdate.MaxSurge.IntVal = int32(3)

invalidNamespace := valid.DeepCopy()
invalidNamespace.Spec.InfrastructureTemplate.Namespace = "bar"

Expand Down Expand Up @@ -142,6 +169,16 @@ func TestKubeadmControlPlaneValidateCreate(t *testing.T) {
expectErr: true,
kcp: invalidVersion1,
},
{
name: "should return error when maxunavailable is not 0",
expectErr: true,
kcp: invalidMaxUnavailable,
},
{
name: "should return error when maxsurge is not 1",
expectErr: true,
kcp: invalidMaxSurge,
},
}

for _, tt := range tests {
Expand Down

0 comments on commit 1c5dfbd

Please sign in to comment.