# Service discovery в Prometheus: k8s, Consul, file_sd, relabel _Observability и мониторинг · LinuxLab Knowledge Base_ **TL;DR:** Prom discoverит targets через k8s API, Consul, file_sd (static). relabel_configs - до scrape (filter+rewrite labels). metric_relabel - после scrape (drop bad metrics). Без relabel - cardinality из k8s взрывается. ## Зачем service discovery Static config работает на 5 хостах. На 500, нет. В Kubernetes endpoints меняются каждую секунду (rollouts, autoscaling). Должен быть способ автоматически узнавать кого scrape'ать. Решение, **service discovery (SD)**: Prometheus говорит «дай мне все pods/services с такими-то labels», SD-mechanism возвращает список endpoint'ов. Prom их scrape'ает. Поддерживается ~30 SD механизмов: kubernetes, consul, dns, ec2, azure, gce, file_sd, http_sd. Самые ходовые, k8s и consul. ## Discovery → relabel → scrape ``` ┌──────────────┐ │ SD mechanism │ возвращает targets c meta-labels │ (k8s, etc) │ __meta_kubernetes_pod_name, etc └──────┬───────┘ │ raw targets с __meta_* labels ▼ ┌──────────────┐ │ relabel_ │ фильтр + трансформация labels │ configs │ action: keep/drop/replace/labelmap └──────┬───────┘ │ финальные targets ▼ ┌──────────────┐ │ scrape │ HTTP GET /metrics └──────┬───────┘ │ raw metrics ▼ ┌──────────────┐ │ metric_ │ drop bad metrics, rewrite names │ relabel_ │ │ configs │ └──────┬───────┘ │ ▼ TSDB ``` Critical insight: **`__meta_*` labels отбрасываются после relabel**. Если нужны в TSDB, explicit `replace` action. ## Kubernetes SD ```yaml scrape_configs: - job_name: kubernetes-pods kubernetes_sd_configs: - role: pod # pod | service | endpoints | endpointslices | node | ingress relabel_configs: # Только pods с annotation prometheus.io/scrape=true - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: 'true' # Берём порт из annotation prometheus.io/port - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] action: replace regex: '([^:]+)(?::\d+)?;(\d+)' replacement: '$1:$2' target_label: __address__ # Path из annotation prometheus.io/path (default /metrics) - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] action: replace target_label: __metrics_path__ regex: '(.+)' # Все labels пода → metric labels с префиксом - action: labelmap regex: __meta_kubernetes_pod_label_(.+) # Convenience labels - source_labels: [__meta_kubernetes_namespace] target_label: namespace - source_labels: [__meta_kubernetes_pod_name] target_label: pod - source_labels: [__meta_kubernetes_pod_node_name] target_label: node ``` Result: каждый pod с `prometheus.io/scrape=true` annotation скрейпится. Все его k8s-labels копируются в metric labels. ## Roles в kubernetes_sd | Role | Что возвращает | Когда | |------|----------------|-------| | `node` | Kubernetes nodes (kubelet) | metric'и хоста, kubelet | | `pod` | каждый pod | application metrics | | `service` | k8s Service объекты | blackbox-probes к services | | `endpoints` | endpoints (legacy) | замена `service` для kube-state-metrics | | `endpointslices` | EndpointSlice (modern) | k8s 1.21+, scale better | | `ingress` | Ingress объекты | check ingresses | Modern setup: `endpointslices` вместо `endpoints` (производительность на больших кластерах). ## Consul SD ```yaml scrape_configs: - job_name: consul consul_sd_configs: - server: consul.example.com:8500 tags: ['prometheus'] # только services с tag relabel_configs: - source_labels: [__meta_consul_service] target_label: service - source_labels: [__meta_consul_tags] target_label: tags ``` Consul популярен в non-k8s стеках (Nomad, classic VMs). Service registers себя в Consul, Prom через SD узнаёт. ## file_sd, статика с гранулярностью Когда нет k8s/Consul, но есть скрипт который знает кого scrape'ать: ```yaml scrape_configs: - job_name: file-discovery file_sd_configs: - files: ['/etc/prometheus/targets/*.json'] refresh_interval: 30s ``` Файл: ```json [ { "targets": ["host1:9100", "host2:9100"], "labels": {"env": "prod", "team": "infra"} }, { "targets": ["dbhost:9187"], "labels": {"env": "prod", "team": "db"} } ] ``` Внешний инструмент (Ansible, terraform, chef) генерирует JSON. Prom auto-reload каждые 30s. Гибко и просто. ## relabel actions | Action | Что делает | |--------|------------| | `replace` | пишет в `target_label` значение `regex.replace(source, replacement)` | | `keep` | drop target если `source ~ regex` НЕ матчится | | `drop` | drop target если `source ~ regex` матчится | | `keepequal` | keep если source == target | | `dropequal` | drop если source == target | | `hashmod` | `target_label = hash(source) % modulus` (для sharding) | | `labelmap` | копирует все labels matching regex (с переименованием) | | `labeldrop` | удаляет labels matching regex | | `labelkeep` | оставляет только labels matching regex | | `lowercase` / `uppercase` | case transform | `keep` и `drop`, самые ходовые для фильтрации. `replace` и `labelmap`, для shaping labels. ## Sharding через hashmod 3 Prom скрейпят 1000 targets, делим поровну: ```yaml relabel_configs: - source_labels: [__address__] modulus: 3 target_label: __tmp_hash action: hashmod - source_labels: [__tmp_hash] regex: '0' # этот Prom, shard 0 action: keep ``` Каждый Prom держит ~330 targets. Federation вверх агрегирует. ## metric_relabel_configs, после scrape Применяется к **уже scrape'нным метрикам**, до записи в TSDB. ```yaml scrape_configs: - job_name: ... metric_relabel_configs: # Drop high-cardinality метрики - source_labels: [__name__] regex: 'go_gc_pauses_seconds_bucket' action: drop # Drop конкретный label с user_id (cardinality) - regex: 'user_id' action: labeldrop # Rewrite metric name - source_labels: [__name__] regex: 'old_metric_name' replacement: 'new_metric_name' target_label: __name__ ``` Используется для борьбы с [cardinality-explosion](/kb/cardinality-explosion.md) от ill-behaved exporters. Лучше чинить в коде, но иногда нет доступа. ## Best practices - **Filter в SD-этапе**, не на metric-этапе: `keep`/`drop` дешевле чем `metric_relabel`. Меньше нагрузка на target. - **Convenient labels** (`namespace`, `pod`, `service`), стабильные имена для всех jobs. Не `__meta_kubernetes_*` в queries. - **Не копируй все pod labels** через labelmap слепо, k8s навешивает `controller-revision-hash`, `pod-template-hash` etc. Cardinality. Whitelist через regex в `labelmap`: ```yaml - action: labelmap regex: __meta_kubernetes_pod_label_(app|version|component) ``` - **CI test relabel**: `promtool check config` + конкретные dry-run через `promtool` (limited). ## kube-state-metrics + node-exporter Стандартный k8s monitoring stack: - **node-exporter** на каждой ноде → `node_*` метрики - **kube-state-metrics** одиночный → `kube_*` метрики о состоянии объектов k8s - **cAdvisor** в kubelet → container metrics - **app metrics** через annotation discovery Все через k8s SD с разными relabel конфигами. ## Когда что-то пошло не так - **No targets in `/targets`**, relabel `keep` слишком strict, не осталось. Уберай по одному правилу, проверяй UI. - **Targets есть, но scrape error «401 Unauthorized»**, для kubelet-scrape нужен ServiceAccount + RBAC, или `bearer_token_file: /var/run/secrets/.../token`. - **Cardinality explosion после rollout**, labelmap copy'нул `pod-template-hash`. Whitelist labels. - **Targets дублируются**, одна и та же endpoint в нескольких roles. Используй deduplicating: один role + правильный selector. - **Slow SD reload (5+ minutes)**, k8s API rate limit. Уменьши `refresh_interval` или используй endpointslices вместо endpoints. - **`__address__` неправильный port**, k8s SD берёт первый declared port. Используй annotation override через replace. - **Stale targets после k8s namespace delete**, Prom держит до `--query.lookback-delta` (default 5m). Норма. ## Команды ```bash curl -s http://prometheus:9090/api/v1/targets | jq '.data.activeTargets[] | {scrapeUrl, labels}' ``` Все active targets с их финальными labels - после relabel ```bash curl -s http://prometheus:9090/api/v1/targets/metadata | jq '.data | length' ``` Сколько метрик описано в targets - предсказание объёма ```bash promtool check config /etc/prometheus/prometheus.yml ``` Валидация всего config: scrape_configs, relabel, rules ```bash curl -s http://prometheus:9090/api/v1/status/config | jq -r .data.yaml | grep -A 20 relabel ``` Live конфиг runtime - что Prom реально использует (после reload) ```bash kubectl get servicemonitor -A # если используется prometheus-operator ``` ServiceMonitor CRD - декларативная замена scrape_configs в k8s ```bash consul catalog services -tags # для consul SD ``` Все Consul services с tags - что Prom через consul_sd увидит ## См. также - [Prometheus: scrape, TSDB, PromQL и production-pitfalls](/kb/prometheus-basics.md) - [Kubernetes Service и Ingress - сетевая публикация подов](/kb/kubernetes-services-and-ingress.md) - [CNI plugins - сеть Kubernetes (calico, cilium, flannel)](/kb/cni-plugins.md) - [kubelet - архитектура агента ноды Kubernetes](/kb/kubelet-internals.md) - [Cardinality explosion: как убить Prometheus и как чинить](/kb/cardinality-explosion.md) - [Alertmanager: route tree, inhibit, dedup, on-call](/kb/alerting-rules-alertmanager.md)