Skip to content

Commit

Permalink
MachineDeployment rolloutAfter support
Browse files Browse the repository at this point in the history
  • Loading branch information
ykakarap committed Mar 2, 2023
1 parent 9fe7ff5 commit 9a67a0c
Show file tree
Hide file tree
Showing 17 changed files with 198 additions and 56 deletions.
7 changes: 7 additions & 0 deletions api/v1alpha3/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ func (src *MachineDeployment) ConvertTo(dstRaw conversion.Hub) error {

dst.Spec.Template.Spec.NodeDeletionTimeout = restored.Spec.Template.Spec.NodeDeletionTimeout
dst.Spec.Template.Spec.NodeVolumeDetachTimeout = restored.Spec.Template.Spec.NodeVolumeDetachTimeout
if restored.Spec.RolloutAfter != nil {
dst.Spec.RolloutAfter = restored.Spec.RolloutAfter
}
dst.Status.Conditions = restored.Status.Conditions
return nil
}
Expand Down Expand Up @@ -316,6 +319,10 @@ func Convert_v1beta1_MachineSpec_To_v1alpha3_MachineSpec(in *clusterv1.MachineSp
return autoConvert_v1beta1_MachineSpec_To_v1alpha3_MachineSpec(in, out, s)
}

func Convert_v1beta1_MachineDeploymentSpec_To_v1alpha3_MachineDeploymentSpec(in *clusterv1.MachineDeploymentSpec, out *MachineDeploymentSpec, s apiconversion.Scope) error {
return autoConvert_v1beta1_MachineDeploymentSpec_To_v1alpha3_MachineDeploymentSpec(in, out, s)
}

func Convert_v1beta1_MachineDeploymentStatus_To_v1alpha3_MachineDeploymentStatus(in *clusterv1.MachineDeploymentStatus, out *MachineDeploymentStatus, s apiconversion.Scope) error {
// Status.Conditions was introduced in v1alpha4, thus requiring a custom conversion function; the values is going to be preserved in an annotation thus allowing roundtrip without loosing informations
return autoConvert_v1beta1_MachineDeploymentStatus_To_v1alpha3_MachineDeploymentStatus(in, out, s)
Expand Down
16 changes: 6 additions & 10 deletions api/v1alpha3/zz_generated.conversion.go

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

7 changes: 7 additions & 0 deletions api/v1alpha4/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,9 @@ func (src *MachineDeployment) ConvertTo(dstRaw conversion.Hub) error {

dst.Spec.Template.Spec.NodeDeletionTimeout = restored.Spec.Template.Spec.NodeDeletionTimeout
dst.Spec.Template.Spec.NodeVolumeDetachTimeout = restored.Spec.Template.Spec.NodeVolumeDetachTimeout
if restored.Spec.RolloutAfter != nil {
dst.Spec.RolloutAfter = restored.Spec.RolloutAfter
}
return nil
}

Expand Down Expand Up @@ -335,6 +338,10 @@ func Convert_v1beta1_MachineSpec_To_v1alpha4_MachineSpec(in *clusterv1.MachineSp
return autoConvert_v1beta1_MachineSpec_To_v1alpha4_MachineSpec(in, out, s)
}

func Convert_v1beta1_MachineDeploymentSpec_To_v1alpha4_MachineDeploymentSpec(in *clusterv1.MachineDeploymentSpec, out *MachineDeploymentSpec, s apiconversion.Scope) error {
return autoConvert_v1beta1_MachineDeploymentSpec_To_v1alpha4_MachineDeploymentSpec(in, out, s)
}

func Convert_v1beta1_Topology_To_v1alpha4_Topology(in *clusterv1.Topology, out *Topology, s apiconversion.Scope) error {
// spec.topology.variables has been added with v1beta1.
return autoConvert_v1beta1_Topology_To_v1alpha4_Topology(in, out, s)
Expand Down
16 changes: 6 additions & 10 deletions api/v1alpha4/zz_generated.conversion.go

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

6 changes: 6 additions & 0 deletions api/v1beta1/machinedeployment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ type MachineDeploymentSpec struct {
// +optional
Replicas *int32 `json:"replicas,omitempty"`

// RolloutAfter is a field to indicate a rollout should be performed
// after the specified time even if no changes have been made to the
// MachineDeployment.
// +optional
RolloutAfter *metav1.Time `json:"rolloutAfter,omitempty"`

// Label selector for machines. Existing MachineSets whose machines are
// selected by this will be the ones affected by this deployment.
// It must match the machine template's labels.
Expand Down
4 changes: 4 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

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

8 changes: 7 additions & 1 deletion api/v1beta1/zz_generated.openapi.go

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

4 changes: 2 additions & 2 deletions cmd/clusterctl/client/alpha/kubeadmcontrolplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ func getKubeadmControlPlane(proxy cluster.Proxy, name, namespace string) (*contr
return kcpObj, nil
}

// setRolloutAfter sets KubeadmControlPlane.spec.rolloutAfter.
func setRolloutAfter(proxy cluster.Proxy, name, namespace string) error {
// setRolloutAfterOnKCP sets KubeadmControlPlane.spec.rolloutAfter.
func setRolloutAfterOnKCP(proxy cluster.Proxy, name, namespace string) error {
patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"rolloutAfter":"%v"}}`, time.Now().Format(time.RFC3339))))
return patchKubeadmControlPlane(proxy, name, namespace, patch)
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/clusterctl/client/alpha/machinedeployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ func getMachineDeployment(proxy cluster.Proxy, name, namespace string) (*cluster
return mdObj, nil
}

// setRestartedAtAnnotation sets the restartedAt annotation in the MachineDeployment's spec.template.objectmeta.
func setRestartedAtAnnotation(proxy cluster.Proxy, name, namespace string) error {
patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"template":{"metadata":{"annotations":{"cluster.x-k8s.io/restartedAt":"%v"}}}}}`, time.Now().Format(time.RFC3339))))
// setRolloutAfterOnMachineDeployment sets MachineDeployment.spec.rolloutAfter.
func setRolloutAfterOnMachineDeployment(proxy cluster.Proxy, name, namespace string) error {
patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"rolloutAfter":"%v"}}`, time.Now().Format(time.RFC3339))))
return patchMachineDeployment(proxy, name, namespace, patch)
}

Expand Down
7 changes: 5 additions & 2 deletions cmd/clusterctl/client/alpha/rollout_restarter.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ func (r *rollout) ObjectRestarter(proxy cluster.Proxy, ref corev1.ObjectReferenc
if deployment.Spec.Paused {
return errors.Errorf("can't restart paused MachineDeployment (run rollout resume first): %v/%v", ref.Kind, ref.Name)
}
if err := setRestartedAtAnnotation(proxy, ref.Name, ref.Namespace); err != nil {
if deployment.Spec.RolloutAfter != nil && deployment.Spec.RolloutAfter.After(time.Now()) {
return errors.Errorf("can't update MachineDeployment (remove 'spec.rolloutAfter' first): %v/%v", ref.Kind, ref.Name)
}
if err := setRolloutAfterOnMachineDeployment(proxy, ref.Name, ref.Namespace); err != nil {
return err
}
case KubeadmControlPlane:
Expand All @@ -51,7 +54,7 @@ func (r *rollout) ObjectRestarter(proxy cluster.Proxy, ref corev1.ObjectReferenc
if kcp.Spec.RolloutAfter != nil && kcp.Spec.RolloutAfter.After(time.Now()) {
return errors.Errorf("can't update KubeadmControlPlane (remove 'spec.rolloutAfter' first): %v/%v", ref.Kind, ref.Name)
}
if err := setRolloutAfter(proxy, ref.Name, ref.Namespace); err != nil {
if err := setRolloutAfterOnKCP(proxy, ref.Name, ref.Namespace); err != nil {
return err
}
default:
Expand Down
37 changes: 32 additions & 5 deletions cmd/clusterctl/client/alpha/rollout_restarter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func Test_ObjectRestarter(t *testing.T) {
wantRollout bool
}{
{
name: "machinedeployment should have restart annotation",
name: "machinedeployment should have rolloutAfter",
fields: fields{
objs: []client.Object{
&clusterv1.MachineDeployment{
Expand All @@ -67,7 +67,7 @@ func Test_ObjectRestarter(t *testing.T) {
wantRollout: true,
},
{
name: "paused machinedeployment should not have restart annotation",
name: "paused machinedeployment should not have rolloutAfter",
fields: fields{
objs: []client.Object{
&clusterv1.MachineDeployment{
Expand All @@ -93,6 +93,33 @@ func Test_ObjectRestarter(t *testing.T) {
wantErr: true,
wantRollout: false,
},
{
name: "machinedeployment with spec.rolloutAfter should not be updatable",
fields: fields{
objs: []client.Object{
&clusterv1.MachineDeployment{
TypeMeta: metav1.TypeMeta{
Kind: "MachineDeployment",
APIVersion: "cluster.x-k8s.io/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "md-1",
},
Spec: clusterv1.MachineDeploymentSpec{
RolloutAfter: &metav1.Time{Time: time.Now().Local().Add(time.Hour)},
},
},
},
ref: corev1.ObjectReference{
Kind: MachineDeployment,
Name: "md-1",
Namespace: "default",
},
},
wantErr: true,
wantRollout: false,
},
{
name: "kubeadmcontrolplane should have rolloutAfter",
fields: fields{
Expand Down Expand Up @@ -193,9 +220,9 @@ func Test_ObjectRestarter(t *testing.T) {
err = cl.Get(context.TODO(), key, md)
g.Expect(err).ToNot(HaveOccurred())
if tt.wantRollout {
g.Expect(md.Spec.Template.Annotations).To(HaveKey("cluster.x-k8s.io/restartedAt"))
g.Expect(md.Spec.RolloutAfter).NotTo(BeNil())
} else {
g.Expect(md.Spec.Template.Annotations).ToNot(HaveKey("cluster.x-k8s.io/restartedAt"))
g.Expect(md.Spec.RolloutAfter).To(BeNil())
}
case *controlplanev1.KubeadmControlPlane:
kcp := &controlplanev1.KubeadmControlPlane{}
Expand All @@ -204,7 +231,7 @@ func Test_ObjectRestarter(t *testing.T) {
if tt.wantRollout {
g.Expect(kcp.Spec.RolloutAfter).NotTo(BeNil())
} else {
g.Expect(kcp.Spec.RolloutAfter).To(nil)
g.Expect(kcp.Spec.RolloutAfter).To(BeNil())
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml

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

14 changes: 9 additions & 5 deletions docs/book/src/tasks/upgrading-clusters.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,20 @@ The rollout can be triggered by running the following command:
clusterctl alpha rollout restart kubeadmcontrollplane/my-kcp
```

To do the same for machines managed by a `MachineDeployment` it's enough to make an arbitrary
change to its `Spec.Template`, one common approach is to run:
Similarly, to do the same for machines managed by a `MachineDeployment` it has field `RolloutAfter` that
can be set to a timestamp (RFC-3339) after which a rollout should be triggered regardless of whether there
were any changes to the `MachineDeployment.Spec.Template` or not.

Note that this field can only be used for triggering a rollout, not for delaying one. Specifically,
a rollout can also happen before the time specified in `RolloutAfter` if any changes are made to
the spec before that time.

The rollout can be triggered by running the following command:

``` shell
clusterctl alpha rollout restart machinedeployment/my-md-0
```

This will modify the template by setting an `cluster.x-k8s.io/restartedAt` annotation which will
trigger a rollout.

### Upgrading machines managed by a `MachineDeployment`

Upgrades are not limited to just the control plane. This section is not related to Kubeadm control plane specifically,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ type Reconciler struct {
Client client.Client
APIReader client.Reader

// reconciliationTime is the time of the current reconciliation, and should be used for all "now" calculations
reconciliationTime metav1.Time

// WatchFilterValue is the label value used to filter events prior to reconciliation.
WatchFilterValue string

Expand Down Expand Up @@ -111,6 +114,7 @@ func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opt

func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
log := ctrl.LoggerFrom(ctx)
r.reconciliationTime = metav1.Now()

// Fetch the MachineDeployment instance.
deployment := &clusterv1.MachineDeployment{}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (r *Reconciler) sync(ctx context.Context, d *clusterv1.MachineDeployment, m
// Note that currently the deployment controller is using caches to avoid querying the server for reads.
// This may lead to stale reads of machine sets, thus incorrect deployment status.
func (r *Reconciler) getAllMachineSetsAndSyncRevision(ctx context.Context, d *clusterv1.MachineDeployment, msList []*clusterv1.MachineSet, createIfNotExisted bool) (*clusterv1.MachineSet, []*clusterv1.MachineSet, error) {
_, allOldMSs := mdutil.FindOldMachineSets(d, msList)
_, allOldMSs := mdutil.FindOldMachineSets(d, msList, &r.reconciliationTime)

// Get new machine set with the updated revision number
newMS, err := r.getNewMachineSet(ctx, d, msList, allOldMSs, createIfNotExisted)
Expand All @@ -101,7 +101,7 @@ func (r *Reconciler) getNewMachineSet(ctx context.Context, d *clusterv1.MachineD
// If we don't find a matching MachineSet, we need a rollout and thus create a new MachineSet.
// Note: The in-place mutable fields can be just updated inline, because they do not affect the actual machines
// themselves (i.e. the infrastructure and the software running on the Machines not the Machine object).
matchingMS := mdutil.FindNewMachineSet(d, msList)
matchingMS := mdutil.FindNewMachineSet(d, msList, &r.reconciliationTime)

// If there is a MachineSet that matches the intent of the MachineDeployment, update the MachineSet
// to propagate all in-place mutable fields from MachineDeployment to the MachineSet.
Expand Down
Loading

0 comments on commit 9a67a0c

Please sign in to comment.