ловить скачки стоимости namespace и безопасно автоматизировать реакцию

Обнаружение аномалий стоимости в Kubernetes: оповещения и автоматическое исправление с OpenCost

14 минут

Ежемесячный счёт приходит, когда перерасход уже случился. OpenCost отдаёт метрики в Prometheus, скользящие базовые линии ловят скачки по namespace, а Alertmanager при критическом превышении бюджета запускает Argo Workflows — после того как команды две–четыре недели смотрели дашборды.

Почему ежемесячный биллинг не ловит скачки в Kubernetes

Расходы в Kubernetes растут незаметно, пока финансы не откроют счёт. Отложенные отчёты не успевают за нагрузками, которые днём масштабируются, а ночью сжимаются. К моменту скачка в выписке деньги уже потрачены. Типичные сценарии: один namespace разворачивает неограниченные реплики и забирает большую часть кластера; завышенные requests резервируют CPU и память, которые не используются; HPA или KEDA наращивают узлы без сигнала в долларах; эфемерные preview-окружения не удаляются. Оповещения только по CPU или памяти не отражают бизнес-сторону: узел на девяноста процентах CPU — это эксплуатация, namespace в три раза дороже бюджета — это FinOps. Kubecost и его ядро OpenCost в CNCF используют одну модель аллокации: OpenCost — открытый путь к метрикам, Kubecost добавляет UI, API бюджетов и корпоративные интеграции. Здесь — OpenCost с Prometheus, Alertmanager и Argo Workflows.

Три слоя: метрики, обнаружение и исправление

Цепочка остаётся внутри кластера. OpenCost сопоставляет аллокацию контейнеров с почасовыми тарифами узлов и экспортирует метрики Prometheus — container_cpu_allocation, node_cpu_hourly_cost и аналоги для памяти. Recording rules Prometheus считают почасовую стоимость по namespace, скользящее среднее и стандартное отклонение. Alertmanager шлёт предупреждения в Slack, а критическое превышение бюджета — в сценарий исправления. Argo Workflows уменьшает реплики некритичных Deployment с нужными метками и уведомляет владельцев — только идемпотентные действия, без удаления namespace. Сначала покажите дашборды две–четыре недели, затем включайте автоматическое принуждение, чтобы команды доверяли цифрам.

Развёртывание OpenCost и сбор метрик в Prometheus

Установите OpenCost в отдельный namespace и настройте Prometheus на endpoint метрик на порту 9003. Задайте облачные тарифы или custom rates, чтобы node_cpu_hourly_cost отражал on-demand или reserved pricing. OpenCost читает kube-state-metrics и данные использования, уже попадающие в Prometheus; он их не заменяет. В мультикластере скрейпьте каждый экземпляр OpenCost и добавьте метку cluster в metric_relabel_configs. В Kubecost при включённом cost analyzer доступны совместимые метрики аллокации с теми же именами.

YAML · Flux HelmRelease для OpenCost
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: opencost
  namespace: opencost
spec:
  interval: 1h
  chart:
    spec:
      chart: opencost
      sourceRef:
        kind: HelmRepository
        name: opencost
        namespace: flux-system
  values:
    opencost:
      exporter:
        defaultClusterId: prod-us-east-1
      prometheus:
        external:
          enabled: true
          url: http://prometheus-kube-prometheus-prometheus.monitoring.svc:9090
YAML · scrape job Prometheus для OpenCost
scrape_configs:
  - job_name: opencost
    scrape_interval: 1m
    scrape_timeout: 15s
    metrics_path: /metrics
    static_configs:
      - targets:
          - opencost.opencost.svc.cluster.local:9003
    metric_relabel_configs:
      - target_label: cluster
        replacement: prod-us-east-1

Базовые линии и алерты на PromQL из OpenCost

Не опирайтесь на несуществующую серию kubecost_namespace_cost_daily — считайте почасовую стоимость namespace из аллокации и тарифов узлов. Запишите это значение, затем семидневное среднее и стандартное отклонение по каждому namespace. Алерт срабатывает, если текущая почасовая стоимость выше среднего плюс две сигмы в течение часа. Жёсткий дневной лимит сравнивайте через почасовую стоимость, умноженную на двадцать четыре. Метрики аллокации отражают requests, а не фактическое использование — дополняйте алерты рекомендациями VPA по right-sizing. Для месячной отчётности используйте тридцатидневное окно на тех же recording rules.

YAML · PrometheusRule для базовых линий стоимости
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: cost-anomaly-detection
  namespace: monitoring
spec:
  groups:
    - name: cost.baseline
      interval: 5m
      rules:
        - record: namespace:cost_hourly_usd
          expr: |
            sum by (namespace) (
              container_cpu_allocation
                * on (node) group_left()
                  node_cpu_hourly_cost
              +
              container_memory_allocation_bytes
                * on (node) group_left()
                  node_ram_hourly_cost
                / 1024 / 1024 / 1024
            )
        - record: namespace:cost_hourly_avg_7d
          expr: avg_over_time(namespace:cost_hourly_usd[7d])
        - record: namespace:cost_hourly_stddev_7d
          expr: stddev_over_time(namespace:cost_hourly_usd[7d])
    - name: cost.anomaly_alerts
      rules:
        - alert: NamespaceCostAnomalyHigh
          expr: |
            namespace:cost_hourly_usd
            > (namespace:cost_hourly_avg_7d + 2 * namespace:cost_hourly_stddev_7d)
          for: 1h
          labels:
            severity: warning
          annotations:
            summary: "Аномалия стоимости в namespace {{ $labels.namespace }}"
            description: >
              Почасовая стоимость ${{ $value | humanize }} превышает
              семидневную базу более чем на две сигмы.
        - alert: NamespaceBudgetExceeded
          expr: namespace:cost_hourly_usd * 24 > 10000
          for: 30m
          labels:
            severity: critical
          annotations:
            summary: "Превышен дневной бюджет в {{ $labels.namespace }}"
            description: >
              Прогнозируемые суточные расходы превышают лимит $10 000 для namespace.

Исправление через Argo Workflows при нарушении бюджета

Автоматическое уменьшение реплик оставьте для критических алертов, когда уже есть каналы уведомления людей. Пометьте Deployment меткой cost-tier=critical для баз и шлюзов, cost-tier=best-effort для пакетных задач и dev-инструментов. Workflow ставит best-effort Deployment на одну реплику — действие идемпотентно и обратимо после разбора бюджета. В продакшене Alertmanager обычно подключают через Argo Events Sensor; webhook на events API Argo Server здесь иллюстрирует передачу. Не удаляйте namespace и PVC из cost-автоматизации. Сохраняйте прежнее число реплик в журнале, если нужен ручной откат.

YAML · Argo Workflow для некритичных Deployment
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: cost-remediation-
  namespace: argo-workflows
spec:
  entrypoint: remediate
  arguments:
    parameters:
      - name: namespace
      - name: current_cost
  templates:
    - name: remediate
      dag:
        tasks:
          - name: scale-down-non-critical
            template: scale-deployments
          - name: notify-team
            template: send-notification
            dependencies: [scale-down-non-critical]
    - name: scale-deployments
      container:
        image: bitnami/kubectl:1.30
        command: [sh, -c]
        args:
          - |
            set -euo pipefail
            echo "Уменьшаем некритичные deployment в ${NAMESPACE}"
            kubectl scale deployment \
              -n "${NAMESPACE}" \
              -l 'cost-tier=best-effort' \
              --replicas=1
            echo "Исправление завершено для ${NAMESPACE}"
        env:
          - name: NAMESPACE
            value: "{{workflow.parameters.namespace}}"
    - name: send-notification
      container:
        image: curlimages/curl:8.5.0
        command: [sh, -c]
        args:
          - |
            curl -fsS -X POST "${SLACK_WEBHOOK}" \
              -H 'Content-Type: application/json' \
              -d "{\"text\":\"Исправление по стоимости для ${NAMESPACE}: некритичные deployment уменьшены до 1 реплики. Почасовой сигнал: {{workflow.parameters.current_cost}}\"}"
        env:
          - name: NAMESPACE
            value: "{{workflow.parameters.namespace}}"
          - name: SLACK_WEBHOOK
            valueFrom:
              secretKeyRef:
                name: cost-alerts-slack
                key: webhook-url

Маршрутизация Alertmanager и предохранители ResourceQuota

NamespaceCostAnomalyHigh направляйте в Slack команды-владельца. NamespaceBudgetExceeded — в сценарий исправления и на дежурного FinOps платформы. continue: true оставляет дублирующее уведомление в Slack для критических алертов. ResourceQuota задаёт жёсткие потолки CPU, памяти и объектов — долларовый бюджет в нём хранить нельзя. Согласуйте лимиты квот с целевой стоимостью и дополняйте их dollar-алертами Prometheus. Kyverno может требовать метку cost-tier при admission, чтобы селекторы исправления были предсказуемыми.

YAML · AlertmanagerConfig для cost-маршрутов
apiVersion: monitoring.coreos.com/v1alpha1
kind: AlertmanagerConfig
metadata:
  name: cost-alerts
  namespace: monitoring
spec:
  route:
    receiver: slack-cost-team
    routes:
      - matchers:
          - name: alertname
            value: NamespaceBudgetExceeded
        receiver: argo-workflows
        continue: true
      - matchers:
          - name: alertname
            value: NamespaceCostAnomalyHigh
        receiver: slack-cost-team
  receivers:
    - name: argo-workflows
      webhookConfigs:
        - url: http://argo-events-eventsource-svc.argo-events.svc:12000/cost-remediation
          sendResolved: true
    - name: slack-cost-team
      slackConfigs:
        - apiURL:
            name: slack-webhook
            key: url
          channel: "#cost-alerts"
          title: '{{ .GroupLabels.alertname }}'
          text: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'
YAML · ResourceQuota под вычислительный бюджет
apiVersion: v1
kind: ResourceQuota
metadata:
  name: cost-guardrails
  namespace: team-backend
  labels:
    team: backend
    cost-center: "cc-2201"
spec:
  hard:
    requests.cpu: "40"
    requests.memory: 80Gi
    limits.cpu: "80"
    limits.memory: 160Gi
    pods: "100"
    services.loadbalancers: "2"

Практика: уровни порогов и обратимая автоматизация

Три уровня алертов: info при полутора сигмах — только аннотация на дашборде; warning при двух сигмах час — уведомление команде; critical при прогнозе суточных расходов выше лимита тридцать минут — уменьшение реплик и вызов дежурного. Обновляйте базы: семь дней для оперативных аномалий, тридцать — для месячного обзора. Атрибуируйте расходы метками team и cost-center на namespace и Deployment; OpenCost агрегирует по меткам для отчётности командам. Сопоставляйте стоимость на запрос с SLO по задержке — финансы и инженеры смотрят в один стек наблюдаемости. Рост стоимости от HPA разбирайте вместе с алертами на saturation. Обнаружение аномалий — операционная дисциплина: наблюдайте через дашборды OpenCost, оповещайте статистикой Prometheus, действуйте осторожной автоматизацией Argo — и подгоняйте пороги под допустимый риск.

Жёсткие лимиты namespace и метки cost-center дополняют обнаружение аномалий из гайда по FinOps на уровне namespace с ResourceQuota и атрибуцией.

Цикл «наблюдение → оповещение → действие» вписывается в фазы FinOps из гайда по FinOps для облачно-нативных платформ.