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

Автор: | 23/01/2023
 

Очень полезное дело – мониторить то, насколько эффективно используется кластер, особенно, если приложения деплоятся девелоперами, которые не сильно вникают в requests, и устанавливают завышенные значения “про запас”. Запас, конечно, нужен – но и просто так реквестить ресурсы идея плохая.

К примеру, у вас есть WorkerNode у которой 4 vCPU (4.000 milicpu) и 16 GB RAM, и создаёте Kubernetes Deployment, в котором для подов задаёте requests 2.5m и 4gb памяти. После запуска одного пода – он зареквестит более половины доступного процессорного времени, и для запуска второго под 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 подскажет оптимальное с его точки зрения значение.

Далее думаем сами, учитывая ваше приложение – действительно ли требуется только requested, или его можно уменьшить.

В данном случае у нас Apache Druid с 16 подами, в каждой крутится JVM, которая процессор и память любит, и процессора желательно выделять по 1 ядру на каждый поток выполнения Java, поэтому ОК – пусть будет 14.65 запрошенного процессора.

Kubecost

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

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

Правда, стоимость Business лицензии в 499 долларов немного завышена, как мне кажется. Впрочем, для базовых вещей вполне хватает Free версии.

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

Установка

Основные доступные опции описаны в документации на 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 день, и алертим, если он становится дороже 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
  • 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

В целом, на этом всё.

Система интересная и полезная, но есть баги и сложности, с которыми надо разбираться.