Prometheus 아키텍처와 메트릭 수집
Pull 방식이 정착된 이유
모니터링 시스템을 설계할 때 가장 먼저 만나는 질문은 "누가 데이터를 먼저 움직이는가"다. 전통적인 Nagios, Zabbix 계열은 에이전트가 데이터를 밀어 보내는 push 방식을 많이 썼다. Prometheus는 반대다. 서버가 대상을 스스로 당겨온다(pull).
이 선택이 가져오는 실용적인 차이는 세 가지다.
- 대상의 건강 상태를 자동으로 안다. 스크레이프가 실패하면 Prometheus가 즉시 인지한다. push 방식에서는 에이전트가 조용히 죽으면 서버는 모른다.
- 설정이 서버 중심이다. 무엇을 수집할지를 Prometheus 설정 파일 한 곳에서 결정한다. 새 서비스가 배포돼도 Prometheus 쪽 설정(또는 서비스 디스커버리)만 바꾸면 된다.
- 수집 빈도를 서버가 통제한다. 대상이 너무 자주 보내거나 너무 드물게 보내는 문제가 생기지 않는다.
단, pull이 불가능한 상황도 있다. 배치 job처럼 수명이 짧은 프로세스는 실행이 끝나기 전에 Prometheus가 스크레이프를 못 할 수 있다. 이때 중간 버퍼 역할을 하는 Pushgateway를 쓴다.
메트릭 타입 네 가지
Prometheus가 노출하는 메트릭은 네 종류다. 종류를 알아야 집계할 때 실수하지 않는다.
Counter
값이 단조 증가만 하는 메트릭이다. 재시작하면 0으로 리셋된다. 요청 수, 에러 수, 처리한 바이트 등이 여기 해당한다.
# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200"} 4812
http_requests_total{method="POST",status="500"} 17Counter는 절댓값보다 변화율(rate)이 의미 있다. 현재 값이 4812라는 것보다, 초당 몇 건이 들어오는지가 중요하다.
Gauge
임의로 오르내리는 값이다. 현재 메모리 사용량, 현재 연결 수, 온도처럼 지금 상태를 나타낸다.
# HELP node_memory_MemAvailable_bytes Available memory
# TYPE node_memory_MemAvailable_bytes gauge
node_memory_MemAvailable_bytes 1.73e+09Gauge는 현재 값 자체가 의미 있으므로 rate()를 적용하지 않는다.
Histogram
값의 분포를 측정한다. 요청 지연 시간처럼 "얼마나 자주 얼마나 빠른가"를 알고 싶을 때 쓴다. Prometheus는 자동으로 세 종류의 시리즈를 만든다.
_bucket{le="0.1"}: 0.1초 이하 요청 수 (누적)_sum: 지연 시간의 합계_count: 총 요청 수
http_request_duration_seconds_bucket{le="0.05"} 240
http_request_duration_seconds_bucket{le="0.1"} 422
http_request_duration_seconds_bucket{le="0.5"} 511
http_request_duration_seconds_bucket{le="+Inf"} 600
http_request_duration_seconds_sum 48.3
http_request_duration_seconds_count 600Histogram은 나중에 histogram_quantile() 함수로 p50, p90, p99 같은 퍼센타일을 계산할 수 있다.
Summary
Histogram과 비슷하지만 클라이언트 사이드에서 미리 퍼센타일을 계산해서 노출한다. 여러 인스턴스의 퍼센타일을 서버에서 재집계하는 것이 수학적으로 부정확하기 때문에, 요즘은 Histogram을 쓰고 서버에서 계산하는 것이 권장된다.
데이터 모델: 레이블이 정체성이다
Prometheus에서 시계열 하나의 정체성은 메트릭 이름 + 레이블 집합으로 결정된다.
http_requests_total{job="api-server", instance="10.0.1.5:8080", method="GET", status="200"}레이블 하나를 바꾸면 완전히 다른 시계열이 된다. 이 설계 덕분에 단일 메트릭으로 수천 개 인스턴스를 동시에 추적할 수 있다. 주의할 점은 레이블 카디널리티다. user_id처럼 값의 종류가 수백만 개인 레이블을 붙이면 Prometheus의 메모리와 TSDB 공간이 폭발적으로 늘어난다. 레이블 값은 경우의 수가 작은 것만 써야 한다.
TSDB: 2시간 블록과 WAL
Prometheus의 저장소는 TSDB(Time Series Database)라는 임베디드 저장 엔진이다.
수집되는 샘플은 일단 메모리의 Head Block에 들어간다. Head Block의 변경 사항은 즉시 WAL(Write-Ahead Log)에 기록된다. Prometheus가 비정상 종료되면 재시작 시 WAL을 재생해 복구한다.
Head Block이 약 2시간치 데이터를 모으면 압축(compaction)이 일어나 디스크의 불변 블록 파일로 변환된다. 처음에는 2시간 범위지만 인접 블록이 합쳐지면서 6시간 → 24시간 → 7일 범위로 커진다. 이 방식 덕분에 장기 쿼리도 적은 수의 블록 파일만 읽어도 된다.
Facebook의 Gorilla 논문에서 가져온 delta-of-delta 인코딩으로 샘플을 압축한다. 이 방법으로 샘플당 평균 약 1.37 바이트까지 줄어든다. 일반적인 float64(8 바이트)와 비교하면 5~6배 효율이다.
기본 보존 기간은 15일이다. --storage.tsdb.retention.time으로 변경할 수 있다.
서비스 디스커버리와 릴레이블링
정적 환경에서는 static_configs로 대상 주소를 나열하면 끝이다.
scrape_configs:
- job_name: 'api-server'
static_configs:
- targets: ['10.0.1.5:8080', '10.0.1.6:8080']Kubernetes처럼 Pod이 계속 생기고 사라지는 환경에서는 이 방식이 맞지 않는다. kubernetes_sd_configs를 쓰면 Prometheus가 Kubernetes API를 통해 Pod, Node, Service, Endpoint 목록을 자동으로 추적한다.
릴레이블링(relabeling)은 스크레이프 전에 메타 레이블을 가공하는 단계다. Pod 어노테이션에 prometheus.io/scrape: "true"가 있는 대상만 수집하거나, 내부 IP를 instance 레이블로 가져오는 작업을 여기서 처리한다.
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: 'true'
- source_labels: [__meta_kubernetes_pod_ip,
__meta_kubernetes_pod_annotation_prometheus_io_port]
separator: ':'
target_label: __address__Exporter: 서드파티 시스템 수집
Prometheus 형식으로 메트릭을 직접 노출하지 않는 시스템은 Exporter를 쓴다. Exporter는 대상 시스템의 상태를 읽어 Prometheus 형식으로 변환해 주는 프록시다.
| Exporter | 수집 대상 |
|---|---|
| node_exporter | Linux 호스트의 CPU, 메모리, 디스크, 네트워크 |
| mysqld_exporter | MySQL 슬로우 쿼리, 연결 수, InnoDB 상태 |
| postgres_exporter | PostgreSQL 테이블 통계, 락, WAL 지연 |
| redis_exporter | Redis 커맨드 수, 메모리, 키 만료 |
| blackbox_exporter | HTTP, TCP, ICMP 엔드포인트 프로브(외부 가용성 검사) |
| kafka_exporter | Kafka 컨슈머 그룹 랙, 파티션 오프셋 |
Remote Write와 장기 보존
Prometheus 기본 보존 기간(15일)은 장기 분석에 부족할 수 있다. remote_write를 쓰면 수집한 샘플을 외부 저장소로 실시간 복제할 수 있다. Thanos, Cortex, VictoriaMetrics, Grafana Mimir 같은 장기 저장 솔루션이 remote_write를 받아 수개월~수년치 데이터를 보존한다.
remote_write:
- url: "https://mimir.example.com/api/v1/push"
basic_auth:
username: my-user
password_file: /etc/prometheus/remote_write_password한 줄 정리
Prometheus는 pull 방식으로 대상을 정기 스크레이프하고, 레이블이 붙은 시계열을 TSDB에 저장한다. 서비스 디스커버리와 릴레이블링이 동적 환경 적응을 담당하고, Exporter가 서드파티 시스템을 브리징한다.
References
- https://prometheus.io/docs/introduction/overview/
- https://prometheus.io/docs/concepts/data_model/
- https://prometheus.io/docs/concepts/metric_types/
- https://github.com/prometheus/prometheus/blob/main/documentation/internal_architecture.md
- https://ganeshvernekar.com/blog/prometheus-tsdb-compaction-and-retention/
- https://www.groundcover.com/learn/observability/prometheus-tsdb
- https://dev.to/kanywst/prometheus-deep-dive-pull-architecture-and-tsdb-secrets-2jn4
- https://kevinfeng.github.io/post/kubernetes-sd-in-prometheus/