Skip to content

Commit bac8e27

Browse files
author
Yuvaraj Kakaraparthi
committed
add KCP rollout support based on machine certificate expiry
A new annotation is added to KubeadmConfig that captures the certificate expiry information. This information is propagated to machines. This new information can be used by KCP to perform a rollout if the machine certificates are about to expire. Note: The expiry time captured in KubeadmConfig is an approximate time of when the certificates will expire but it is guaranteed to be before the actual certificate expiry.
1 parent e468cab commit bac8e27

29 files changed

+603
-22
lines changed

api/v1alpha3/conversion.go

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func (src *Machine) ConvertTo(dstRaw conversion.Hub) error {
9999

100100
dst.Spec.NodeDeletionTimeout = restored.Spec.NodeDeletionTimeout
101101
dst.Status.NodeInfo = restored.Status.NodeInfo
102+
dst.Status.CertificatesExpiryDate = restored.Status.CertificatesExpiryDate
102103
return nil
103104
}
104105

api/v1alpha3/zz_generated.conversion.go

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

api/v1alpha4/conversion.go

+6
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ func (src *Machine) ConvertTo(dstRaw conversion.Hub) error {
159159
}
160160

161161
dst.Spec.NodeDeletionTimeout = restored.Spec.NodeDeletionTimeout
162+
dst.Status.CertificatesExpiryDate = restored.Status.CertificatesExpiryDate
162163
return nil
163164
}
164165

@@ -333,3 +334,8 @@ func Convert_v1beta1_ControlPlaneTopology_To_v1alpha4_ControlPlaneTopology(in *c
333334
// controlPlaneTopology.nodeDrainTimeout has been added with v1beta1.
334335
return autoConvert_v1beta1_ControlPlaneTopology_To_v1alpha4_ControlPlaneTopology(in, out, s)
335336
}
337+
338+
func Convert_v1beta1_MachineStatus_To_v1alpha4_MachineStatus(in *clusterv1.MachineStatus, out *MachineStatus, s apiconversion.Scope) error {
339+
// MachineStatus.CertificatesExpiryDate has been added in v1beta1.
340+
return autoConvert_v1beta1_MachineStatus_To_v1alpha4_MachineStatus(in, out, s)
341+
}

api/v1alpha4/zz_generated.conversion.go

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

api/v1beta1/machine_types.go

+11
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ const (
5050
// to pause reconciliation of deletion. These hooks will prevent removal of
5151
// an instance from an infrastructure provider until all are removed.
5252
PreTerminateDeleteHookAnnotationPrefix = "pre-terminate.delete.hook.machine.cluster.x-k8s.io"
53+
54+
// MachineCertificatesExpiryDateAnnotation annotation specifies the expiry date of the machine certificates in RFC3339 format.
55+
// This annotation can be used on control plane machines to trigger rollout before certificates expire.
56+
// This annotation can be set on BootstrapConfig or Machine objects. The value set on the Machine object takes precedence.
57+
// This annotation can only be used on Control Plane Machines.
58+
MachineCertificatesExpiryDateAnnotation = "machine.cluster.x-k8s.io/certificates-expiry"
5359
)
5460

5561
// ANCHOR: MachineSpec
@@ -171,6 +177,11 @@ type MachineStatus struct {
171177
// +optional
172178
Phase string `json:"phase,omitempty"`
173179

180+
// CertificatesExpiryDate is the expiry date of the machine certificates.
181+
// This value is only set for control plane machines.
182+
// +optional
183+
CertificatesExpiryDate *metav1.Time `json:"certificatesExpiryDate,omitempty"`
184+
174185
// BootstrapReady is the state of the bootstrap provider.
175186
// +optional
176187
BootstrapReady bool `json:"bootstrapReady"`

api/v1beta1/zz_generated.deepcopy.go

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

api/v1beta1/zz_generated.openapi.go

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

bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go

+32
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,15 @@ const (
6565
const (
6666
// DefaultTokenTTL is the default TTL used for tokens.
6767
DefaultTokenTTL = 15 * time.Minute
68+
69+
// This hard-coded duration matches the hard-coded value used by kubeadm certificate generation.
70+
certificateExpiryDuration = 365 * 24 * time.Hour
6871
)
6972

73+
// now returns the current time.
74+
// This is defined as a variable so that it can be overridden in unit tests.
75+
var now = time.Now
76+
7077
// InitLocker is a lock that is used around kubeadm init.
7178
type InitLocker interface {
7279
Lock(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) bool
@@ -439,6 +446,7 @@ func (r *KubeadmConfigReconciler) handleClusterNotInitialized(ctx context.Contex
439446
conditions.MarkFalse(scope.Config, bootstrapv1.CertificatesAvailableCondition, bootstrapv1.CertificatesGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
440447
return ctrl.Result{}, err
441448
}
449+
442450
conditions.MarkTrue(scope.Config, bootstrapv1.CertificatesAvailableCondition)
443451

444452
verbosityFlag := ""
@@ -495,6 +503,10 @@ func (r *KubeadmConfigReconciler) handleClusterNotInitialized(ctx context.Contex
495503
return ctrl.Result{}, err
496504
}
497505

506+
// Update the certificate expiration time in the config.
507+
// This annotation will be used by KCP to trigger control plane machines rollout before the certificate generated on the machine are going to expire.
508+
r.addCertificateExpiryAnnotation(scope.Config)
509+
498510
return ctrl.Result{}, nil
499511
}
500512

@@ -620,6 +632,7 @@ func (r *KubeadmConfigReconciler) joinControlplane(ctx context.Context, scope *S
620632
conditions.MarkFalse(scope.Config, bootstrapv1.CertificatesAvailableCondition, bootstrapv1.CertificatesCorruptedReason, clusterv1.ConditionSeverityError, err.Error())
621633
return ctrl.Result{}, err
622634
}
635+
623636
conditions.MarkTrue(scope.Config, bootstrapv1.CertificatesAvailableCondition)
624637

625638
// Ensure that joinConfiguration.Discovery is properly set for joining node on the current cluster.
@@ -695,6 +708,9 @@ func (r *KubeadmConfigReconciler) joinControlplane(ctx context.Context, scope *S
695708
return ctrl.Result{}, err
696709
}
697710

711+
// Update the certificate expiration time in the config.
712+
r.addCertificateExpiryAnnotation(scope.Config)
713+
698714
return ctrl.Result{}, nil
699715
}
700716

@@ -1012,3 +1028,19 @@ func (r *KubeadmConfigReconciler) storeBootstrapData(ctx context.Context, scope
10121028
conditions.MarkTrue(scope.Config, bootstrapv1.DataSecretAvailableCondition)
10131029
return nil
10141030
}
1031+
1032+
// addCertificateExpiryAnnotation sets the certificate expiration time as an
1033+
// annotation on KubeadmConfig, if it doesn't exist already.
1034+
// NOTE: the certificate expiry date stored in the annotation will be slightly different from the one
1035+
// actually used in the certificates - that depends on the exact time kubeadm runs on the machine-,
1036+
// but this approximation is acceptable given that it happens before the actual expiration date.
1037+
func (r *KubeadmConfigReconciler) addCertificateExpiryAnnotation(config *bootstrapv1.KubeadmConfig) {
1038+
annotations := config.GetAnnotations()
1039+
if annotations == nil {
1040+
annotations = map[string]string{}
1041+
}
1042+
if _, ok := annotations[clusterv1.MachineCertificatesExpiryDateAnnotation]; !ok {
1043+
annotations[clusterv1.MachineCertificatesExpiryDateAnnotation] = now().Add(certificateExpiryDuration).Format(time.RFC3339)
1044+
config.SetAnnotations(annotations)
1045+
}
1046+
}

bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -2069,6 +2069,48 @@ func TestKubeadmConfigReconciler_ResolveUsers(t *testing.T) {
20692069
}
20702070
}
20712071

2072+
func TestKubeadmConfigReconciler_ReconcileCertificateExpiryTime(t *testing.T) {
2073+
fakeNow, _ := time.Parse(time.RFC3339, "2022-01-01T00:00:00Z")
2074+
now = func() time.Time {
2075+
return fakeNow
2076+
}
2077+
oneYearFromNow := "2023-01-01T00:00:00Z"
2078+
time2 := "2023-10-01T00:00:00Z"
2079+
2080+
tests := []struct {
2081+
name string
2082+
cfg *bootstrapv1.KubeadmConfig
2083+
wantTime string
2084+
}{
2085+
{
2086+
name: "set the expiry time to one year from now if the expiry time is not set",
2087+
cfg: &bootstrapv1.KubeadmConfig{},
2088+
wantTime: oneYearFromNow,
2089+
},
2090+
{
2091+
name: "do not change the expiry time if it is already set",
2092+
cfg: &bootstrapv1.KubeadmConfig{
2093+
ObjectMeta: metav1.ObjectMeta{
2094+
Annotations: map[string]string{
2095+
clusterv1.MachineCertificatesExpiryDateAnnotation: time2,
2096+
},
2097+
},
2098+
},
2099+
wantTime: time2,
2100+
},
2101+
}
2102+
2103+
for _, tt := range tests {
2104+
t.Run(tt.name, func(t *testing.T) {
2105+
g := NewWithT(t)
2106+
k := &KubeadmConfigReconciler{}
2107+
k.addCertificateExpiryAnnotation(tt.cfg)
2108+
annotations := tt.cfg.GetAnnotations()
2109+
g.Expect(annotations[clusterv1.MachineCertificatesExpiryDateAnnotation]).To(Equal(tt.wantTime))
2110+
})
2111+
}
2112+
}
2113+
20722114
// test utils.
20732115

20742116
// newWorkerMachineForCluster returns a Machine with the passed Cluster's information and a pre-configured name.

config/crd/bases/cluster.x-k8s.io_machines.yaml

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

controlplane/kubeadm/api/v1alpha3/conversion.go

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error {
8282
dst.Spec.KubeadmConfigSpec.JoinConfiguration.SkipPhases = restored.Spec.KubeadmConfigSpec.JoinConfiguration.SkipPhases
8383
}
8484

85+
dst.Spec.RolloutBefore = restored.Spec.RolloutBefore
86+
8587
return nil
8688
}
8789

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

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

controlplane/kubeadm/api/v1alpha4/conversion.go

+8
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error {
6767
}
6868

6969
dst.Spec.MachineTemplate.NodeDeletionTimeout = restored.Spec.MachineTemplate.NodeDeletionTimeout
70+
dst.Spec.RolloutBefore = restored.Spec.RolloutBefore
7071

7172
return nil
7273
}
@@ -140,6 +141,8 @@ func (src *KubeadmControlPlaneTemplate) ConvertTo(dstRaw conversion.Hub) error {
140141
dst.Spec.Template.Spec.MachineTemplate.NodeDeletionTimeout = restored.Spec.Template.Spec.MachineTemplate.NodeDeletionTimeout
141142
}
142143

144+
dst.Spec.Template.Spec.RolloutBefore = restored.Spec.Template.Spec.RolloutBefore
145+
143146
return nil
144147
}
145148

@@ -226,3 +229,8 @@ func Convert_v1beta1_KubeadmControlPlaneMachineTemplate_To_v1alpha4_KubeadmContr
226229
// .NodeDrainTimeout was added in v1beta1.
227230
return autoConvert_v1beta1_KubeadmControlPlaneMachineTemplate_To_v1alpha4_KubeadmControlPlaneMachineTemplate(in, out, s)
228231
}
232+
233+
func Convert_v1beta1_KubeadmControlPlaneSpec_To_v1alpha4_KubeadmControlPlaneSpec(in *controlplanev1.KubeadmControlPlaneSpec, out *KubeadmControlPlaneSpec, scope apiconversion.Scope) error {
234+
// .RolloutBefore was added in v1beta1.
235+
return autoConvert_v1beta1_KubeadmControlPlaneSpec_To_v1alpha4_KubeadmControlPlaneSpec(in, out, scope)
236+
}

controlplane/kubeadm/api/v1alpha4/zz_generated.conversion.go

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

controlplane/kubeadm/api/v1beta1/kubeadm_control_plane_types.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,14 @@ type KubeadmControlPlaneSpec struct {
7070
// to use for initializing and joining machines to the control plane.
7171
KubeadmConfigSpec bootstrapv1.KubeadmConfigSpec `json:"kubeadmConfigSpec"`
7272

73+
// RolloutBefore is a field to indicate a rollout should be performed
74+
// if the specified criteria is met.
75+
// +optional
76+
RolloutBefore *RolloutBefore `json:"rolloutBefore,omitempty"`
77+
7378
// RolloutAfter is a field to indicate a rollout should be performed
7479
// after the specified time even if no changes have been made to the
7580
// KubeadmControlPlane.
76-
//
7781
// +optional
7882
RolloutAfter *metav1.Time `json:"rolloutAfter,omitempty"`
7983

@@ -109,6 +113,14 @@ type KubeadmControlPlaneMachineTemplate struct {
109113
NodeDeletionTimeout *metav1.Duration `json:"nodeDeletionTimeout,omitempty"`
110114
}
111115

116+
// RolloutBefore describes when a rollout should be performed on the KCP machines.
117+
type RolloutBefore struct {
118+
// CertificatesExpiryDays indicates a rollout needs to be performed if the
119+
// certificates of the machine will expire within the specified days.
120+
// +optional
121+
CertificatesExpiryDays *int32 `json:"certificatesExpiryDays,omitempty"`
122+
}
123+
112124
// RolloutStrategy describes how to replace existing machines
113125
// with new ones.
114126
type RolloutStrategy struct {

0 commit comments

Comments
 (0)