Skip to content

Commit 0ac94e1

Browse files
Fix multiple in-place updates issues
Signed-off-by: Alex Demicev <alex.demicev@lambdal.com>
1 parent 0f89bb4 commit 0ac94e1

7 files changed

Lines changed: 344 additions & 11 deletions

File tree

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
.vscode
44
bin/
55
test/
6+
!test/extension/
67
**/*.yaml
78
hack/
89
docs/

controlplane/config/rbac/role.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ rules:
1818
- patch
1919
- update
2020
- watch
21+
- apiGroups:
22+
- ""
23+
resources:
24+
- namespaces
25+
verbs:
26+
- get
27+
- list
28+
- watch
2129
- apiGroups:
2230
- apiextensions.k8s.io
2331
resources:

controlplane/internal/controllers/rke2controlplane_controller.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,14 @@ import (
7070
)
7171

7272
const (
73-
// rke2ManagerName is the name of the RKE2 manager deployment.
73+
// rke2ManagerName is the SSA field manager used for the main RKE2 control-plane
74+
// objects (Machine spec, InfraMachine, RKE2Config).
7475
rke2ManagerName = "rke2controlplane"
7576

77+
// rke2MetadataManagerName is a separate SSA field manager used for the
78+
// labels and annotations only patches written every reconcile by syncMachines.
79+
rke2MetadataManagerName = "rke2controlplane-metadata"
80+
7681
// rke2ControlPlaneKind is the kind of the RKE2 control plane.
7782
rke2ControlPlaneKind = "RKE2ControlPlane"
7883

@@ -119,6 +124,7 @@ type RKE2ControlPlaneReconciler struct {
119124
// +kubebuilder:rbac:groups="infrastructure.cluster.x-k8s.io",resources=*,verbs=get;list;watch;create;patch;delete
120125
// +kubebuilder:rbac:groups="apiextensions.k8s.io",resources=customresourcedefinitions,verbs=get;list;watch
121126
// +kubebuilder:rbac:groups=runtime.cluster.x-k8s.io,resources=extensionconfigs,verbs=get;list;watch
127+
// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch
122128

123129
// Reconcile is part of the main kubernetes reconciliation loop which aims to
124130
// move the current state of the cluster closer to the desired state.

controlplane/internal/controllers/scale.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,11 @@ func (r *RKE2ControlPlaneReconciler) UpdateExternalObject(
474474
// Update annotations
475475
updatedObject.SetAnnotations(rcp.Spec.MachineTemplate.ObjectMeta.Annotations)
476476

477-
if err := ssa.Patch(ctx, r.Client, rke2ManagerName, updatedObject, ssa.WithCachingProxy{Cache: r.ssaCache, Original: obj}); err != nil {
477+
// Use the metadata only field manager so we only own the labels/annotations
478+
// that were derived from RCP.MachineTemplate metadata. This avoids stripping
479+
// other annotations (e.g. UpdateInProgressAnnotation) that triggerInPlaceUpdate
480+
// wrote under the main rke2ManagerName.
481+
if err := ssa.Patch(ctx, r.Client, rke2MetadataManagerName, updatedObject, ssa.WithCachingProxy{Cache: r.ssaCache, Original: obj}); err != nil {
478482
return fmt.Errorf("failed to update %s: %w", obj.GetObjectKind().GroupVersionKind().Kind, err)
479483
}
480484

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ require (
1919
go.etcd.io/etcd/api/v3 v3.6.10
2020
go.etcd.io/etcd/client/v3 v3.6.10
2121
go.uber.org/zap v1.28.0
22+
gomodules.xyz/jsonpatch/v2 v2.5.0
2223
google.golang.org/grpc v1.80.0
2324
gopkg.in/yaml.v3 v3.0.1
2425
k8s.io/api v0.35.4
2526
k8s.io/apiextensions-apiserver v0.35.4
2627
k8s.io/apimachinery v0.35.4
2728
k8s.io/apiserver v0.35.4
2829
k8s.io/client-go v0.35.4
30+
k8s.io/component-base v0.35.4
2931
k8s.io/klog/v2 v2.140.0
3032
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
3133
sigs.k8s.io/cluster-api v1.13.1
@@ -156,14 +158,12 @@ require (
156158
golang.org/x/text v0.36.0 // indirect
157159
golang.org/x/time v0.14.0 // indirect
158160
golang.org/x/tools v0.44.0 // indirect
159-
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
160161
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
161162
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
162163
google.golang.org/protobuf v1.36.11 // indirect
163164
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
164165
gopkg.in/inf.v0 v0.9.1 // indirect
165166
k8s.io/cluster-bootstrap v0.35.4 // indirect
166-
k8s.io/component-base v0.35.4 // indirect
167167
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
168168
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
169169
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect

pkg/rke2/control_plane.go

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import (
4646
controlplanev1 "github.com/rancher/cluster-api-provider-rke2/controlplane/api/v1beta2"
4747
"github.com/rancher/cluster-api-provider-rke2/pkg/capi/hooks"
4848
"github.com/rancher/cluster-api-provider-rke2/pkg/capi/inplace"
49+
"github.com/rancher/cluster-api-provider-rke2/pkg/rke2/desiredstate"
4950
)
5051

5152
// UpToDateResult is the result of calling the UpToDate func for a Machine.
@@ -141,7 +142,7 @@ func NewControlPlane(
141142
machinesUpToDateResults := map[string]UpToDateResult{}
142143

143144
for _, m := range ownedMachines {
144-
upToDate, result, err := UpToDate(ctx, m, rcp, infraObjects, rke2Configs)
145+
upToDate, result, err := UpToDate(ctx, client, cluster, m, rcp, infraObjects, rke2Configs)
145146
if err != nil {
146147
return nil, err
147148
}
@@ -628,8 +629,14 @@ func (c *ControlPlane) UsesEmbeddedEtcd() bool {
628629

629630
// UpToDate checks if a Machine is up to date with the control plane's configuration.
630631
// If not, messages explaining why are provided with different level of detail for logs and conditions.
631-
func UpToDate(ctx context.Context, machine *clusterv1.Machine, rcp *controlplanev1.RKE2ControlPlane,
632-
infraConfigs map[string]*unstructured.Unstructured, machineConfigs map[string]*bootstrapv1.RKE2Config,
632+
func UpToDate(
633+
ctx context.Context,
634+
c client.Client,
635+
cluster *clusterv1.Cluster,
636+
machine *clusterv1.Machine,
637+
rcp *controlplanev1.RKE2ControlPlane,
638+
infraConfigs map[string]*unstructured.Unstructured,
639+
machineConfigs map[string]*bootstrapv1.RKE2Config,
633640
) (bool, *UpToDateResult, error) {
634641
res := &UpToDateResult{
635642
EligibleForInPlaceUpdate: true,
@@ -655,7 +662,11 @@ func UpToDate(ctx context.Context, machine *clusterv1.Machine, rcp *controlplane
655662
}
656663

657664
// Machines that do not match with rcp config.
658-
matches, specLogMessages, specConditionMessages := matchesMachineSpec(ctx, infraConfigs, machineConfigs, rcp, machine)
665+
matches, specLogMessages, specConditionMessages, err := matchesMachineSpec(ctx, c, cluster, infraConfigs, machineConfigs, rcp, machine, res)
666+
if err != nil {
667+
return false, nil, err
668+
}
669+
659670
if !matches {
660671
res.LogMessages = append(res.LogMessages, specLogMessages...)
661672
res.ConditionMessages = append(res.ConditionMessages, specConditionMessages...)
@@ -681,14 +692,55 @@ func UpToDate(ctx context.Context, machine *clusterv1.Machine, rcp *controlplane
681692
// - are not relevant for the rollout decision (ex: failureDomain).
682693
func matchesMachineSpec(
683694
ctx context.Context,
695+
c client.Client,
696+
cluster *clusterv1.Cluster,
684697
infraConfigs map[string]*unstructured.Unstructured,
685698
machineConfigs map[string]*bootstrapv1.RKE2Config,
686699
rcp *controlplanev1.RKE2ControlPlane,
687700
machine *clusterv1.Machine,
688-
) (bool, []string, []string) {
701+
res *UpToDateResult,
702+
) (bool, []string, []string, error) {
689703
logMessages := []string{}
690704
conditionMessages := []string{}
691705

706+
if cluster != nil {
707+
desiredMachine, err := desiredstate.ComputeDesiredMachine(
708+
rcp, cluster,
709+
machine.Spec.InfrastructureRef, machine.Spec.Bootstrap.ConfigRef,
710+
machine.Spec.FailureDomain, machine,
711+
)
712+
if err != nil {
713+
return false, nil, nil, fmt.Errorf("failed to compute desired Machine for %s: %w", machine.Name, err)
714+
}
715+
716+
// Note: spec.version is not mutated in-place by syncMachines and accordingly
717+
// not updated by desiredstate.ComputeDesiredMachine, so we have to update it here.
718+
// Note: spec.failureDomain is in general only changed on delete/create, so we don't have to update it here for in-place.
719+
desiredMachine.Spec.Version = rcp.Spec.Version
720+
res.DesiredMachine = desiredMachine
721+
// Note: Intentionally not storing currentMachine as it can change later, e.g. through syncMachines.
722+
723+
if res.CurrentRKE2Config != nil {
724+
desiredRKE2Config, err := desiredstate.ComputeDesiredRKE2Config(rcp, cluster, res.CurrentRKE2Config.Name, res.CurrentRKE2Config)
725+
if err != nil {
726+
return false, nil, nil, fmt.Errorf("failed to compute desired RKE2Config for %s: %w", machine.Name, err)
727+
}
728+
729+
res.DesiredRKE2Config = desiredRKE2Config
730+
}
731+
732+
if res.CurrentInfraMachine != nil {
733+
desiredInfraMachine, err := desiredstate.ComputeDesiredInfraMachine(
734+
ctx, c, rcp, cluster, res.CurrentInfraMachine.GetName(), res.CurrentInfraMachine,
735+
)
736+
if err != nil {
737+
return false, nil, nil, fmt.Errorf("failed to compute desired InfraMachine for %s: %w", machine.Name, err)
738+
}
739+
740+
res.DesiredInfraMachine = desiredInfraMachine
741+
}
742+
}
743+
692744
if !collections.MatchesKubernetesVersion(rcp.Spec.Version)(machine) {
693745
machineVersion := ""
694746
if machine != nil && machine.Spec.Version != "" {
@@ -711,8 +763,8 @@ func matchesMachineSpec(
711763
}
712764

713765
if len(logMessages) > 0 || len(conditionMessages) > 0 {
714-
return false, logMessages, conditionMessages
766+
return false, logMessages, conditionMessages, nil
715767
}
716768

717-
return true, nil, nil
769+
return true, nil, nil, nil
718770
}

0 commit comments

Comments
 (0)