LLM WikiAccess-protected knowledge portal
← 스터디 홈
1편 · 약 18분

Prometheus 아키텍처와 메트릭 수집

Pull 방식이 정착된 이유

모니터링 시스템을 설계할 때 가장 먼저 만나는 질문은 "누가 데이터를 먼저 움직이는가"다. 전통적인 Nagios, Zabbix 계열은 에이전트가 데이터를 밀어 보내는 push 방식을 많이 썼다. Prometheus는 반대다. 서버가 대상을 스스로 당겨온다(pull).

이 선택이 가져오는 실용적인 차이는 세 가지다.

  1. 대상의 건강 상태를 자동으로 안다. 스크레이프가 실패하면 Prometheus가 즉시 인지한다. push 방식에서는 에이전트가 조용히 죽으면 서버는 모른다.
  2. 설정이 서버 중심이다. 무엇을 수집할지를 Prometheus 설정 파일 한 곳에서 결정한다. 새 서비스가 배포돼도 Prometheus 쪽 설정(또는 서비스 디스커버리)만 바꾸면 된다.
  3. 수집 빈도를 서버가 통제한다. 대상이 너무 자주 보내거나 너무 드물게 보내는 문제가 생기지 않는다.

단, pull이 불가능한 상황도 있다. 배치 job처럼 수명이 짧은 프로세스는 실행이 끝나기 전에 Prometheus가 스크레이프를 못 할 수 있다. 이때 중간 버퍼 역할을 하는 Pushgateway를 쓴다.

수집 대상 Application /metrics 노출 Node Exporter OS 메트릭 변환 MySQL Exporter DB 메트릭 변환 Pushgateway 단기 job 버퍼 Service Discovery k8s / consul / file Prometheus Server Scrape Manager HTTP GET /metrics 수집 TSDB WAL + 압축 블록 저장 Rule Evaluator recording rule / alerting rule HTTP API (PromQL) Grafana / 외부 쿼리 Alertmanager route / dedup / silence Grafana Dashboard 시각화 Receivers Slack / Email / PagerDuty scrape scrape scrape scrape 대상 목록 alert 전송 PromQL
Prometheus 전체 아키텍처

메트릭 타입 네 가지

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"} 17

Counter는 절댓값보다 변화율(rate)이 의미 있다. 현재 값이 4812라는 것보다, 초당 몇 건이 들어오는지가 중요하다.

Gauge

임의로 오르내리는 값이다. 현재 메모리 사용량, 현재 연결 수, 온도처럼 지금 상태를 나타낸다.

# HELP node_memory_MemAvailable_bytes Available memory
# TYPE node_memory_MemAvailable_bytes gauge
node_memory_MemAvailable_bytes 1.73e+09

Gauge는 현재 값 자체가 의미 있으므로 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           600

Histogram은 나중에 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_exporterLinux 호스트의 CPU, 메모리, 디스크, 네트워크
mysqld_exporterMySQL 슬로우 쿼리, 연결 수, InnoDB 상태
postgres_exporterPostgreSQL 테이블 통계, 락, WAL 지연
redis_exporterRedis 커맨드 수, 메모리, 키 만료
blackbox_exporterHTTP, TCP, ICMP 엔드포인트 프로브(외부 가용성 검사)
kafka_exporterKafka 컨슈머 그룹 랙, 파티션 오프셋

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/