Skip to content

Commit 6fa1e93

Browse files
sats-23ajaysundark
andauthored
feat: Restrict NodeReadinessRuleSpec.Taint to "readiness.k8s.io/" prefix (#112)
Signed-off-by: Sathvik <Sathvik.S@ibm.com> Co-authored-by: ajaysundar.k <ajaysundar.k@gmail.com>
1 parent df24a64 commit 6fa1e93

File tree

11 files changed

+165
-72
lines changed

11 files changed

+165
-72
lines changed

api/v1alpha1/nodereadinessrule_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ type NodeReadinessRuleSpec struct {
7474
// when combined with continuous enforcement mode. Prefer NoSchedule for most use cases.
7575
//
7676
// +required
77+
// +kubebuilder:validation:XValidation:rule="self.key.startsWith('readiness.k8s.io/')",message="taint key must start with 'readiness.k8s.io/'"
78+
// +kubebuilder:validation:XValidation:rule="self.key.size() <= 253",message="taint key length must be at most 253 characters"
79+
// +kubebuilder:validation:XValidation:rule="!has(self.value) || self.value.size() <= 63",message="taint value length must be at most 63 characters"
80+
// +kubebuilder:validation:XValidation:rule="self.effect in ['NoSchedule', 'PreferNoSchedule', 'NoExecute']",message="taint effect must be one of 'NoSchedule', 'PreferNoSchedule', 'NoExecute'"
7781
Taint corev1.Taint `json:"taint,omitempty,omitzero"`
7882

7983
// nodeSelector limits the scope of this rule to a specific subset of Nodes.

config/crd/bases/readiness.node.x-k8s.io_nodereadinessrules.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,16 @@ spec:
183183
- effect
184184
- key
185185
type: object
186+
x-kubernetes-validations:
187+
- message: taint key must start with 'readiness.k8s.io/'
188+
rule: self.key.startsWith('readiness.k8s.io/')
189+
- message: taint key length must be at most 253 characters
190+
rule: self.key.size() <= 253
191+
- message: taint value length must be at most 63 characters
192+
rule: '!has(self.value) || self.value.size() <= 63'
193+
- message: taint effect must be one of 'NoSchedule', 'PreferNoSchedule',
194+
'NoExecute'
195+
rule: self.effect in ['NoSchedule', 'PreferNoSchedule', 'NoExecute']
186196
required:
187197
- conditions
188198
- enforcementMode

docs/book/src/user-guide/concepts.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ A rule specifies:
1313

1414
When a rule is created, the controller continuously watches all matching nodes. If a node does not satisfy the required conditions, the controller ensures the configured taint is present, preventing the scheduler from assigning new pods to that node.
1515

16+
### Readiness Domain and Taint Keys
17+
18+
Node Readiness Controller uses the `readiness.k8s.io` domain for taints and annotations that it owns. All `spec.taint.key` values in `NodeReadinessRule` must start with the `readiness.k8s.io/` prefix; this is enforced by the CRD schema.
19+
20+
Typical taint keys look like:
21+
- `readiness.k8s.io/cni.example.net/network-not-ready`
22+
- `readiness.k8s.io/csi.vendor.com/storage-driver-not-ready`
23+
- `readiness.k8s.io/<dns.subdomain>/<component-name>`
24+
25+
The segment after `readiness.k8s.io/` should describe the dependency or subsystem whose readiness is being guarded (for example, a CNI plugin, storage backend, or security agent). Treat this domain as reserved for the controller and closely related components, and avoid reusing it for unrelated taints.
26+
1627
## Enforcement Modes
1728

1829
The controller supports two distinct modes of enforcement, configured via `spec.enforcementMode`, to handle different operational needs.

internal/controller/node_controller.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,7 @@ func (r *RuleReadinessController) hasTaintBySpec(node *corev1.Node, taintSpec co
244244
// addTaintBySpec adds a taint to a node.
245245
func (r *RuleReadinessController) addTaintBySpec(ctx context.Context, node *corev1.Node, taintSpec corev1.Taint) error {
246246
patch := client.StrategicMergeFrom(node.DeepCopy())
247-
node.Spec.Taints = append(node.Spec.Taints, corev1.Taint{
248-
Key: taintSpec.Key,
249-
Value: taintSpec.Value,
250-
Effect: taintSpec.Effect,
251-
})
247+
node.Spec.Taints = append(node.Spec.Taints, taintSpec)
252248
return r.Patch(ctx, node, patch)
253249
}
254250

internal/controller/node_controller_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ var _ = Describe("Node Controller", func() {
3636
const (
3737
nodeName = "node-controller-test-node"
3838
ruleName = "node-controller-test-rule"
39-
taintKey = "test-taint"
39+
taintKey = "readiness.k8s.io/test-taint"
4040
conditionType = "TestCondition"
4141
)
4242

@@ -66,19 +66,19 @@ var _ = Describe("Node Controller", func() {
6666

6767
It("should correctly compare node taints", func() {
6868
taint1 := []corev1.Taint{
69-
{Key: "key1", Effect: corev1.TaintEffectNoSchedule, Value: "value1"},
70-
{Key: "key2", Effect: corev1.TaintEffectNoExecute, Value: "value2"},
69+
{Key: "readiness.k8s.io/key1", Effect: corev1.TaintEffectNoSchedule, Value: "value1"},
70+
{Key: "readiness.k8s.io/key2", Effect: corev1.TaintEffectNoExecute, Value: "value2"},
7171
}
7272
taint2 := []corev1.Taint{
73-
{Key: "key1", Effect: corev1.TaintEffectNoSchedule, Value: "value1"},
74-
{Key: "key2", Effect: corev1.TaintEffectNoExecute, Value: "value2"},
73+
{Key: "readiness.k8s.io/key1", Effect: corev1.TaintEffectNoSchedule, Value: "value1"},
74+
{Key: "readiness.k8s.io/key2", Effect: corev1.TaintEffectNoExecute, Value: "value2"},
7575
}
7676
taint3 := []corev1.Taint{
77-
{Key: "key1", Effect: corev1.TaintEffectNoSchedule, Value: "different"},
78-
{Key: "key2", Effect: corev1.TaintEffectNoExecute, Value: "value2"},
77+
{Key: "readiness.k8s.io/key1", Effect: corev1.TaintEffectNoSchedule, Value: "different"},
78+
{Key: "readiness.k8s.io/key2", Effect: corev1.TaintEffectNoExecute, Value: "value2"},
7979
}
8080
taint4 := []corev1.Taint{
81-
{Key: "key1", Effect: corev1.TaintEffectNoSchedule, Value: "value1"},
81+
{Key: "readiness.k8s.io/key1", Effect: corev1.TaintEffectNoSchedule, Value: "value1"},
8282
}
8383

8484
Expect(taintsEqual(taint1, taint2)).To(BeTrue(), "identical taints should be equal")
@@ -547,7 +547,7 @@ var _ = Describe("Node Controller", func() {
547547
},
548548
Spec: corev1.NodeSpec{
549549
Taints: []corev1.Taint{
550-
{Key: "status-test-taint", Effect: corev1.TaintEffectNoSchedule, Value: "pending"},
550+
{Key: "readiness.k8s.io/status-test-taint", Effect: corev1.TaintEffectNoSchedule, Value: "pending"},
551551
},
552552
},
553553
Status: corev1.NodeStatus{
@@ -566,7 +566,7 @@ var _ = Describe("Node Controller", func() {
566566
{Type: "StatusTestCondition", RequiredStatus: corev1.ConditionTrue},
567567
},
568568
Taint: corev1.Taint{
569-
Key: "status-test-taint",
569+
Key: "readiness.k8s.io/status-test-taint",
570570
Effect: corev1.TaintEffectNoSchedule,
571571
Value: "pending",
572572
},

internal/controller/nodereadinessrule_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ func (r *RuleReadinessController) processDryRun(ctx context.Context, rule *readi
563563
}
564564

565565
// Update rule status with dry run results
566+
rule.Status.ObservedGeneration = rule.Generation
566567
rule.Status.DryRunResults = readinessv1alpha1.DryRunResults{
567568
AffectedNodes: &affectedNodes,
568569
TaintsToAdd: &taintsToAdd,

0 commit comments

Comments
 (0)