Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions controlplane/kubeadm/internal/controllers/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ const (
kcpManagerName = "capi-kubeadmcontrolplane"
kcpMetadataManagerName = "capi-kubeadmcontrolplane-metadata"
kubeadmControlPlaneKind = "KubeadmControlPlane"

// Event reasons for certificate-related automatic actions.

// EventKubeconfigCertificateRotated is emitted when kubeconfig certificate
// is automatically rotated due to approaching expiry.
EventKubeconfigCertificateRotated = "KubeconfigCertificateRotated"

// EventKubeconfigCertificateRotationFailed is emitted when kubeconfig
// certificate rotation fails.
EventKubeconfigCertificateRotationFailed = "KubeconfigCertificateRotationFailed"

// EventCertificateExpiryTriggeredRollout is emitted when a machine is marked
// for rollout due to certificate expiry approaching the threshold.
EventCertificateExpiryTriggeredRollout = "CertificateExpiryTriggeredRollout"
)

// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
Expand Down Expand Up @@ -515,6 +529,44 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, controlPl
allMessages = append(allMessages, fmt.Sprintf("Machine %s needs rollout: %s", name, strings.Join(machinesUpToDateResults[name].LogMessages, ", ")))
}
log.Info(fmt.Sprintf("Machines need rollout: %s", strings.Join(machinesNeedingRolloutNames, ",")), "reason", strings.Join(allMessages, ", "))

// Emit events for machines being rolled out due to certificate expiry
// Following the double-sided event pattern: emit on both Machine (victim) and KCP (actor)
for _, machine := range machinesNeedingRollout {
upToDateResult, ok := machinesUpToDateResults[machine.Name]
if !ok {
continue
}

// Check if the rollout is due to certificate expiry
isCertExpiryRollout := false
for _, msg := range upToDateResult.LogMessages {
if strings.Contains(msg, "certificates will expire soon") || strings.Contains(msg, "rolloutBefore expired") {
isCertExpiryRollout = true
break
}
}

if isCertExpiryRollout {
// Event on Machine (the victim) - tells the machine why it's being rolled out
r.recorder.Eventf(
machine,
corev1.EventTypeWarning,
EventCertificateExpiryTriggeredRollout,
"Machine will be rolled out due to certificate expiry approaching the configured threshold",
)

// Event on KCP (the actor) - tells the administrator what the controller is doing
r.recorder.Eventf(
controlPlane.KCP,
corev1.EventTypeNormal,
EventCertificateExpiryTriggeredRollout,
"Rolling out machine %s due to certificate expiry approaching threshold",
machine.Name,
)
}
}

v1beta1conditions.MarkFalse(controlPlane.KCP, controlplanev1.MachinesSpecUpToDateV1Beta1Condition, controlplanev1.RollingUpdateInProgressV1Beta1Reason, clusterv1.ConditionSeverityWarning, "Rolling %d replicas with outdated spec (%d replicas up to date)", len(machinesNeedingRollout), len(controlPlane.Machines)-len(machinesNeedingRollout))
return r.updateControlPlane(ctx, controlPlane, machinesNeedingRollout, machinesUpToDateResults)
default:
Expand Down
20 changes: 20 additions & 0 deletions controlplane/kubeadm/internal/controllers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
kerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

Expand Down Expand Up @@ -90,7 +91,26 @@ func (r *KubeadmControlPlaneReconciler) reconcileKubeconfig(ctx context.Context,

if needsRotation {
log.Info("Rotating kubeconfig secret")

// Emit event to notify about automatic certificate rotation
r.recorder.Eventf(
controlPlane.KCP,
corev1.EventTypeNormal,
EventKubeconfigCertificateRotated,
"Automatically rotated kubeconfig secret for cluster %s due to client certificate approaching expiry",
klog.KRef(controlPlane.Cluster.Namespace, controlPlane.Cluster.Name),
)

if err := kubeconfig.RegenerateSecret(ctx, r.Client, configSecret, kubeconfig.KeyEncryptionAlgorithm(controlPlane.GetKeyEncryptionAlgorithm())); err != nil {
// Emit warning event on failure
r.recorder.Eventf(
controlPlane.KCP,
corev1.EventTypeWarning,
EventKubeconfigCertificateRotationFailed,
"Failed to rotate kubeconfig secret for cluster %s: %v",
klog.KRef(controlPlane.Cluster.Namespace, controlPlane.Cluster.Name),
err,
)
return ctrl.Result{}, errors.Wrap(err, "failed to regenerate kubeconfig")
}
}
Expand Down