Цели проекта
Видимость расходов — получать расчёт стоимости Kubernetes-кластера (ноды, поды, CPU/RAM/диск) в рублях по ценам Yandex Cloud.
Просмотр в мониторинге — видеть cost-метрики OpenCost в Grafana вместе с остальными метриками кластера.
Распределение по командам (FinOps) — группировать расходы по лейблу
team.Сверка с биллингом — при необходимости сравнивать аллокации OpenCost с фактическими списаниями Yandex Cloud (Billing API, детализация).
Доступ к cost-данным — использовать Allocation API и метрики не только в UI/Grafana, но и через MCP для автоматизации и AI-ассистентов.
Уточнение
Уточнение. Яндекс считает не 730 часов в месяц, а 720. Поэтому цифры нужно пересчитать и статью обновить.
Что такое OpenCost
OpenCost — open-source проект для расчёта и визуализации стоимости ресурсов в Kubernetes. Он агрегирует данные об использовании CPU, памяти и диска (ноды, поды, PVC), применяет к ним тарифы и даёт метрики и API для отображения затрат по namespace, deployment, label и т.д. Изначально создан в Kubecost, затем выделен в отдельный проект под CNCF; поддерживает кастомные цены и интеграцию с разными облаками и он-прем-кластерами.
Зачем OpenCost нужен Prometheus-совместимый TSDB
OpenCost сам не собирает и не хранит метрики. Для расчёта стоимости ему нужна внешняя база временных рядов с Prometheus API, из которой он читает:
node-exporter — использование CPU, памяти, ди��ка на нодах;
kube-state-metrics — запросы/лимиты подов, PVC, состояние нод.
На основе этих метрик OpenCost строит cost-модель и экспортирует свои метрики. Подробнее далее по тексту.
Использование Prometheus Operator CRD
Используем Prometheus Operator CRD, чтобы не писать конфигурации скрейпинга (scrape) для vmagent вручную. CRD нужен для ServiceMonitor OpenCost: чарт OpenCost создаёт ServiceMonitor в namespace opencost, и vmagent (VictoriaMetrics) по нему автоматически скрейпит метрики OpenCost.
helm upgrade --install prometheus-operator-crds oci://ghcr.io/prometheus-community/charts/prometheus-operator-crds --namespace kube-system --wait --version 27.0.0
После появления CRD в кластере чарт OpenCost сможет создать ServiceMonitor.
Установка VictoriaMetrics Stack
helm upgrade --install --wait --timeout 10m \ vmks oci://ghcr.io/victoriametrics/helm-charts/victoria-metrics-k8s-stack \ --namespace vmks --create-namespace \ --version 0.72.4 \ --values vmks-values.yaml
Ссылка на исходный код: vmks-values.yaml.
Пароль admin Grafana
Grafana входит в VictoriaMetrics Stack. Логин по умолчанию: admin. Пароль хранится в секрете Kubernetes:
kubectl get secret vmks-grafana -n vmks -o jsonpath="{.data.admin-password}" | base64 -d; echo
Grafana доступна по адресу http://grafana.apatsev.org.ru.
Настройка цен для OpenCost
Billing API Яндекс Облака
Тарифы для OpenCost можно получать через Billing API Яндекса: метод Sku.List возвращает каталог SKU (тарифных единиц — цены за vCPU, RAM, диск и т.д.).
Скрипт scripts/fetch_yandex_sku_prices.py может вывести список всех SKU из каталога. Его можно запустить по прямой ссылке из интернета:
export IAM_TOKEN=$(yc iam create-token) # Вывести список всех SKU из каталога: python3 scripts/fetch_yandex_sku_prices.py --list-skus # Сохранение списка в markdown-файл с таблицей (при локальном запуске): python3 scripts/fetch_yandex_sku_prices.py --list-skus --output skus.md
В файле skus.md — markdown-таблица с колонками: id, serviceId, pricingUnit, price, name.
Автоматическое создание custom-pricing-configmap.yaml
Скрипт подставляет в ConfigMap цены для CPU, RAM, storage (из Billing API), а также egress (internetNetworkEgress) и Load Balancer (почасовая ставка). При необходимости можно подставить цены из детализации биллинга (CSV): для LB в CSV часто есть ненулевая стоимость, для egress в CSV обычно 0 — тогда берётся тариф из API.
# Показать подобранные цены (рубли), не менять файлы: python3 scripts/fetch_yandex_sku_prices.py # Обновить custom-pricing-configmap.yaml (только API): python3 scripts/fetch_yandex_sku_prices.py --update custom-pricing-configmap.yaml # Подставить цены из детализации CSV (где cost > 0): LB почасовая и, при наличии, egress; скрипт дополняет/перезаписывает значения: python3 scripts/fetch_yandex_sku_prices.py --update custom-pricing-configmap.yaml --csv 20260312-20260331.csv
Egress (zone/region/internet): в ConfigMap задаются ключи zoneNetworkEgress, regionNetworkEgress, internetNetworkEgress (₽/GiB). В Yandex Cloud платным обычно бывает только исходящий трафик → заполняют internetNetworkEgress (из API или skus.md); zone/region можно оставить "0".
Load Balancer: ключ loadBalancer — почасовая ставка (₽/час), соответствует «Сетевой балансировщик нагрузки�� в детализации. Стоимость входящего трафика через LB (LBIngressDataCost, ₽/GiB) и правила маршрутизации в custom pricing OpenCost отдельными ключами не задаются; при запуске с --csv скрипт выводит рассчитанную по CSV цену за ГБ входящего трафика для справки.
Как заполнить custom-pricing-configmap.yaml вручную по данным файла skus.md
Шаг 1 — какие поля ConfigMap заполнять
В custom-pricing-configmap.yaml в блоке data: задаются:
Ключ ConfigMap | Описание | Единица в биллинге | Пример из детализации |
|---|---|---|---|
| Цена за vCPU (месячная ставка) | core×час (core*hour) | Вычислительные ресурсы обычной BM, Intel Ice Lake, 100% vCPU |
| Цена за ГБ RAM (месячная ставка) | ГБ×час (gbyte*hour) | Вычислительные ресурсы обычной BM, Intel Ice Lake, RAM |
| Цена за ГБ диска (месячная ставка) | ГБ×час (gbyte*hour) | Стандартный диск (HDD) |
| Стоимость egress в зоне (₽/GiB) | — | Обычно 0 |
| Стоимость egress в регионе (₽/GiB) | — | Обычно 0 |
| Стоимость исходящего трафика в интернет (₽/GiB) | gbyte | Исходящий трафик (VPC) |
| Почасовая стоимость LB (₽/час) | hour | Network Load Balancer. Сетевой балансировщик нагрузки |
Шаг 2 — найти цену за единицу в skus.md
В файле skus.md (таблица) нужно найти строку, где name совпадает с Продуктом из Яндекс UI, а pricingUnit — с Ед. потребления (в skus.md единицы вроде core_hour, gbyte_hour).
Примеры соответствия:
CPU — продукт «Вычислительные ресурсы обычной BM, Intel Ice Lake, 100% vCPU», ед. потребления
core*hour. В skus.md ищем, например:Regular VM computing resources, Intel Ice Lake, 100% vCPUиcore_hour. Цена за 1 core×час (руб.) × 730 = значение дляCPU.RAM — продукт «Вычислительные ресурсы обычной BM, Intel Ice Lake, RAM», ед.
gbyte*hour. В skus.md:Regular VM computing resources, Intel Ice Lake, RAMиgbyte_hour. Цена за 1 ГБ×час × 730 = значение дляRAM.storage (HDD) — продукт «Стандартный диск (HDD)», ед.
gbyte*hour. В skus.md ищемStandard disk drive (HDD)или аналог сgbyte_hour; формула: цена за 1 ГБ×час × 730 = значение дляstorage.
Шаг 3 — формула для значений
OpenCost интерпретирует значения в ConfigMap как месячные ставки и сам делит на 730. Поэтому:
значение в ConfigMap = цена_за_единицу_из_skus_руб × 730
Пример: в skus.md для Regular VM Intel Ice Lake 100% vCPU указано 1.1529 RUB за core_hour. Тогда CPU = "841.617" (1.1529 × 730). Аналогично для RAM (0.3074 ₽/ГБ×час → 224.402) и storage (0.0045 ₽/ГБ×час для HDD → 3.285).
Проверка: в детализации «Стоимость потребления» за период = (объём потребления в единицах) × (цена за единицу). Цена за единицу можно проверить: стоимость потребления / объём (например, 55.34 ₽ / 48 core×час ≈ 1.1529 ₽/core×час).
При сверке с экспортированным CSV файлом детализации биллинга: CPU и RAM совпали с фактическими ставками (стоимость потребления / объём). Для storage (Стандартный диск HDD, sku dn2al287u6jr3a710u8g) наблюдается небольшое расхождение: в каталоге skus.md указано 0.0045 ₽/ГБ×час, по факту из CSV выходит около 0.00445 ₽/ГБ×час (в пределах округления). Для расчётов можно использовать либо значение из skus.md, либо уточнённое по детализации.
Как обновить custom-pricing-configmap.yaml
Вручную: откройте
custom-pricing-configmap.yaml, в блокеdata:замените значения ключейCPU,RAM,storageпо формуле цена_из_skus × 730 (округлить до 3 знаков после запятой). Пример для цен из таблицы выше:CPU: "841.617"(1.1529 × 730)RAM: "224.402"(0.3074 × 730)storage: "3.285"для HDD (0.0045 × 730)
Egress и LB:
internetNetworkEgress— цена за 1 ГБ исходящего трафика (из skus.md или API; в детализации CSV часто cost=0).loadBalancer— почасовая ставка NLB: в CSV по продукту «Сетевой балансировщик нагрузк��» при cost>0 можно взять стоимость / объём (часы) (например, 20.55456 / 24 ≈ 0.856 ₽/час).
Для egress и loadBalancer значения задаются в тех же единицах, что в биллинге: ₽/GiB и ₽/час (не умножать на 730). Остальные позиции детализации (публичный IP, Cloud DNS, мастер Kubernetes и т.д.) в custom pricing OpenCost отдельными ключами не задаются.
Установка OpenCost
Создайте namespace и примените ConfigMap с кастомными ценами до установки OpenCost. В issue #240 описано, что данные в ConfigMap должны быть в виде плоских ключей в
data:, иначе OpenCost их не прочитает:
kubectl create namespace opencost --dry-run=client -o yaml | kubectl apply -f - kubectl apply -f custom-pricing-configmap.yaml
Установите OpenCost из OCI-репозитория:
helm upgrade --install --wait \ opencost oci://ghcr.io/opencost/charts/opencost \ --namespace opencost \ --version 2.5.10 \ --values opencost-values.yaml
Ссылка на исходный код: opencost-values.yaml.
После установки OpenCost будет доступен по адресу http://opencost.apatsev.org.ru. Перед использованием подождите около 10 минут — за это время OpenCost соберёт необходимые метрики из системы.
Примечание (валюта в UI): по умолчанию веб-интерфейс показывает USD. Переключить на RUB можно в самом UI, но при новом заходе снова будет USD — выбор не сохраняется. Валюта в UI не берётся из ConfigMap (ConfigMap задаёт только расчёты бэкенда); в текущих версиях OpenCost задать RUB по умолчанию через конфиг или Ingress нельзя. Чтобы сразу открывать в рублях, используйте ссылку с параметром: http://opencost.apatsev.org.ru?currency=RUB.
Стоимость по командам (team cost)
OpenCost позволяет группировать расходы по командам, даже если у команды несколько namespace. Для этого используется агрегация по Kubernetes-лейблу через параметр aggregate=label:<имя_лейбла>.
Настройка
Лейбл, по которому выполняется группировка, в этом проекте (team), должен быть установлен на самом Pod. Установка лейбла только на объект Deployment / StatefulSet / DaemonSet (в их metadata.labels) или только на Namespace не позволяет группировать расходы по командам — OpenCost учитывает только лейблы на подах.
В этом проекте уже настроены лейблы для группировки инфраструктурных затрат:
Victoria Metrics K8s Stack (
vmks-values.yaml): поды vmsingle, vmagent, vmalert, alertmanager, grafana, node-exporter, kube-state-metrics и victoria-metrics-operator помеченыteam: metrics(черезspec.podMetadata.labelsдля CR-компонентов иpodLabelsдля подчартов).OpenCost (
opencost-values.yaml): под OpenCost помеченteam: finops.
Агрегация в UI
В веб-интерфейсе OpenCost есть выпадающий список «Aggregate by» (Namespace, Deployment, Pod и т.д.). Варианта «по лейблу team» в стандартной сборке может не быть — его можно открыть через URL, добавив параметр agg=label:team:
http://opencost.apatsev.org.ru/allocation?window=today&agg=label:team&acc=true

Так откроется отчёт за выбранный период (в примере URL — за сегодня), сгруппированный по командам (включая metrics и finops). Важно: в URL веб-интерфейса используется параметр agg, а в API и MCP для той же логики используется aggregate=label:team.
Скрейпинг метрик OpenCost
OpenCost не только читает метрики из VictoriaMetrics, но и отдаёт свои (node_cpu_hourly_cost, container_cpu_allocation и др.) на порту 9003 (/metrics). ServiceMonitor сам метрики не создаёт, а лишь описывает для vmagent, как их скрейпить. Эти метрики должны попадать в VictoriaMetrics; иначе в TSDB не будет cost-метрик, и Grafana/PromQL-запросы к ним будут возвращать No data.
Дублирование метрик kube_* и опции EMIT_KSM_V1_*
Эндпоинт OpenCost /metrics по умолчанию отдаёт те же метрики, что и kube-state-metrics (kube_pod_container_status_running, kube_pod_container_resource_requests, kube_node_status_* и др.). Если vmagent скрейпит и OpenCost, и kube-state-metrics, в VictoriaMetrics одни и те же ряды попадают из двух источников — запросы вида sum(kube_pod_container_status_running) дают удвоенные значения. Удвоенные значения вредны: искажают дашборды, агрегаты и любые расчёты, опирающиеся на эти метрики. Подробнее: opencost/opencost#1465.
В этом проекте предполагается, что kube-state-metrics всегда есть (он входит в VictoriaMetrics K8s Stack). Ниже — опции в opencost-values.yaml, которые отключают дубликаты.
Отключение дубликатов KSM в opencost-values.yaml
В opencost-values.yaml заданы:
Переменная | Значение | Смысл |
|---|---|---|
|
| Не отдавать полный набор KSM-метрик на |
|
| Отдаются метрики, которые не пересекаются по имени и не дублируются с kube-state-metrics. |
Дашборды Grafana для OpenCost
Ниже приведены ссылки на шесть дашбордов; в разделе Метрики, которые экспортирует OpenCost для каждого дашборда — скриншот и краткое описание.
Файл | GitHub | Описание |
|---|---|---|
opencost-overview.json | OpenCost Overview | |
opencost-namespace.json | OpenCost Namespace | |
opencost-cost-reporter-basic-overview.json | OpenCost Cost reporter — базовый обзор | |
opencost-cost-reporter-detailed-overview.json | OpenCost Cost reporter — детальный обзор | |
opencost-network-lb.json | OpenCost Network & Load Balancer | |
opencost-health.json | OpenCost Health |
Суть исправлений
Проблема: в дашбордах opencost-overview.json и opencost-namespace.json на панелях Hourly/Daily/Monthly Cost и PV возникало No data. Причина: метрика kube_persistentvolume_capacity_bytes в VictoriaMetrics K8s Stack поставляется kube-state-metrics (job="kube-state-metrics"), а не OpenCost (job="opencost"). Переменная Job в дашбордах берётся из opencost_build_info, то есть по умолчанию opencost. В оригинальных запросах стояло kube_persistentvolume_capacity_bytes{job=~"$job"}, поэтому при Job = opencost результат был пустым.
Что сделано: во всех запросах, где используется kube_persistentvolume_capacity_bytes, в фильтре явно указан источник job="kube-state-metrics". Остальные метрики в этих дашбордах (стоимость нод, pv_hourly_cost и т.д.) по-прежнему фильтруются по job=~"$job" (opencost).
В opencost-namespace.json привязка PV к namespace делается через kube_persistentvolumeclaim_info. Эта метрика в стеке тоже приходит от kube-state-metrics; в текущей версии дашборда для неё оставлен фильтр job=~"$job". Если на панелях PV по namespace появляется No data, в запросах для kube_persistentvolumeclaim_info нужно подставить job="kube-state-metrics" вместо $job.
Дашборды без правок: в opencost-cost-reporter-basic-overview.json и opencost-cost-reporter-detailed-overview.json метрики PV (kube_persistentvolume_*) не используются — правка не нужна. В opencost-network-lb.json и opencost-health.json запросы идут только к метрикам OpenCost с job=~"$job" — тоже без изменений.
Переменную Job в дашборде оставляйте opencost.
Валюта в панелях
Дашборды по умолчанию показывают единицу currencyUSD. При кастомных ценах в рублях (RUB) числовые значения в метриках уже в рублях; подпись единицы в панели при необходимости можно изменить в настройках панели (Field → Unit).
Метрики, которые экспортирует OpenCost
Запросы за прошлые периоды (день, месяц) выполняются через PromQL по уже сохранённым данным в TSDB. Без Prometheus-совместимого хранилища OpenCost не из чего считать стоимость. В этом репозитории в качестве TSDB используется VictoriaMetrics (совместима с Prometheus API).
OpenCost отдаёт метрики на порту 9003 (/metrics). Ниже для каждого дашборда — скриншот и краткое описание. При кастомных ценах в рублях (currency: RUB) значения cost-метрик — в ₽/час (или ₽/GiB для egress), а не в USD.
OpenCost Overview

Обзор стоимости кластера: почасовая, дневная и месячная стоимость; разбивка по namespace и типу инстансов; стоимость нод, PV и Load Balancer; блоки GPU и Spot.
OpenCost Namespace

Детализация по namespace: почасовая/дневная/месячная стоимость, CPU и RAM по namespace, стоимость по ресурсам, подам и контейнерам, сводка по PV, аллокации GPU и PVC.
OpenCost Cost reporter — базовый обзор


Базовый отчёт: средняя дневная стоимость, почасовая стоимость кластера, оценка месячных расходов; топ-20 namespace, контейнеров и подов; относительная цена и стандартное отклонение.
OpenCost Cost reporter — детальный обзор


Детальный отчёт: топ по namespace и контейнерам, почасовая стоимость, текущая цена за месяц/день/час, стоимость по приложениям, средние и кластерные показатели, PVC.
OpenCost Network & Load Balancer

Сетевые расходы: egress по зоне, региону и в интернет (₽/GiB), NAT Gateway (egress/ingress), почасовая и месячная стоимость Load Balancer по namespace и сервисам. При использовании NLB за ingress-nginx OpenCost может не обнаруживать LB — метрика бывает пустой.
OpenCost Health

Метрики здоровья сервиса OpenCost: число HTTP-запросов по endpoint, методу и статусу, среднее время и размер ответа.
Примеры PromQL: месячная стоимость всех нод — sum(node_total_hourly_cost) * 730; стоимость CPU+RAM по namespace — см. документацию OpenCost.
Подключение OpenCost через MCP (для AI-ассистентов) описано в разделе Подключение MCP OpenCost ниже.
Биллинг Yandex Cloud и интеграция с OpenCost
Cloud Costs и External Costs в OpenCost
OpenCost помимо расчёта стоимости Kubernetes (Allocation API) предоставляет два дополнительных механизма:
Механизм | Появился | Что учитывает |
|---|---|---|
Cloud Costs | 1.108.0 | Фактические расходы из billing API облачного провайдера (AWS, GCP, Azure). Показывает реальные списания, а не расчёт по list price. |
External Costs | 1.110.0 | Затраты на сторонние сервисы вне облака: мониторинг (Datadog), SaaS (MongoDB Atlas), AI/API (OpenAI) и т.п. |
Yandex Cloud не входит в список поддерживаемых провайдеров Cloud Costs. External Costs не подходит, так как нас интересуют расходы на само облако, а не на сторонние сервисы.
Yandex Cloud Billing API: почему не поможет с детализацией
Billing API v1 (gRPC/REST) содержит три сервиса:
Сервис | Что даёт |
|---|---|
| Список аккаунтов, баланс, привязки |
| Каталог SKU и тарифы (list price) |
| Справочник сервисов |
Ни один из них не предоставляет фактического потребления или списаний. Нельзя запросить «сколько потрачено за период X на сервис Y» — API отдаёт только баланс, тарифы и список сервисов. Детализация расходов доступна только через экспорт в CSV.
Доступные способы получения фактических расходов Yandex Cloud
CSV-экспорт в Object Storage
Источник: Экспортировать расширенную детализацию.
Разовый экспорт — в консоли биллинга выгрузка за выбранный период в CSV.
Регулярный экспорт — в настройках биллинга задаётся бакет Object Storage, куда ежедневно выгружаются CSV (обновление раз в час). Поресурсная детализация включает
resource_id,sku_id, идентификаторы каталогов, лейблы.
Для программного доступа: настроить экспорт в бакет и читать CSV через S3-совместимый API.
Yandex Query
Если детализация уже выгружается в бакет, можно анализировать данные через Yandex Query: готовые запросы (топ ресурсов, расход по сервисам) и произвольный YQL. Результаты доступны через HTTP API.
Возможные пути интеграции с OpenCost
Путь | Тип в OpenCost | Описание |
|---|---|---|
Кастомный Cloud Costs провайдер | Cloud Costs ( | Go-код в ядро OpenCost, читающий CSV из Object Storage (S3-совместимый API). Аналог интеграций AWS (CUR из S3) и Azure (Cost Exports). Требует модификации ядра. |
OpenCost Plugin | External Costs ( | Плагин, читающий CSV из Object Storage или вызывающий Yandex Query HTTP API. Не требует модификации ядра OpenCost. Репозиторий: opencost-plugins. |
Итог: прямого API для получения списаний у Yandex Cloud нет — фактические расходы доступны только через CSV-экспорт в Object Storage. Наиболее реалистичный путь интеграции — OpenCost Plugin, читающий CSV-детализацию из Object Storage через S3-совместимый API.
Подключение MCP OpenCost
В OpenCost встроен MCP-сервер (Model Context Protocol). Он предоставляет инструменты для запроса данных о стоимости кластера: AI-ассистенты (например, Cursor) могут через MCP получать cost-метрики и отвечать на вопросы о расходах.
MCP-сервер по умолчанию включён в OpenCost и слушает порт 8081.
Возможности MCP
Через MCP доступны четыре инструмента:
Инструмент | Назначение |
|---|---|
get_allocation_costs | Стоимость по аллокациям (namespace, deployment, pod, container и др.). Агрегация, фильтры, учёт idle/LB, накопление по времени. |
get_asset_costs | Стоимость по активам (ноды, диски, load balancer и т.п.) за заданное окно времени. |
get_cloud_costs | Облачные расходы: по провайдеру, региону, сервису, аккаунту, категории. Агрегация и фильтрация. |
get_efficiency | Эффективность ресурсов: CPU/память (usage/request), рекомендации по rightsizing и оценка потенциальной экономии. Опционально: |
Общие параметры: window (временное окно, например "7d", "1d", "30m") — обязателен везде. Для аллокаций и облака: aggregate, filter. Для аллокаций: include_idle, share_idle, share_lb, step, resolution, accumulate.
Настройка
Добавьте сервер в настройки MCP (Cursor: настройки → MCP). Пример для доступа по вашему поддомену:
{ "mcpServers": { "opencost": { "type": "http", "url": "http://mcp-opencost.apatsev.org.ru" } } }
Документация: OpenCost MCP.
Подписывайтесь на ТГ: https://t.me/notes_devops_engineer
