Чому оптимізація витрат Kubernetes — це критично у 2026 році
Ось чесна правда: понад 68% організацій у 2026 році перевитрачають на Kubernetes від 20% до 40% свого бюджету. І причина банально проста — розробники налаштовують ресурси «про запас», тестові середовища працюють 24/7, а аудит реального споживання ніхто не проводить. У результаті 80-90% pod-ів у типовому кластері мають завищені requests для CPU та пам'яті.
Це як платити за п'ятикімнатну квартиру, коли вам вистачило б двокімнатної.
Якщо ваша організація витрачає $10 000 на місяць на Kubernetes, цілком реально знизити цю суму до $4 000-6 000 — і без жодної втрати продуктивності. У цьому посібнику ми пройдемося по конкретних інструментах та YAML-конфігураціях: Vertical Pod Autoscaler (VPA) для right-sizing, Goldilocks для візуалізації рекомендацій, Karpenter для розумного автомасштабування вузлів та OpenCost для моніторингу витрат. Все з прикладами, які можна копіювати й одразу використовувати.
Крок 1: Розуміння requests і limits — фундамент усього
Перш ніж щось оптимізувати, варто розібратися, як Kubernetes взагалі розподіляє ресурси. Кожен контейнер у pod має два ключові параметри:
- Requests (запити) — мінімальний обсяг CPU та пам'яті, який планувальник резервує для контейнера. Саме на основі requests Kubernetes вирішує, на який вузол поставити pod.
- Limits (обмеження) — максимум ресурсів, який контейнер може використати. Перевищення ліміту CPU призведе до throttling, а перевищення ліміту пам'яті — до OOMKill (і це завжди неприємний сюрприз).
А ось типовий приклад завищених ресурсів, який я постійно зустрічаю в продакшн-кластерах:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: myapp:latest
resources:
requests:
cpu: "2000m" # Запит: 2 повних ядра
memory: "4Gi" # Запит: 4 ГБ пам'яті
limits:
cpu: "4000m"
memory: "8Gi"
Але коли дивишся на реальне споживання через Prometheus — pod використовує лише 200m CPU (10% від запиту) та 512Mi пам'яті (12.5%). Решта — чисте марнотратство, за яке ви справно платите кожного місяця.
Важливе правило: для CPU варто розглянути варіант без limits — це дозволить pod-ам використовувати вільні ресурси вузла без ризику throttling. А от для пам'яті limits ставте завжди, бо поведінка OOM без них непередбачувана і, чесно кажучи, зазвичай катастрофічна.
Крок 2: Right-sizing за допомогою VPA та Goldilocks
Встановлення Vertical Pod Autoscaler (VPA)
VPA аналізує реальне споживання ресурсів вашими pod-ами та генерує рекомендації щодо оптимальних значень requests і limits. Головне — почніть із режиму рекомендацій. Він не буде автоматично перезапускати pod-и, а просто покаже, які значення були б оптимальними.
# Встановлення metrics-server (якщо ще не встановлений)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# Встановлення VPA
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-up.sh
# Перевірка встановлення
kubectl get pods -n kube-system | grep vpa
Тепер створіть VPA-об'єкт для вашого deployment-у в режимі Off (тільки рекомендації, без автоматичних змін):
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-server-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Off" # Тільки рекомендації
resourcePolicy:
containerPolicies:
- containerName: api
minAllowed:
cpu: "50m"
memory: "128Mi"
maxAllowed:
cpu: "4000m"
memory: "8Gi"
Дайте йому 24-48 годин на збір метрик, а потім перевірте рекомендації:
kubectl get vpa api-server-vpa -n production -o yaml
VPA покаже три рівні рекомендацій для кожного контейнера:
- lowerBound — мінімум, нижче якого pod може зазнавати throttling
- target — рекомендоване значення для requests (та сама «золота середина»)
- upperBound — максимум, вище якого pod навряд чи знадобляться додаткові ресурси
Goldilocks: візуальний дашборд для right-sizing
Якщо створювати VPA-об'єкти вручну для кожного deployment-у здається нудним — Goldilocks від Fairwinds зробить це за вас. Просто позначте namespace, і він автоматично створить VPA для всіх deployment-ів та покаже рекомендації у зручному веб-інтерфейсі.
# Встановлення Goldilocks через Helm
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm repo update
helm install goldilocks fairwinds-stable/goldilocks \
--namespace goldilocks \
--create-namespace \
--set dashboard.enabled=true
# Перевірка pod-ів
kubectl get pods -n goldilocks
Увімкніть моніторинг для потрібного namespace:
# Позначте namespace для аналізу
kubectl label namespace production goldilocks.fairwinds.com/enabled=true
# Відкрийте дашборд
kubectl -n goldilocks port-forward svc/goldilocks-dashboard 8080:80
# Перейдіть на http://localhost:8080
Дашборд покаже для кожного контейнера поточні значення requests/limits поруч із рекомендаціями VPA. Дуже зручно — одразу видно, де найбільше перевитрат. Він також пропонує два варіанти QoS-класів:
- Guaranteed — requests дорівнюють limits (вищий пріоритет, але менша гнучкість)
- Burstable — requests нижчі за limits (pod може «вибухнути» і використати більше ресурсів за потреби)
Порада з досвіду: не поспішайте застосовувати рекомендації одразу. Зберіть дані мінімум за 2 тижні, а ще краще — за повний місяць, щоб врахувати всі паттерни навантаження, включаючи піки та batch-завдання. Я бачив випадки, коли рекомендації на основі тижневих даних не враховували місячні піки — і це закінчувалося аварійними OOM-подіями.
Крок 3: Розумне автомасштабування вузлів із Karpenter
Karpenter — це, по суті, наступне покоління автомасштабування вузлів для Kubernetes на AWS. На відміну від Cluster Autoscaler, який просто збільшує заздалегідь визначені NodeGroups, Karpenter динамічно підбирає оптимальний тип інстансу для кожного pod-у. Він враховує вартість, доступність і вимоги — і часто знаходить варіанти, про які ви навіть не думали.
Компанії, що мігрували з Cluster Autoscaler на Karpenter, повідомляють про скорочення витрат на обчислення приблизно на 30%. Непогано, правда?
Базова конфігурація NodePool
NodePool — це основний CRD Karpenter, що визначає правила створення вузлів. Ось конфігурація для production-кластера з підтримкою Spot-інстансів:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general-workloads
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: kubernetes.io/os
operator: In
values: ["linux"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["2"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
expireAfter: 720h # 30 днів
limits:
cpu: 1000
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 1m
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
role: "KarpenterNodeRole-my-cluster"
amiSelectorTerms:
- alias: "al2023@latest"
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: "my-cluster"
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: "my-cluster"
Що тут важливо розуміти:
- capacity-type: ["spot", "on-demand"] — Karpenter спершу спробує Spot-інстанси (знижка до 90%!), а якщо Spot недоступний — візьме On-Demand
- instance-category: ["c", "m", "r"] — дозволяє обирати з обчислювальних (c), загального призначення (m) та пам'яті-оптимізованих (r) інстансів
- consolidationPolicy: WhenEmptyOrUnderutilized — Karpenter сам замінить недовантажені вузли компактнішими. Це як автоматичний «прибиральник» для вашого кластера
NodePool для Dev/Staging — максимальна економія
Для непродакшн середовищ можна і потрібно бути агресивнішими:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: dev-pool
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"] # Тільки Spot для dev
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r", "t"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["2"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
expireAfter: 168h # 7 днів
limits:
cpu: 200
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 10s # Агресивна консолідація
budgets:
- nodes: "30%"
Окремий NodePool для критичних сервісів
Для баз даних, черг та інших stateful-сервісів потрібен окремий NodePool із політикою WhenEmpty — тут стабільність важливіша за економію:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: stable-pool
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"] # Тільки On-Demand
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["m", "r"]
taints:
- key: stable
value: "true"
effect: NoSchedule
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
disruption:
consolidationPolicy: WhenEmpty # Видаляти тільки порожні вузли
consolidateAfter: 5m
Щоб pod потрапив на стабільний вузол, додайте відповідний toleration:
tolerations:
- key: stable
operator: Equal
value: "true"
effect: NoSchedule
Крок 4: Моніторинг витрат із OpenCost
OpenCost — безкоштовний open-source проєкт CNCF для моніторингу витрат Kubernetes у реальному часі. Він покаже вартість кожного namespace, deployment, pod та навіть окремого контейнера. Без нього ви, по суті, оптимізуєте наосліп.
Встановлення OpenCost
# Встановлення через Helm
helm repo add opencost https://opencost.github.io/opencost-helm-chart
helm repo update
helm install opencost opencost/opencost \
--namespace opencost \
--create-namespace \
--set opencost.ui.enabled=true \
--set opencost.prometheus.internal.enabled=true
# Перевірка
kubectl get pods -n opencost
# Відкрити UI
kubectl -n opencost port-forward svc/opencost 9090:9090
# Перейдіть на http://localhost:9090
API-запити для автоматизації
OpenCost має REST API, який можна інтегрувати з вашими процесами. Ось приклад отримання витрат за namespace за останні 48 годин:
# Витрати за namespace
curl -s "http://localhost:9090/allocation/compute?window=48h&aggregate=namespace" \
| jq '.data[] | to_entries[] | {namespace: .key, totalCost: .value.totalCost}'
# Витрати за deployment у конкретному namespace
curl -s "http://localhost:9090/allocation/compute?window=7d&aggregate=deployment&filterNamespaces=production" \
| jq '.data[]'
Цікава новинка 2026 року: OpenCost тепер підтримує MCP-сервер (Model Context Protocol), що дозволяє AI-агентам напряму запитувати дані про витрати. Уявіть: ваш AI-асистент сам аналізує витрати і пропонує оптимізації. Ми живемо в цікавий час.
OpenCost vs Kubecost: що обрати
OpenCost — це ядро, на якому побудований Kubecost. Для більшості команд OpenCost цілком достатньо. Обирайте Kubecost Enterprise тільки якщо вам дійсно потрібні:
- Уніфіковані дашборди для кількох кластерів
- Врахування знижок (Reserved Instances, Spot, кредити)
- Прогнозування витрат та виявлення аномалій
- Інтеграція зі Slack, PagerDuty, Jira
Крок 5: Governance — запобігання марнотратству на рівні політик
Інструменти — це чудово, але без організаційних політик витрати неминуче повернуться до попередніх рівнів. Повірте, я це бачив не раз. Використовуйте Kyverno або OPA Gatekeeper для автоматичного дотримання правил.
Приклад: обов'язкові resource requests через Kyverno
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-requests
spec:
validationFailureAction: Enforce
rules:
- name: check-resource-requests
match:
any:
- resources:
kinds:
- Pod
validate:
message: "CPU та memory requests обов'язкові для всіх контейнерів"
pattern:
spec:
containers:
- resources:
requests:
memory: "?*"
cpu: "?*"
Приклад: обмеження максимальних requests
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: limit-resource-requests
spec:
validationFailureAction: Enforce
rules:
- name: limit-cpu-request
match:
any:
- resources:
kinds:
- Pod
validate:
message: "CPU request не може перевищувати 4 ядра без погодження з Platform-командою"
deny:
conditions:
any:
- key: "{{ request.object.spec.containers[].resources.requests.cpu }}"
operator: GreaterThan
value: "4000m"
Також не забудьте про ResourceQuotas на рівні namespace — це простий, але ефективний спосіб обмежити загальне споживання кожною командою:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-alpha-quota
namespace: team-alpha
spec:
hard:
requests.cpu: "20"
requests.memory: "40Gi"
limits.cpu: "40"
limits.memory: "80Gi"
pods: "100"
Покроковий план впровадження
Не намагайтеся впровадити все одразу — це вірний шлях до хаосу. Ось порядок, який працює на практиці:
- Тиждень 1-2: Встановіть OpenCost і зберіть базовий рівень витрат. Визначте, скільки ви витрачаєте зараз та які namespace найдорожчі. Без цієї базової лінії ви не зможете виміряти прогрес.
- Тиждень 3-4: Встановіть Goldilocks та VPA в режимі рекомендацій. Позначте всі production namespace для аналізу. Нехай збирають дані — не поспішайте.
- Тиждень 5-6: Застосуйте рекомендації right-sizing для найбільш перевитрачених deployment-ів. Починайте з dev/staging, а потім обережно переходьте до production.
- Тиждень 7-8: Впровадьте Karpenter замість Cluster Autoscaler. Знову ж таки — спочатку dev-кластер, налаштуйте Spot-інстанси та консолідацію, переконайтеся що все працює стабільно.
- Тиждень 9-10: Додайте Kyverno-політики для запобігання майбутньому перевитрачанню. Налаштуйте ResourceQuotas для кожної команди.
- Постійно: Щомісяця переглядайте дашборд OpenCost та рекомендації Goldilocks. Оптимізація витрат — це не проєкт із кінцевою датою, а безперервний процес.
FAQ — найпоширеніші запитання
Чи безпечно використовувати Spot-інстанси у production?
Так, але з правильним підходом. Використовуйте Spot для stateless-сервісів із кількома репліками, налаштуйте PodDisruptionBudgets та розподіліть pod-и по кількох Availability Zones. Karpenter автоматично обирає Spot-інстанси з найменшою ймовірністю переривання. А от для баз даних та інших критичних stateful-сервісів — тільки On-Demand через окремий NodePool. Тут краще не ризикувати.
Чи може VPA конфліктувати з HPA?
Може, і це поширена пастка. Якщо обидва налаштовані на однакові метрики (наприклад, CPU), VPA збільшує requests → відсоток утилізації падає → HPA масштабує вниз. Замкнене коло. Рішення: використовуйте VPA для пам'яті, а HPA — для CPU, або (ще краще) налаштуйте HPA на кастомні метрики — кількість запитів, довжину черги тощо.
Скільки часу потрібно для збору достатніх даних VPA?
Мінімум 2 тижні, а краще — повний місяць. Це дозволяє VPA врахувати робочі та вихідні дні, піки навантаження та batch-завдання. Для короткоживучих pod-ів (CI-ранери, batch-job-и) VPA, чесно кажучи, не дуже підходить — вони не генерують достатньо історії для якісних рекомендацій.
Яку реальну економію можна очікувати?
За даними 2026 року: right-sizing з VPA дає 30-50% скорочення витрат на перевитрачені ресурси. Karpenter із Spot-інстансами — ще 20-30% зверху. Очищення зомбі-ресурсів додасть 10-20%. Загалом організації досягають скорочення витрат на Kubernetes на 40-70%. Звучить неймовірно, але це реальні цифри — просто більшість кластерів настільки неоптимізовані, що навіть базові кроки дають величезний ефект.
Чи працює Karpenter тільки з AWS?
Станом на 2026 рік Karpenter є офіційним проєктом Kubernetes SIG Autoscaling, але production-ready реалізація є тільки для AWS (EKS). Якщо ви на Azure AKS чи GCP GKE — використовуйте вбудований Node Auto-provisioner або Cluster Autoscaler у поєднанні з VPA та Goldilocks для right-sizing. Результат буде не таким вражаючим, але все одно відчутним.