Skip to content

Commit 9e48879

Browse files
authored
Merge pull request #8358 from ykakarap/unitialized-worker-nodes-only
🐛 set uninitialized taint only on worker nodes
2 parents 9f45df1 + 1bb9bca commit 9e48879

File tree

4 files changed

+11
-176
lines changed

4 files changed

+11
-176
lines changed

bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go

Lines changed: 5 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,6 @@ const (
6464
KubeadmConfigControllerName = "kubeadmconfig-controller"
6565
)
6666

67-
var (
68-
// controlPlaneTaint is the taint that kubeadm applies to the control plane nodes
69-
// for Kubernetes version >= v1.24.0.
70-
// The values are copied from kubeadm codebase.
71-
controlPlaneTaint = corev1.Taint{
72-
Key: "node-role.kubernetes.io/control-plane",
73-
Effect: corev1.TaintEffectNoSchedule,
74-
}
75-
76-
// oldControlPlaneTaint is the taint that kubeadm applies to the control plane nodes
77-
// for Kubernetes version < v1.25.0.
78-
// The values are copied from kubeadm codebase.
79-
oldControlPlaneTaint = corev1.Taint{
80-
Key: "node-role.kubernetes.io/master",
81-
Effect: corev1.TaintEffectNoSchedule,
82-
}
83-
)
84-
8567
const (
8668
// DefaultTokenTTL is the default TTL used for tokens.
8769
DefaultTokenTTL = 15 * time.Minute
@@ -432,14 +414,7 @@ func (r *KubeadmConfigReconciler) handleClusterNotInitialized(ctx context.Contex
432414
}
433415
}
434416

435-
// Add the node uninitialized taint to the list of taints.
436-
// DeepCopy the InitConfiguration to prevent updating the actual KubeadmConfig.
437-
// Do not modify the KubeadmConfig in etcd as this is a temporary taint that will be dropped after the node
438-
// is initialized by ClusterAPI.
439-
initConfiguration := scope.Config.Spec.InitConfiguration.DeepCopy()
440-
addNodeUninitializedTaint(&initConfiguration.NodeRegistration, true, parsedVersion)
441-
442-
initdata, err := kubeadmtypes.MarshalInitConfigurationForVersion(initConfiguration, parsedVersion)
417+
initdata, err := kubeadmtypes.MarshalInitConfigurationForVersion(scope.Config.Spec.InitConfiguration, parsedVersion)
443418
if err != nil {
444419
scope.Error(err, "Failed to marshal init configuration")
445420
return ctrl.Result{}, err
@@ -580,7 +555,9 @@ func (r *KubeadmConfigReconciler) joinWorker(ctx context.Context, scope *Scope)
580555
// Do not modify the KubeadmConfig in etcd as this is a temporary taint that will be dropped after the node
581556
// is initialized by ClusterAPI.
582557
joinConfiguration := scope.Config.Spec.JoinConfiguration.DeepCopy()
583-
addNodeUninitializedTaint(&joinConfiguration.NodeRegistration, false, parsedVersion)
558+
if !hasTaint(joinConfiguration.NodeRegistration.Taints, clusterv1.NodeUninitializedTaint) {
559+
joinConfiguration.NodeRegistration.Taints = append(joinConfiguration.NodeRegistration.Taints, clusterv1.NodeUninitializedTaint)
560+
}
584561

585562
joinData, err := kubeadmtypes.MarshalJoinConfigurationForVersion(joinConfiguration, parsedVersion)
586563
if err != nil {
@@ -688,14 +665,7 @@ func (r *KubeadmConfigReconciler) joinControlplane(ctx context.Context, scope *S
688665
return ctrl.Result{}, errors.Wrapf(err, "failed to parse kubernetes version %q", kubernetesVersion)
689666
}
690667

691-
// Add the node uninitialized taint to the list of taints.
692-
// DeepCopy the JoinConfiguration to prevent updating the actual KubeadmConfig.
693-
// Do not modify the KubeadmConfig in etcd as this is a temporary taint that will be dropped after the node
694-
// is initialized by ClusterAPI.
695-
joinConfiguration := scope.Config.Spec.JoinConfiguration.DeepCopy()
696-
addNodeUninitializedTaint(&joinConfiguration.NodeRegistration, true, parsedVersion)
697-
698-
joinData, err := kubeadmtypes.MarshalJoinConfigurationForVersion(joinConfiguration, parsedVersion)
668+
joinData, err := kubeadmtypes.MarshalJoinConfigurationForVersion(scope.Config.Spec.JoinConfiguration, parsedVersion)
699669
if err != nil {
700670
scope.Error(err, "Failed to marshal join configuration")
701671
return ctrl.Result{}, err
@@ -1105,37 +1075,6 @@ func (r *KubeadmConfigReconciler) ensureBootstrapSecretOwnersRef(ctx context.Con
11051075
return nil
11061076
}
11071077

1108-
// addNodeUninitializedTaint adds the NodeUninitializedTaint to the nodeRegistration.
1109-
// Note: If isControlPlane is true then it also adds the control plane taint if the initial set of taints is nil.
1110-
// This is to ensure consistency with kubeadm's defaulting behavior.
1111-
func addNodeUninitializedTaint(nodeRegistration *bootstrapv1.NodeRegistrationOptions, isControlPlane bool, kubernetesVersion semver.Version) {
1112-
var taints []corev1.Taint
1113-
taints = nodeRegistration.Taints
1114-
if hasTaint(taints, clusterv1.NodeUninitializedTaint) {
1115-
return
1116-
}
1117-
1118-
// For a control plane, kubeadm adds the default control plane taint if the provided taints are nil.
1119-
// Since we are adding the uninitialized taint we also have to add the default taints kubeadm would have added if
1120-
// the taints were nil.
1121-
if isControlPlane && taints == nil {
1122-
// Note: Kubeadm uses a different default control plane taint depending on the kubernetes version.
1123-
// Ref: https://github.com/kubernetes/kubeadm/issues/2200
1124-
if kubernetesVersion.LT(semver.MustParse("1.24.0")) {
1125-
taints = []corev1.Taint{oldControlPlaneTaint}
1126-
} else if kubernetesVersion.GTE(semver.MustParse("1.24.0")) && kubernetesVersion.LT(semver.MustParse("1.25.0")) {
1127-
taints = []corev1.Taint{
1128-
oldControlPlaneTaint,
1129-
controlPlaneTaint,
1130-
}
1131-
} else {
1132-
taints = []corev1.Taint{controlPlaneTaint}
1133-
}
1134-
}
1135-
taints = append(taints, clusterv1.NodeUninitializedTaint)
1136-
nodeRegistration.Taints = taints
1137-
}
1138-
11391078
func hasTaint(taints []corev1.Taint, targetTaint corev1.Taint) bool {
11401079
for _, taint := range taints {
11411080
if taint.MatchTaint(&targetTaint) {

bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go

Lines changed: 0 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"testing"
2525
"time"
2626

27-
"github.com/blang/semver"
2827
ignition "github.com/flatcar/ignition/config/v2_3"
2928
. "github.com/onsi/gomega"
3029
corev1 "k8s.io/api/core/v1"
@@ -2194,112 +2193,6 @@ func TestKubeadmConfigReconciler_ResolveUsers(t *testing.T) {
21942193
}
21952194
}
21962195

2197-
func TestAddNodeUninitializedTaint(t *testing.T) {
2198-
dummyTaint := corev1.Taint{
2199-
Key: "dummy-taint",
2200-
Value: "",
2201-
Effect: corev1.TaintEffectNoSchedule,
2202-
}
2203-
2204-
tests := []struct {
2205-
name string
2206-
nodeRegistration *bootstrapv1.NodeRegistrationOptions
2207-
kubernetesVersion semver.Version
2208-
isControlPlane bool
2209-
wantTaints []corev1.Taint
2210-
}{
2211-
{
2212-
name: "for control plane with version < v1.24.0, if taints is nil it should add the uninitialized and the master taint",
2213-
nodeRegistration: &bootstrapv1.NodeRegistrationOptions{
2214-
Taints: nil,
2215-
},
2216-
kubernetesVersion: semver.MustParse("1.23.0"),
2217-
isControlPlane: true,
2218-
wantTaints: []corev1.Taint{
2219-
oldControlPlaneTaint,
2220-
clusterv1.NodeUninitializedTaint,
2221-
},
2222-
},
2223-
{
2224-
name: "for control plane with version >= v1.24.0 and < v1.25.0, if taints is nil it should add the uninitialized, control-plane and the master taints",
2225-
nodeRegistration: &bootstrapv1.NodeRegistrationOptions{
2226-
Taints: nil,
2227-
},
2228-
kubernetesVersion: semver.MustParse("1.24.5"),
2229-
isControlPlane: true,
2230-
wantTaints: []corev1.Taint{
2231-
oldControlPlaneTaint,
2232-
controlPlaneTaint,
2233-
clusterv1.NodeUninitializedTaint,
2234-
},
2235-
},
2236-
{
2237-
name: "for control plane with version >= v1.25.0, if taints is nil it should add the uninitialized and the control-plane taint",
2238-
nodeRegistration: &bootstrapv1.NodeRegistrationOptions{
2239-
Taints: nil,
2240-
},
2241-
kubernetesVersion: semver.MustParse("1.25.0"),
2242-
isControlPlane: true,
2243-
wantTaints: []corev1.Taint{
2244-
controlPlaneTaint,
2245-
clusterv1.NodeUninitializedTaint,
2246-
},
2247-
},
2248-
{
2249-
name: "for control plane, if taints is not nil it should only add the uninitialized taint",
2250-
nodeRegistration: &bootstrapv1.NodeRegistrationOptions{
2251-
Taints: []corev1.Taint{},
2252-
},
2253-
kubernetesVersion: semver.MustParse("1.26.0"),
2254-
isControlPlane: true,
2255-
wantTaints: []corev1.Taint{
2256-
clusterv1.NodeUninitializedTaint,
2257-
},
2258-
},
2259-
{
2260-
name: "for control plane, if it already has some taints it should add the uninitialized taint",
2261-
nodeRegistration: &bootstrapv1.NodeRegistrationOptions{
2262-
Taints: []corev1.Taint{dummyTaint},
2263-
},
2264-
kubernetesVersion: semver.MustParse("1.26.0"),
2265-
isControlPlane: true,
2266-
wantTaints: []corev1.Taint{
2267-
dummyTaint,
2268-
clusterv1.NodeUninitializedTaint,
2269-
},
2270-
},
2271-
{
2272-
name: "for worker, it should add the uninitialized taint",
2273-
nodeRegistration: &bootstrapv1.NodeRegistrationOptions{},
2274-
kubernetesVersion: semver.MustParse("1.26.0"),
2275-
isControlPlane: false,
2276-
wantTaints: []corev1.Taint{
2277-
clusterv1.NodeUninitializedTaint,
2278-
},
2279-
},
2280-
{
2281-
name: "for worker, if it already has some taints it should add the uninitialized taint",
2282-
nodeRegistration: &bootstrapv1.NodeRegistrationOptions{
2283-
Taints: []corev1.Taint{dummyTaint},
2284-
},
2285-
kubernetesVersion: semver.MustParse("1.26.0"),
2286-
isControlPlane: false,
2287-
wantTaints: []corev1.Taint{
2288-
dummyTaint,
2289-
clusterv1.NodeUninitializedTaint,
2290-
},
2291-
},
2292-
}
2293-
2294-
for _, tt := range tests {
2295-
t.Run(tt.name, func(t *testing.T) {
2296-
g := NewWithT(t)
2297-
addNodeUninitializedTaint(tt.nodeRegistration, tt.isControlPlane, tt.kubernetesVersion)
2298-
g.Expect(tt.nodeRegistration.Taints).To(Equal(tt.wantTaints))
2299-
})
2300-
}
2301-
}
2302-
23032196
// test utils.
23042197

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

docs/book/src/developer/providers/bootstrap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ A bootstrap provider's bootstrap data must create `/run/cluster-api/bootstrap-su
125125

126126
## Taint Nodes at creation
127127

128-
A bootstrap provider can optionally taint nodes at creation with `node.cluster.x-k8s.io/uninitialized:NoSchedule`.
128+
A bootstrap provider can optionally taint worker nodes at creation with `node.cluster.x-k8s.io/uninitialized:NoSchedule`.
129129
This taint is used to prevent workloads to be scheduled on Nodes before the node is initialized by Cluster API.
130130
As of today the Node initialization consists of syncing labels from Machines to Nodes. Once the labels have been
131131
initially synced the taint is removed form the Node.

docs/proposals/20220927-label-sync-between-machine-and-nodes.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,23 @@ Kubernetes supports both equality and inequality requirements in label selection
101101

102102
In an inequality based selection, the user wants to place a workload on node(s) that do not contain a specific label (e.g. Node.Labels not contain `my.prefix/foo=bar`). The case is potentially problematic because it relies on the absence of a label and this can occur if the pod scheduler runs during the delay interval.
103103

104-
One way to address this is to use kubelet's `--register-with-taints` flag. Newly minted nodes can be tainted via the taint `node.cluster.x-k8s.io=uninitialized:NoSchedule`. Assuming workloads don't have this specific toleration, then nothing should be scheduled. KubeadmConfigTemplate provides the means to set taints on nodes (see JoinConfiguration.NodeRegistration.Taints).
104+
One way to address this is to use kubelet's `--register-with-taints` flag. Newly minted nodes can be tainted via the taint `node.cluster.x-k8s.io/uninitialized:NoSchedule`. Assuming workloads don't have this specific toleration, then nothing should be scheduled. KubeadmConfigTemplate provides the means to set taints on nodes (see JoinConfiguration.NodeRegistration.Taints).
105105

106106
The process of tainting the nodes, can be carried out by the user and can be documented as follows:
107107

108108
```
109109
If you utilize inequality based selection for workload placement, to prevent unintended scheduling of pods during the initial node startup phase, it is recommend that you specify the following taint in your KubeadmConfigTemplate:
110-
`node.cluster.x-k8s.io=uninitialized:NoSchedule`
110+
`node.cluster.x-k8s.io/uninitialized:NoSchedule`
111111
```
112112

113113
After the node has come up and the machine controller has applied the labels, the machine controller will also remove this specific taint if it's present.
114114

115115
During the implementation we will consider also automating the insertion of the taint via CABPK in order to simplify UX;
116116
in this case, the new behaviour should be documented in the contract as optional requirement for bootstrap providers.
117117

118+
The `node.cluster.x-k8s.io/uninitialized:NoSchedule` taint should only be applied on the worker nodes. It should not be applied on the control plane nodes as it could prevent other components like CPI from initializing which will block cluster creation.
119+
120+
118121
## Alternatives
119122

120123
### Use KubeadmConfigTemplate capabilities

0 commit comments

Comments
 (0)