Зачем 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
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
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'ать:
scrape_configs:
- job_name: file-discovery
file_sd_configs:
- files: ['/etc/prometheus/targets/*.json']
refresh_interval: 30s
Файл:
[
{"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, делим поровну:
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.
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 от 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-hashetc. 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, relabelkeepслишком 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). Норма.