Loading...
Усі статті
Kubernetes · 8 min read

Оптимізація витрат на Kubernetes: скоротіть рахунок за хмару на 40%

Системний підхід до зниження витрат у Kubernetes: right-sizing з VPA, консолідація Karpenter, spot-ворклоади, namespace quotas та showback з OpenCost.

Чому ваш кластер спалює гроші

Брудний секрет економіки Kubernetes у тому, що більшість кластерів працюють на реальній утилізації 15–25%, а оплачуються так, ніби вони в piku. За минулий рік ми аудитували десятки продакшн-кластерів у клієнтів, і патерн ідентичний: requests, виставлені у два-три рази більше, ніж под коли-небудь реально використовує, флот великих on-demand нод і рахунок, що зростає швидше за headcount. Типовий проєкт скорочує витрати на 35–45% у перший місяць, не змінюючи жодного рядка коду застосунку.

Ця стаття — playbook, який ми використовуємо. Вона впорядкована так, як ви маєте її виконувати — від змін, що дають найбільші виграші, до оптимізацій з довгого хвоста, які мають значення, коли ви вже зібрали низько висячі фрукти.

Вимірюйте, перш ніж різати

Ви не можете оптимізувати те, що не можете атрибутувати. Перш ніж торкатися жодного request, встановіть щось, що дає вам видимість вартості per-namespace і per-workload. У 2026 році дві опції, що мають значення:

  • OpenCost — CNCF-проєкт, що виріс з Kubecost. Open source, vendor neutral, інтегрується з Prometheus.
  • Kubecost — комерційна збірка тієї самої кодової бази з hosted-опціями, алертами та командними фічами.

Обидва розгортаються приблизно за годину через Helm:

helm repo add opencost https://opencost.github.io/opencost-helm-chart
helm upgrade --install opencost opencost/opencost \
  --namespace opencost --create-namespace \
  --set opencost.exporter.defaultClusterId=prod-eu-west-1 \
  --set opencost.prometheus.internal.enabled=true

Націльте його на ваш Prometheus, зачекайте 24 години на дані, а потім збудуйте Grafana-дашборд з вартістю per namespace, idle cost та ефективністю подів (actual / requested). Якщо namespace менше ніж на 30% ефективний — це мішень.

Крок 1: Right-Size requests та limits

Надмірно запитані CPU і memory — це найбільший драйвер вартості, який ми бачимо. Інженери копіпастять requests зі старого чарту, множать на safety factor "про всяк випадок" і відправляють. Кластер слухняно провіжить ноди, щоб задовольнити ці requests.

Використовуйте VPA у recommendation mode

Не вмикайте VPA у Auto mode на stateful-ворклоадах — він рестартуватиме поди, щоб їх переразмірити. Використовуйте Off mode, щоб отримати рекомендації без мутації:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: api-vpa
  namespace: prod
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
      - containerName: "*"
        minAllowed:
          cpu: 50m
          memory: 64Mi
        maxAllowed:
          cpu: 2
          memory: 4Gi

Через тиждень запитайте рекомендації VPA через kubectl get vpa api-vpa -o yaml і порівняйте з поточними requests. Дельта — це ваша економія.

Правила великого пальця

  • CPU requests мають націлюватись на p95 реального використання. CPU стискається — под throttlить, а не вбивається, коли перевищує requests.
  • Memory requests мають націлюватись на p99 або максимум, що спостерігається. Memory не стискається — досягнення limit означає OOMKill.
  • CPU limits зазвичай мають бути не встановленими. Виставлення CPU limits нижче за capacity ноди спричиняє throttling навіть коли хост простоює. Єдиний випадок їх виставити — для ворклоадів, які ви хочете обмежити з міркувань біллінгу чи справедливості.
  • Memory limits мають дорівнювати requests для передбачуваної поведінки. Це дає поду Guaranteed QoS class.

Под з розумним конфігом ресурсів

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    spec:
      containers:
        - name: api
          image: ghcr.io/example/api:1.12.0
          resources:
            requests:
              cpu: 150m
              memory: 256Mi
            limits:
              memory: 256Mi
          readinessProbe:
            httpGet: { path: /ready, port: 8080 }
          livenessProbe:
            httpGet: { path: /live, port: 8080 }

Крок 2: Замініть Cluster Autoscaler на Karpenter

Якщо ви на EKS і все ще використовуєте Cluster Autoscaler з pre-created node groups, ви залишаєте гроші на столі. Karpenter дивиться на pending поди, вибирає найдешевший інстанс-тип, що їм підходить, і провіжить його за ~30 секунд. Він також консолідує: коли нода стає недовикористаною, Karpenter переносить поди на меншу інстанцію і термінує стару.

Мінімальний NodePool із змішаними spot та on-demand, capacity-optimized allocation та увімкненою консолідацією:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general
spec:
  template:
    spec:
      requirements:
        - key: karpenter.k8s.aws/instance-family
          operator: In
          values: ["m7i", "m7a", "c7i", "r7i"]
        - key: karpenter.k8s.aws/instance-size
          operator: NotIn
          values: ["nano", "micro", "small"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      expireAfter: 720h
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 30s
  limits:
    cpu: "2000"
    memory: 4000Gi

Дві деталі, що мають значення. По-перше, дозволяючи і amd64, і arm64, ви дозволяєте Karpenter вибирати Graviton-ноди, коли ворклоад їх підтримує — Graviton зазвичай на 15–20% дешевший за vCPU. По-друге, WhenEmptyOrUnderutilized — це новий режим консолідації, що активно стискає кластер, а не лише коли нода повністю порожня.

Крок 3: Правильні ворклоади на Spot

Spot-інстанції на 60–90% дешевші за on-demand. Страх — це переривання: AWS дає вам two-minute notice і забирає ноду. Цей страх зазвичай перебільшений у 2026 році — рівень переривань на поширених родинах (m7i, c7i, r7i) часто сидить нижче 5% на місяць.

Що йде на Spot

Тип ворклоадуSpot-безпечний?Нотатки
Stateless HTTP APIТакЗапускайте достатньо реплік і ставте PDB
Batch jobsТакВикористовуйте checkpoints; рестарт на перериванні
CI runnersТакJob-level retry обробляє eviction
Dev/stagingТакОчевидно
Stateful бази данихНіВикористовуйте on-demand або керовані RDS/Aurora
Leader-elected контролериОбережноПереконайтесь, що кілька реплік розосереджені по зонах

Захищайте availability через PodDisruptionBudgets

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  minAvailable: 50%
  selector:
    matchLabels:
      app: api

Комбінуйте з topology spread, щоб ваші репліки не всі приземлились на ту саму spot-ноду:

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: ScheduleAnyway
    labelSelector:
      matchLabels:
        app: api

Крок 4: Автоскейліть ворклоади, а не лише ноди

Node autoscaling необхідний, але недостатній. Якщо Deployment має 20 реплік о 3 ночі, коли міг би мати 4, ви платите за idle поди. HPA на основі CPU — це дефолт, але справжня економія живе у custom metrics.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 30
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "200"
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 25
          periodSeconds: 60

Для event-driven ворклоадів (консюмери черг, Kafka-процесори) KEDA — це правильна відповідь. Він скейлить за зовнішніми метриками на кшталт SQS depth, Kafka lag або кількості рядків Postgres — включно зі scale-to-zero.

Крок 5: Namespace quotas та LimitRanges

Дайте кожній команді бюджет. Без квот одна команда, що поводиться не так, може з'їсти весь кластер і весь ваш рахунок.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-growth-quota
  namespace: team-growth
spec:
  hard:
    requests.cpu: "40"
    requests.memory: 80Gi
    limits.memory: 80Gi
    pods: "100"
---
apiVersion: v1
kind: LimitRange
metadata:
  name: defaults
  namespace: team-growth
spec:
  limits:
    - type: Container
      default:
        cpu: 200m
        memory: 256Mi
      defaultRequest:
        cpu: 100m
        memory: 128Mi
      max:
        cpu: "4"
        memory: 8Gi

LimitRange тут — тихий герой: він дає розумні дефолти будь-якому поду, який забув їх вказати, тож "необмежений" не прокрадається.

Крок 6: Showback та Chargeback

Останні 10% економії приходять з культурного тиску, а не з конфігурації. Щойно OpenCost тягне реальні числа, надсилайте кожній інженерній команді щотижневий лист про вартість. Не алерт, не дашборд — спокійне зведення: "ваш namespace коштував $4,213 минулого тижня, ось топ-5 найдорожчих ворклоадів". Команди оптимізують власний код, коли число видно і прив'язано до них.

Простий PromQL-запит для витягування вартості per-namespace:

sum by (namespace) (
  kubecost_namespace_monthly_cost{cluster="prod-eu-west-1"}
)

Підключіть це до запланованого Grafana-звіту або невеликого Python-скрипта і надсилайте кожного понеділка.

Реальне "до/після"

Ось редаговане зведення з недавнього клієнтського проєкту — Series B SaaS на EKS, що запускає близько 400 подів через кластери prod, staging та dev.

МетрикаДоПісляЗміна
Місячні витрати EKS$48,200$28,900-40%
Кількість нод (prod)3218-44%
Середня утилізація CPU14%46%+3.3x
Середня утилізація memory22%61%+2.8x
Spot ratio0%65%-

Увесь проєкт зайняв шість тижнів. Два з них — Karpenter rollout, два — right-sizing 40 найдорожчих ворклоадів, і два — налаштування політики квот і showback.

Короткий чекліст

Якщо ви цього кварталу не зробите нічого іншого:

  1. Встановіть OpenCost. Отримайте цифри per-namespace.
  2. Right-size топ-10 найдорожчих ворклоадів через рекомендації VPA.
  3. Розгорніть Karpenter з увімкненою консолідацією.
  4. Перенесіть stateless та batch ворклоади на spot.
  5. Встановіть ResourceQuotas та LimitRanges на кожен namespace.
  6. Налаштуйте щотижневі листи про вартість до team leads.

Наступні кроки

Оптимізація вартості — це не разовий проєкт — cluster drift реальний, і чистий кластер знову стає брудним приблизно за квартал, якщо ви його не підтримуєте чесним. Команди, що залишаються ефективними, ставляться до цього як до security: автоматизовані перевірки в CI, регулярні аудити, іменований owner. Якщо хочете допомоги з впровадженням цього playbook на вашому кластері, напишіть нам.

у категорії
kuberneteskubernetescloudcost-optimizationfinops
працювати з нами

Хочете, щоб наша команда допомогла з вашою інфраструктурою?

talk to an engineerFree 30-min discovery callBook
close