Kubernetes: моніторинг вартості кластеру – Kubernetes Resource Report та Kubecost

Автор |  23/01/2023

Дуже правильне діло – моніторити, наскільки ефективно використовується кластер, особливо, якщо ресурси деплояться розробниками, які не сильно вникають у requests, і встановлюють завищені значення “про запас”. Запас, звичайно, потрібен, але й просто так реквестити ресурси ідеї погана.

Наприклад, у вас є WorkerNode з 4 vCPU (4000 milicpu) та 16 ГБ оперативної пам’яті, і ви створюєте Kubernetes Deployment, у якому для подів задаєте CPU requests 2500m і 4 Гб пам’яті. Після запуску одного пода – він зареквестить більше половини доступного процесорного часу, і під час запуску другого поду Kubernetes повідомить про нестачу ресурсів на доступних нодах, що призведе до запуску ще одного WorkerNode, який, зрозуміло, відразиться на загальній вартості кластера.

Щоб уникнути цього, є кілька утиліт, таких як Kubernetes Resource Report та Kubecost.

Kube Resource Report

Kubernetes Resource Report – найпростіший у запуску та можливостях: просто виводить ресурси групуючи їх за типом, і відображає статистику – скільки CPU/MEM requested і скільки реально використовується.

Мені вона подобається саме простотою – просто запускаємо, раз в пару тижнів дивимося що відбувається в кластері, і за необхідності пінгуємо розробників із питанням “А вам насправді потрібно 100500 гіг пам’яті для цієї апки? »

Є Helm-чарт, але він оновлюється рідко, тому простіше встановити з маніфестів.

Створюємо Namespace:

[simterm]

$ kk create ns kube-resource-report
namespace/kube-resource-report created

[/simterm]

Завантажуємо репозиторій з kube-resource-report:

[simterm]

$ git clone https://codeberg.org/hjacobs/kube-resource-report
$ cd kube-resource-report/

[/simterm]

У каталозі  deploy вже є файл Kustomize – задамо в ньому деплой в наш неймспейс:

[simterm]

$ echo "namespace: kube-resource-report" >> deploy/kustomization.yaml

[/simterm]

Перевіряємо:

[simterm]

$ cat deploy/kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - rbac.yaml
  - service.yaml
  - configmap.yaml
namespace: kube-resource-report

[/simterm]

Встановлюємо:

[simterm]

$ kubectl apply -k deploy/
serviceaccount/kube-resource-report created
clusterrole.rbac.authorization.k8s.io/kube-resource-report created
clusterrolebinding.rbac.authorization.k8s.io/kube-resource-report created
configmap/kube-resource-report created
service/kube-resource-report created
deployment.apps/kube-resource-report created

[/simterm]

Відкриваємо собі доступ до Service:

[simterm]

$ kubectl -n kube-resource-report port-forward svc/kube-resource-report 8080:80

[/simterm]

Відкриваємо репорт в браузері:

Далі переходимо, наприклад, до Namespaces, сортуємо колонки за CR (CPU Requested):

При наведенні курсора на повзунок Kube Resource Report підкаже оптимальне з його точки зору значення.

Далі думаємо самі – чи дійсно потрібно стільки requests, чи його можна зменшити.

У цьому випадку у нас Apache Druid з 16 подами, в кожній працює JVM, яка і процесор і пам’ять любить, і для Druid процесор бажано виділяти по одному ядру на кожен thread виконання Java, тому ОК – нехай буде 14,65 процесора.

Kubecost

Kubecost – це Kube Resource Report на стероїдах. Вміє рахувати трафік, відправляти алерти, генерує метрики для Prometheus, має свої дашборди Grafana, може підключатися до кількох кластерів Kubernetes та багато іншого.

Місцями не без багів, але в цілому штука приємна.

Правда, вартість бізнес-ліцензії в 499 доларів трохи завищена, як на мене. Втім для базових речей цілком доступна Free версія.

“Під капотом” використовує свій Prometheus для зберігання даних. Можна відключити і використовувати зовнішній, але не рекомендується .

Installation

Основні доступні параметри описані в документації на Github , також можна переглянути дефолтні values його Helm-чарту.

Потребує реєстрації для отримання ключа – заходимо на https://www.kubecost.com/install.html , вказуємо пошту – і відразу переадресує на інструкцію з вашим ключем:

Helm chart values

Поспішати з установкою не будемо – спочатку створимо свої values.

Якщо ви вже маєте Kube Prometheus Stack, є Grafana і NodeExporter – то в Kubecost їх має сенс відключити. Крім того, відключимо kube-state-metrics, що б не дублювали дані в моніторингу.

Що б Prometheus із нашого власного стека почав збирати метрики Kubecost – налаштуємо створення ServiceMonitor і додамо йому labels – тоді можна буде генерувати свої алерти та використовувати Grafana dashboard.

А ось якщо відключити запуск вбудованої Grafana – под с kubecost-cost-analyzer не стартує. Не знаю – бага чи фіча. Але в ній є свої дашборды які можуть бути корисними, тож можна залишити.

Ще можна включити networkCosts, але мені так і не вдалося побачити адекватних витрат на трафік – можливо, неправильно налаштував.

networkCosts може бути достатньо прожорливим по ресурсам – треба моніторити використання ЦПУ.

Власне, сам values.yaml:

kubecostToken: "c2V***f98"

kubecostProductConfigs:
  clusterName: development-qa-data-services

prometheus:
  kube-state-metrics:
    disabled: true
  nodeExporter:
    enabled: false
  serviceAccounts:
    nodeExporter:
      create: false

serviceMonitor:
  enabled: true
  additionalLabels:
    release: prometheus

networkCosts:
  enabled: true
  podMonitor:
    enabled: true
  config:
    services:
      amazon-web-services: true

Встановлюємо в Namespace kubecost:

[simterm]

$ helm repo add kubecost https://kubecost.github.io/cost-analyzer/
$ helm upgrade --install -n kubecost --create-namespace -f values.yaml kubecost kubecost/cost-analyzer

[/simterm]

Відкриваємо порт:

[simterm]

$ kubectl -n kubecost port-forward svc/kubecost-cost-analyzer 9090:9090

[/simterm]

Перевіряємо поды – чи запустився kubecost-cost-analyzer:

[simterm]

$ kubectl -n kubecost get pod
NAME                                         READY   STATUS    RESTARTS   AGE
kubecost-cost-analyzer-5f5b85bf59-f22ld      2/2     Running   0          59s
kubecost-grafana-6bd995d6f9-kslh2            2/2     Running   0          63s
kubecost-network-costs-22dps                 1/1     Running   0          64s
kubecost-network-costs-m7rf5                 1/1     Running   0          64s
kubecost-network-costs-tcdvn                 1/1     Running   0          64s
kubecost-network-costs-xzvsz                 1/1     Running   0          64s
kubecost-prometheus-server-ddb597d5c-dvrgc   2/2     Running   0          6m49s

[/simterm]

Відкриваємо доступ до kubecost-cost-analyzer Service:

[simterm]

$ kubectl -n kubecost port-forward svc/kubecost-cost-analyzer 9090:9090

[/simterm]

І переходимо на http://localhost:9090.

Тут скрин з Kubecost, який вже майже тиждень запущено на одному з наших кластерів:

Коротко оглянемо основні пункти меню.

Assets

Для розуміння витрат краще почати з пункту Assets , де виводиться вартість “заліза”:

Бачимо, що в день наш кластер коштує 43 долари.

Можна “провалитися” глибше в деталі кластера, і побачити розбивку по ресурсам – WorkerNodes, лоад-балансерам, дискам та вартість самого AWS Elastic Kubernetes Service:

Переходимо ще далі, в Nodes:

І дивимось деталі вартості по конкретній ноді:

Перевіряємо:

0.167 за годину, як Kubecost і репортить в Hourly Rate.

Для налаштування витрат на AWS Spot Instances – див. Spot Instance data feed та AWS Spot Instances.

Cost Allocation

Відображає куди в самому Кубері витрачаються ресурси:

Kubecost враховує вартість CPU та RAM на WorkerNodes і, відповідно, виводить вартість кожного неймспейса залежно від його requests і usage.

Див. Pod resource efficiency.

Тут наш Apache Druid має реквестів CPU на цілих 48 долларів за тиждень або 4.08 в день.

Переходимо далі, і маємо картину по конкретним контролерам – StatefulSet, Deployment:

Колонки тут:

  • CPU, RAM: вартість використовуваних ресурсів в залежності від вартості ресурсів WorkerNode
  • PV: вартість PersistentVolume у вибраному контролері, тобто для StaefulSet MiddleManager маємо PV, котрий являє собою AWS EBS, за який ми платимо гроші
  • Network: треба перевіряти, бо якось дивно рахує – дуже мало
  • LB: LoadBalancers по вартості в AWS
  • Shared: загальні ресурси, які не будут рахуватися окремо, наприклад неймспейс kube-system, налаштовується в http://localhost:9090/settings > Shared Cost
  • Efficiency: утилізація vs реквесты за формулою:
    ((CPU Usage / CPU Requested) * CPU Cost) + (RAM Usage / RAM Requested) * RAM Cost) / (RAM Cost + CPU Cost))
    основний показник ефективності ресурсу, див. Efficiency and Idle

Якщо перейти ще глибше – буде посилання на Grafana, де можна переглянути використання ресурсів конкретним подом:

Правда, “з коробки” не відображаються метрики в RAM Requested.

Для перевірки метрик можна зайти на локальний Pometheus:

[simterm]

$ kubectl -n kubecost port-forward svc/kubecost-prometheus-server 9091:80

[/simterm]

І так – kube_pod_container_resource_requests_memory_bytes пуста:

Тому що метрика теперь називаєтся kube_pod_container_resource_requests з resource="memory", треба оновити запрос в цій Графані:

avg(kube_pod_container_resource_requests{namespace=~"$namespace", pod="$pod", container!="POD", resource="memory"}) by (container)

__idle__

Витрати __idle__ – різниця між вартістю ресурсів виділених під існуючі об’єкти (поди, деплойменти) – їх реквести і реальний usage, та “заліза, що простоює”, на якому вони працюють, тобто не зайняті ЦПУ/пам’ять, які можна використовувати під запуск нових ресурсів.

Savings

Тут зібрані поради щодо оптимізації витрат:

Наприклад, у “Right-size your container requests” зібрані рекомендації щодо налаштувань реквестів для ресурсів – аналог репортів у Kubernetes Resource Report:

Глянемо той же Apache Druid:

Тут явний овер-реквест по CPU, і Kubecost рекомендує зменшити ці реквести:

Але про Druid вже писалося вище – JVM, на кожен под MiddleManager ми запускаємо один Supervisor із двома Tasks, а під кожну Task бажано виділяти по повному ядру. Тож залишаємо, як є.

Корисна штука “Delete unassigned resources” – у нас, наприклад, знайшлася пачка EBS, що не використовуються:

Health

Теж корисна штука, що відображає основні проблеми із кластером:

kubecost-network-costs активно використовує CPU, вище своїх реквестів, і Kubernetes його тротлить.

Alerts

Тут можемо налаштувати алерти, але мені вдалося налаштувати відправку тільки через Slack Webhook:

Документація – Alerts.

Prometehus Alertmanager можна налаштувати через values ​​, але використовується свій локальний, який запускається разом з Prometheus, а як йому налаштувати роути – не знайшов.

Приклад алерту, який можна налаштувати в Kubecost:

global:
  notifications:
    alertConfigs:
      alerts:
        - type: budget
          threshold: 1
          window: 1d
          aggregation: namespace
          filter: druid
    alertmanager:
      enabled: true
      fqdn: http://prometheus-kube-prometheus-alertmanager.monitoring.svc

Тут додаємо алерт з типом budget, в якому перевіряємо вартість неймспейсу druid за останній день, і алертимо, якщо він стає дорожчим за 1 долар.

Оновлюємо сетап:

[simterm]

$ helm upgrade --install -n kubecost -f values.yaml kubecost kubecost/cost-analyzer

[/simterm]

Алерт з’являється у списку:

Але на кнопку Test не реагує, і в локальному Alertmanager алерт не з’являється.

Slack webhook

Спробуємо через Slack webhook, документація тут>>>.

Створюємо Application:

Переходимо в Webhooks:

Активуємо та натискаємо Add New Webhook:

Вибираємо канал:

Додаємо URL у Kubecost, і тестуємо:

Final values.yaml

Зрештою для тесту зібрав такий values:

kubecostToken: "c2V***f98"
kubecostProductConfigs:
  clusterName: development-qa-data-services
global:
  notifications:
    alertConfigs:
      globalSlackWebhookUrl: https://hooks.slack.com/services/T03***c1f
      alerts:
        - type: assetBudget
          threshold: 30
          window: 1d
          aggregation: type
          filter: 'Node'

        - type: assetBudget
          threshold: 4
          window: 1d
          aggregation: type
          filter: 'LoadBalancer'

        - type: assetBudget
          threshold: 3
          window: 1d
          aggregation: type
          filter: 'Disk'

        - type: assetBudget
          threshold: 40
          window: 3d
          aggregation: cluster
          filter: 'development-qa-data-services'

        - type: spendChange
          relativeThreshold: 0.01  # change relative to baseline average cost. Must be greater than -1 (can be negative).
          window: 1d
          baselineWindow: 7d       # previous window, offset by window
          aggregation: namespace
          filter: default, druid
        
        - type: spendChange
          relativeThreshold: 0.01
          window: 1d
          baselineWindow: 7d
          aggregation: cluster
          filter: 'development-qa-data-services'
        
        - type: health              # Alerts when health score changes by a threshold
          window: 10m
          threshold: 1
prometheus:
  kube-state-metrics:
    disabled: true
  nodeExporter:
    enabled: false
  serviceAccounts:
    nodeExporter:
      create: false

#serviceMonitor:
#  enabled: true
#  additionalLabels:
#    release: prometheus

networkCosts:
  enabled: true
  podMonitor:
    enabled: true
  config:
    destinations:
      direct-classification:
      - region: "us-west-2"
        zone: "us-west-2c"
        ips:
          - "10.0.64.0/19"
          - "10.0.160.0/20"
          - "10.0.208.0/21"
      - region: "us-west-2"
        zone: "us-west-2d"
        ips:
          - "10.0.216.0/21"
          - "10.0.96.0/19"
          - "10.0.176.0/20"
    services:
      amazon-web-services: true

Тут тестові алерти, які можна буде в принципі тягнути у продакшен.

ServiceMonitor для отримання метрик у зовнішньому Prometheus відключив, бо сенсу поки що не бачу – алертити буде через Slack Webhook своїми алертами, а дашборда для Grafana у вбудованій Графані краща, і їх там кілька.

Додав direct-classification для networkCosts – подивимося, можливо покаже більш правильні дані щодо трафіку.

#TODO

З чим поки що не вдалося розібратися:

  • алерти через Alertmanager (можливо, має сенс таки спробувати відключити внутрішній Prometheus)
  • Kubecost не бачить Node Exporter (перевіряти на сторінці http://localhost:9090/diagnostics ), але це наче ні на що не впливає – основні метрики отримує від cAdvisor
  • витрати на нетворкінг занадто маленькі (але це не точно)

Не робив/не тестував:

  • не налаштовував Cost Usage Reports для AWS , див. AWS Cloud Integration
  • не налаштовував AWS Spot Instances прайсинг
  • не додав Ingress, тому що у нас AWS ALB Controller, і треба робити авторизацію, а SAML в Kubecost доступний тільки в Premium

В цілому, на цьому все.

Система цікава та корисна, але є баги та складності, з якими треба розбиратися.