Skip to content

Commit ef99e32

Browse files
committed
Use capacity annotations to set labels and taints for cluster api node groups
1 parent 3b2e3db commit ef99e32

File tree

4 files changed

+100
-9
lines changed

4 files changed

+100
-9
lines changed

cluster-autoscaler/cloudprovider/clusterapi/README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,22 @@ rules:
230230

231231
#### Pre-defined labels and taints on nodes scaled from zero
232232

233-
The Cluster API provider currently does not support the addition of pre-defined
234-
labels and taints for node groups that are scaling from zero. This work is on-going
235-
and will be included in a future release once the API for specifying those
236-
labels and taints has been accepted by the community.
233+
To provide labels or taint information for scale from zero, the optional
234+
capacity annotations may be supplied as a comma separated list, as
235+
demonstrated in the example below:
236+
237+
```yaml
238+
apiVersion: cluster.x-k8s.io/v1alpha4
239+
kind: MachineDeployment
240+
metadata:
241+
annotations:
242+
cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: "5"
243+
cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size: "0"
244+
capacity.cluster-autoscaler.kubernetes.io/memory: "128G"
245+
capacity.cluster-autoscaler.kubernetes.io/cpu: "16"
246+
capacity.cluster-autoscaler.kubernetes.io/labels: "key1=value1,key2=value2"
247+
capacity.cluster-autoscaler.kubernetes.io/taints: "key1=value1:NoSchedule,key2=value2:NoExecute"
248+
```
237249

238250
## Specifying a Custom Resource Group
239251

cluster-autoscaler/cloudprovider/clusterapi/clusterapi_unstructured.go

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3131
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3232
"k8s.io/apimachinery/pkg/runtime/schema"
33+
"k8s.io/apimachinery/pkg/util/validation"
3334
klog "k8s.io/klog/v2"
3435
)
3536

@@ -169,15 +170,36 @@ func (r unstructuredScalableResource) MarkMachineForDeletion(machine *unstructur
169170
}
170171

171172
func (r unstructuredScalableResource) Labels() map[string]string {
172-
// TODO implement this once the community has decided how they will handle labels
173-
// this issue is related, https://github.com/kubernetes-sigs/cluster-api/issues/7006
174-
173+
annotations := r.unstructured.GetAnnotations()
174+
// annotation value of the form "key1=value1,key2=value2"
175+
if val, found := annotations[labelsKey]; found {
176+
labels := strings.Split(val, ",")
177+
kv := make(map[string]string, len(labels))
178+
for _, label := range labels {
179+
split := strings.SplitN(label, "=", 2)
180+
if len(split) == 2 {
181+
kv[split[0]] = split[1]
182+
}
183+
}
184+
return kv
185+
}
175186
return nil
176187
}
177188

178189
func (r unstructuredScalableResource) Taints() []apiv1.Taint {
179-
// TODO implement this once the community has decided how they will handle taints
180-
190+
annotations := r.unstructured.GetAnnotations()
191+
// annotation value the form of "key1=value1:condition,key2=value2:condition"
192+
if val, found := annotations[taintsKey]; found {
193+
taints := strings.Split(val, ",")
194+
ret := make([]apiv1.Taint, 0, len(taints))
195+
for _, taintStr := range taints {
196+
taint, err := parseTaint(taintStr)
197+
if err == nil {
198+
ret = append(ret, taint)
199+
}
200+
}
201+
return ret
202+
}
181203
return nil
182204
}
183205

@@ -347,3 +369,44 @@ func resourceCapacityFromInfrastructureObject(infraobj *unstructured.Unstructure
347369

348370
return capacity
349371
}
372+
373+
// adapted from https://github.com/kubernetes/kubernetes/blob/release-1.25/pkg/util/taints/taints.go#L39
374+
func parseTaint(st string) (apiv1.Taint, error) {
375+
var taint apiv1.Taint
376+
377+
var key string
378+
var value string
379+
var effect apiv1.TaintEffect
380+
381+
parts := strings.Split(st, ":")
382+
switch len(parts) {
383+
case 1:
384+
key = parts[0]
385+
case 2:
386+
effect = apiv1.TaintEffect(parts[1])
387+
388+
partsKV := strings.Split(parts[0], "=")
389+
if len(partsKV) > 2 {
390+
return taint, fmt.Errorf("invalid taint spec: %v", st)
391+
}
392+
key = partsKV[0]
393+
if len(partsKV) == 2 {
394+
value = partsKV[1]
395+
if errs := validation.IsValidLabelValue(value); len(errs) > 0 {
396+
return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; "))
397+
}
398+
}
399+
default:
400+
return taint, fmt.Errorf("invalid taint spec: %v", st)
401+
}
402+
403+
if errs := validation.IsQualifiedName(key); len(errs) > 0 {
404+
return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; "))
405+
}
406+
407+
taint.Key = key
408+
taint.Value = value
409+
taint.Effect = effect
410+
411+
return taint, nil
412+
}

cluster-autoscaler/cloudprovider/clusterapi/clusterapi_unstructured_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"testing"
2323
"time"
2424

25+
"github.com/stretchr/testify/assert"
26+
v1 "k8s.io/api/core/v1"
2527
"k8s.io/apimachinery/pkg/api/resource"
2628
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2729
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -286,11 +288,14 @@ func TestAnnotations(t *testing.T) {
286288
memQuantity := resource.MustParse("1024Mi")
287289
gpuQuantity := resource.MustParse("1")
288290
maxPodsQuantity := resource.MustParse("42")
291+
expectedTaints := []v1.Taint{{Key: "key1", Effect: v1.TaintEffectNoSchedule, Value: "value1"}, {Key: "key2", Effect: v1.TaintEffectNoExecute, Value: "value2"}}
289292
annotations := map[string]string{
290293
cpuKey: cpuQuantity.String(),
291294
memoryKey: memQuantity.String(),
292295
gpuCountKey: gpuQuantity.String(),
293296
maxPodsKey: maxPodsQuantity.String(),
297+
taintsKey: "key1=value1:NoSchedule,key2=value2:NoExecute",
298+
labelsKey: "key3=value3,key4=value4,key5=value5",
294299
}
295300

296301
test := func(t *testing.T, testConfig *testConfig, testResource *unstructured.Unstructured) {
@@ -325,6 +330,15 @@ func TestAnnotations(t *testing.T) {
325330
} else if maxPodsQuantity.Cmp(maxPods) != 0 {
326331
t.Errorf("expected %v, got %v", maxPodsQuantity, maxPods)
327332
}
333+
334+
taints := sr.Taints()
335+
assert.Equal(t, expectedTaints, taints)
336+
337+
labels := sr.Labels()
338+
assert.Len(t, labels, 3)
339+
assert.Equal(t, "value3", labels["key3"])
340+
assert.Equal(t, "value4", labels["key4"])
341+
assert.Equal(t, "value5", labels["key5"])
328342
}
329343

330344
t.Run("MachineSet", func(t *testing.T) {

cluster-autoscaler/cloudprovider/clusterapi/clusterapi_utils.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const (
3333
gpuTypeKey = "capacity.cluster-autoscaler.kubernetes.io/gpu-type"
3434
gpuCountKey = "capacity.cluster-autoscaler.kubernetes.io/gpu-count"
3535
maxPodsKey = "capacity.cluster-autoscaler.kubernetes.io/maxPods"
36+
taintsKey = "capacity.cluster-autoscaler.kubernetes.io/taints"
37+
labelsKey = "capacity.cluster-autoscaler.kubernetes.io/labels"
3638
)
3739

3840
var (

0 commit comments

Comments
 (0)