Disruption budgets з’явились в версії 0.36, і виглядає як дуже цікавий інструмент для того, аби обмежити Karpenter в перестворенні WorkerNodes.
Наприклад в моєму випадку ми не хочемо, аби EC2 вбивались в робочі часи по США, бо там у нас клієнти, а тому зараз маємо consolidationPolicy=whenEmpty
, аби запобігти “зайвому” видаленню серверів та Pods на них.
Натомість з Disruption budgets ми можемо налаштувати політики таким чином, що в один період часу будуть дозволені операції з WhenEmpty
, а в інший – WhenEmptyOrUnderutilized
.
Див. також Kubernetes: забезпечення High Availability для Pods – бо при використанні Karpenter навіть при налаштованих Disruption budgets необхідно мати відповідно налаштовані поди з Topology Spread та PodDisruptionBudget.
Зміст
Типи Karpenter Disruption
Документація – Automated Graceful Methods.
Спочатку глянемо, в яких випадках Disruption взагалі відбувається:
- Drift: виникає, коли є різниця між створеними конфігураціями NodePools або EC2NodeClass та існуючими WorkerNodes – тоді Karpenter почне перестворювати EC2 аби привести їх у відповідність до заданих параметрів
- Interruption: якщо Karpenter отримує AWS Event, що інстанс буде виключено, наприклад – якщо це Spot
- Consolidation: якщо маємо налаштування Consolidation на
WhenEmptyOrUnderutilized
абоWhenEmpty
, і Karpenter переносить наші Pods на інші WorkerNodes- у нас Karpenter 1.0, тому полісі
WhenEmptyOrUnderutilized
, для 0.37 цеWhenUnderutilized
- у нас Karpenter 1.0, тому полісі
Karpenter Disruption Budgets
За допомогою Disruption budgets ми можемо дуже гнучко налаштувати в який час і які операції Karpenter може проводити, і задати ліміт на те, скільки WorkerNodes одночасно будуть видалятись.
Документація – NodePool Disruption Budgets.
Формат конфігурації доволі простий:
budgets: - nodes: "20%" reasons: - "Empty" schedule: "@daily" duration: 10m
Тут ми задаємо:
- дозволити видалення WorkerNodes для 20% від загальної кількості
- для операції, коли Disruption викликаний умовою
WhenEmpty
- виконуємо це кожен день
- на протязі 10 хвилин
Параметри тут можуть мати значення:
nodes
: в процентах або просто кількості нодreasons
:Drifted
,Underutilized
абоEmpty
schedule
: розклад, за яким правило застосовується, в UTC (інші таймзони поки не підтримуються), див. Kubernetes Schedule syntaxduration
: і скільки часу правило діє, наприклад –1h15m
При цьому не обов’язково задавати всі параметри.
Наприклад, ми можемо описати два таких бюджети:
- nodes: "25%" - nodes: "10"
Тоді у нас постійно будуть працювати обидва правила, і перше обмежує кількість нод в 25% від загальної кількості, а друге – не більше як 10 інстансів – якщо у нас більш ніж 40 серверів.
Також, Budgets можна комбінувати, і якщо їх задано кілька – то ліміти будуть братись по найбільш суворому.
В першому прикладі ми застосовуємо правило на 20% нод і умові WhenEmpty
, а решту часу будуть працювати дефолтні правила disruption – тобто, 10% від загальної кількості серверів із заданою consolidationPolicy
.
Тому можемо записати правило так:
budgets: - nodes: "20%" reasons: - "Empty" schedule: "@daily" duration: 10m - nodes: 0
Тут останнє правило працює постійно, і буде таким собі запобіжником: ми забороняємо все, але дозоляємо виконувати disruption за політикою WhenEmpty
на протязі 10 хвилин раз на добу починаючи з 00:00 UTC.
Приклад Disruption Budgets
Повертаючись до моєї задачі:
- маємо Backend API в Kubernetes на окремому NodePool, а наші клієнти в основному з США, тому ми хочемо мінімізувати down-скейлінг WorkerNodes в робочий час по США
- для цього ми хочемо заблокувати всі операції по
WhenUnderutilized
в період робочого часу по Central Time USA- в
schedule
Karpenter використовує зону UTC, тому початок робочого дня по Central Time USA 9:00 – це 15:00 UTC
- в
- операції з
WhenEmpty
дозволимо в будь-який час, але тільки по 1 WorkerNode одночасно Drift
– аналогічно, бо коли я деплою зміни – то хочу побачити результат відразу
Фактично, нам потрібно задати два бюджети:
- по
Underutilized
– забороняємо все з понеділка по п’ятницю на протязі 9 годин починаючи з 15:00 по UTC - по
Empty
таDrifted
– дозволяємо в будь-який час, але тільки по 1 ноді, а не дефолтні 10%
Тоді наш NodePool буде виглядати так:
apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: backend1a spec: template: metadata: labels: created-by: karpenter component: devops spec: taints: - key: BackendOnly operator: Exists effect: NoSchedule nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: defaultv1a requirements: - key: karpenter.k8s.aws/instance-family operator: In values: ["c5"] - key: karpenter.k8s.aws/instance-size operator: In values: ["large", "xlarge"] - key: topology.kubernetes.io/zone operator: In values: ["us-east-1a"] - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] # total cluster limits limits: cpu: 1000 memory: 1000Gi disruption: consolidationPolicy: WhenEmptyOrUnderutilized consolidateAfter: 600s budgets: - nodes: "0" # block all reasons: - "Underutilized" # if reason == underutilized schedule: "0 15 * * mon-fri" # starting at 15:00 UTC during weekdays duration: 9h # during 9 hours - nodes: "1" # allow by 1 WorkerNode at a time reasons: - "Empty" - "Drifted"
Деплоїмо, перевіряємо NodePool:
$ kk describe nodepool backend1a Name: backend1a ... API Version: karpenter.sh/v1 Kind: NodePool ... Spec: Disruption: Budgets: Duration: 9h Nodes: 0 Reasons: Underutilized Schedule: 0 15 * * mon-fri Nodes: 1 Reasons: Empty Drifted Consolidate After: 600s Consolidation Policy: WhenEmptyOrUnderutilized ...
І в логах бачимо, що спрацював Disruption по WhenUnderutilized
:
karpenter-55b845dd4c-tlrdr:controller {"level":"INFO","time":"2024-09-16T10:48:26.777Z","logger":"controller","message":"disrupting nodeclaim(s) via delete, terminating 1 nodes (2 pods) ip-10-0-42-250.ec2.internal/t3.small/spot","commit":"62a726c","controller":"disruption","namespace":"","name":"","reconcileID":"db2233c3-c64b-41f2-a656-d6a5addeda8a","command-id":"1cd3a8d8-57e9-4107-a701-bd167ed23686","reason":"underutilized"} karpenter-55b845dd4c-tlrdr:controller {"level":"INFO","time":"2024-09-16T10:48:27.016Z","logger":"controller","message":"tainted node","commit":"62a726c","controller":"node.termination","controllerGroup":"","controllerKind":"Node","Node":{"name":"ip-10-0-42-250.ec2.internal"},"namespace":"","name":"ip-10-0-42-250.ec2.internal","reconcileID":"f0815e43-94fb-4546-9663-377441677028","taint.Key":"karpenter.sh/disrupted","taint.Value":"","taint.Effect":"NoSchedule"} karpenter-55b845dd4c-tlrdr:controller {"level":"INFO","time":"2024-09-16T10:50:35.212Z","logger":"controller","message":"deleted node","commit":"62a726c","controller":"node.termination","controllerGroup":"","controllerKind":"Node","Node":{"name":"ip-10-0-42-250.ec2.internal"},"namespace":"","name":"ip-10-0-42-250.ec2.internal","reconcileID":"208e5ff7-8371-442a-9c02-919e3525001b"}
Готово.