서버를 운영하다 보면 「지금 요청이 초당 몇 번 오나」, 「에러가 언제부터 늘었나」를 숫자로 보고 싶어진다. 그래서 나는 APM 구성을 시작했다. 이 구성에서는 Spring Boot의 Actuator가 애플리케이션의 메트릭(요청 수, 응답시간, 메모리 사용량 같은 숫자)을 주소 하나에 내놓고, Prometheus가 그 숫자를 주기적으로 가져가 모아 둔다.

그런데 Prometheus 하나로 그래프 화면(대시보드)과 무제한 보관까지 다 해결되는 것은 아니다. 무엇을 Prometheus에 맡기고 무엇을 다른 도구에 맡길지 정하려면 경계부터 알아야 했다. 이 글은 Prometheus가 무엇을 해주고 무엇을 해주지 않는지를 실무 관점에서 정리한다.


토론 요약 — Prometheus는 수집기이자 저장소이자 규칙 평가기다

  • Prometheus는 Pull 기반 시계열 수집기 + 내장 TSDB + 규칙/알림 평가기다. 직접 찾아가 숫자를 가져오고(Pull), 시간순으로 쌓아 두고(TSDB), 정해 둔 조건을 주기적으로 계산해 넘었으면 알림을 만든다.
  • 저장과 질의(쌓인 숫자를 조건을 걸어 꺼내는 일, 아래의 쿼리와 같은 말)는 Prometheus가 하지만, 보기 좋은 대시보드는 Grafana가, 알림을 누구에게 보낼지 정하는 라우팅은 Alertmanager가 담당한다.
  • 오래 보관하거나 서버를 여러 대로 늘려 감당하는 일(수평 확장)은 내장 TSDB로는 안 된다. 내장 TSDB는 디스크 한 대에 정해 둔 기간만큼만 쌓는 한 대짜리 저장소라서다(아래 「제공하지 않는 것」에서 자세히 본다). 그래서 remote_write/remote_read로 바깥 TSDB를 붙인다(공식 문서: remote_write).

제공하는 것 — Prometheus가 해주는 일

  • 스크레이프(Pull): 설정 파일의 scrape_configs 항목에 적어 둔 대상마다, 정해 둔 주기로 그 대상의 주소 /metrics에 HTTP 요청(브라우저가 웹 페이지를 달라고 할 때와 같은 GET 요청)을 보내 응답에 적힌 숫자를 가져온다. 주소를 따로 적지 않으면 /metrics가 기본값이고, Spring Boot는 그 숫자를 /actuator/prometheus 주소에 내놓는다. 응답은 아래처럼 한 줄에 숫자 하나씩 적힌 글자다(공식 문서 Exposition formats의 예시).

    # HELP http_requests_total The total number of HTTP requests.
    # TYPE http_requests_total counter
    http_requests_total{method="post",code="200"} 1027 1395066363000
    

    # HELP는 이 숫자의 설명, # TYPE은 종류(counter는 계속 늘기만 하는 누적값)다. 마지막 줄이 숫자 하나로, 메트릭 이름 · 중괄호 안의 라벨 · 값(1027) · 잰 시각(생략할 수 있다) 순서다.

  • 내장 TSDB 저장·압축: 들어오는 숫자를 두 시간치가 모일 때까지 메모리에 모아 두었다가, 두 시간 단위 덩어리(블록)로 묶어 디스크에 쓴다. 메모리에 있는 동안 서버가 죽으면 그 숫자를 잃으므로, 들어오는 대로 먼저 로그 파일(WAL)에 적어 두고 다시 켤 때 그 파일로 되살린다. 디스크에 쓴 블록은 뒤에서 더 긴 기간의 블록으로 합친다(compaction). Prometheus 문서는 이 저장 형식을 매우 효율적이라고 소개하고, 이 글은 그 결과로 빠른 쓰기와 「지난 1시간」 같은 시간 범위 쿼리를 얻는다고 본다. 블록을 합치는 일이 크기와 속도를 구체적으로 얼마나 줄이는지는 확인하지 않았다.
  • PromQL 질의: 쌓인 숫자에 조건을 걸어 꺼내는 Prometheus 전용 질의 언어다. 라벨(숫자에 붙은 이름=값 꼬리표)로 골라내고(필터), 여러 줄을 더하거나 묶고(집계), 「지난 5분」처럼 시간 구간을 잘라 계산한다(창 연산). 이 식을 대시보드와 알림에 그대로 쓴다. 기본 문법: PromQL basics.
  • 규칙/알림 평가: 레코딩 룰(무거운 식을 미리 계산해 저장해 두는 규칙)과 알림 룰(「에러율이 5%를 넘으면」 같은 조건)을 주기적으로 계산하고, 알림 룰의 조건에 걸리면 그 알림을 Alertmanager로 보낸다(설정 예시: alerting config).
  • 페더레이션/리모트 연동: 한 Prometheus가 다른 Prometheus의 숫자를 가져가게 해서 위아래로 층을 쌓는 페더레이션과, 바깥 장기 보관소로 보내는 remote_write를 지원한다(federation 예시). 서버가 아주 많으면 아래층이 자세히 모으고 위층은 요약만 모아 한 곳에서 본다.

제공하지 않는 것 — Prometheus가 못 하는 일

  • 보기 좋은 대시보드와 탐색 화면: Prometheus에도 웹 화면이 있지만 식을 넣고 그래프 하나를 보는 정도로 단순하다. 여러 그래프를 모은 실사용 대시보드는 Grafana 등에서 만든다.
  • 장기 무제한 보관·수평 확장: Prometheus 로컬 저장소는 따로 정하지 않으면 15일치만 남기고 지운다. 또 여러 대로 묶이거나 복제되지 않아서, 디스크나 서버 한 대가 망가지면 그 데이터를 잃고 서버를 늘려 나눠 담을 수도 없다. 장기 보관과 확장은 remote_write 뒤에 TSDB(VictoriaMetrics, Mimir, Thanos 등 Prometheus 데이터를 받아 오래·여러 대로 저장하는 제품들)를 둔다.
  • 로그·트레이싱 수집: 메트릭(뭉뚱그린 숫자)만 담당한다. 한 줄씩 남는 사건 기록인 로그와, 요청 하나가 거쳐 간 길을 잇는 트레이싱은 다루지 않는다. 로그는 Loki, 트레이싱은 Tempo/Jaeger 같은 별도 도구 묶음(스택)이 필요하다.
  • 알림 라우팅: 알림을 같은 것끼리 묶고(집계), 받을 곳을 골라 보내고(라우팅), 점검 중에는 잠시 끄는(사일런스) 일은 Alertmanager가 한다. Prometheus는 조건에 걸린 알림을 Alertmanager에 “넘기기”만 한다.

실무 적용 예제 — Spring Boot 앱 하나를 스크레이프하고 알림을 거는 설정

1) 최소 스크레이프 설정 예시

아래는 Prometheus가 시작할 때 읽는 설정 파일(보통 prometheus.yml)이다. YAML 형식이라 들여쓰기가 곧 계층이고, -로 시작하는 줄은 목록의 한 칸이다. 이 파일 하나로 「누구를, 얼마나 자주 가져오고, 알림은 어디로 넘길지」를 정한다.

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "app-rules.yml"

scrape_configs:
  - job_name: "farm-predict-kamis-dev"
    metrics_path: /actuator/prometheus
    static_configs:
      - targets: ["farm.example.com:8080"]
        labels:
          application: "kamis"

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["alertmanager:9093"]

한 줄씩 따라가면 이렇다.

  • global: — 아래 항목들의 기본값을 모아 두는 칸이다.
  • scrape_interval: 15s — 대상을 15초마다 스크레이프한다. 적지 않으면 기본값은 1분이다.
  • evaluation_interval: 15s — 레코딩 룰과 알림 룰을 15초마다 다시 계산한다. 이것도 적지 않으면 1분이다.
  • rule_files: — 규칙을 적어 둔 파일 목록이다. 2번의 알림 룰 파일을 여기서 가리켜야 Prometheus가 읽는다. 이 줄이 없으면 룰 파일을 만들어 두어도 평가되지 않는다. 파일 이름 app-rules.yml은 내가 정한 예시다.
  • scrape_configs: — 스크레이프할 대상 묶음의 목록이다. - 하나가 묶음 하나다.
  • job_name: "farm-predict-kamis-dev" — 이 묶음의 이름(job)이다. Prometheus는 여기서 가져온 모든 숫자에 job="farm-predict-kamis-dev" 라벨을 자동으로 붙인다. 아래 PromQL에서 {job="..."}으로 이 앱의 숫자만 골라내는 것이 이 라벨 덕분이다.
  • metrics_path: /actuator/prometheus — 기본값 /metrics 대신 Spring Boot Actuator가 숫자를 내놓는 주소로 바꾼다. 위 「제공하는 것」에서 말한 그 주소다.
  • static_configs: — 대상 주소를 파일에 고정으로(static) 적는다는 뜻이다. 주소를 자동으로 찾아오는 방식(서비스 디스커버리)도 있지만 서버 한 대라면 이것으로 충분하다.
  • targets: ["farm.example.com:8080"] — 찾아갈 서버의 호스트(farm.example.com, 서버를 가리키는 이름)와 포트(8080)다. Prometheus는 http://farm.example.com:8080/actuator/prometheus에 요청을 보낸다. 이 호스트:포트는 instance 라벨로도 붙는다.
  • labels: application: "kamis" — 이 대상에서 가져온 모든 숫자에 application="kamis" 라벨을 더 붙인다. 나중에 여러 앱의 숫자가 섞여도 이 라벨로 가를 수 있다.
  • alerting: alertmanagers: — 알림 룰에 걸린 알림을 넘길 Alertmanager의 주소다. alertmanager:9093은 alertmanager라는 이름의 서버, 9093번 포트를 뜻한다. farm.example.com과 alertmanager는 생김새만 다를 뿐 둘 다 호스트 이름이다. 이름을 실제 서버 주소로 바꾸는 일은 DNS 같은 이름 풀이 장치가 하므로, alertmanager처럼 짧은 이름도 Prometheus가 있는 곳에서 그 이름이 풀려야 찾아간다.

설정 항목들은 공식 구성 문서에 상세 설명이 있다(configuration).

2) 알림 룰 예시 (에러율 1분 평균이 5% 이상)

에러율은 「전체 요청 중 서버 쪽 잘못으로 실패한 요청(HTTP 상태 코드 500번대, 줄여서 5xx)의 비율」이다. 아래는 1번 설정의 rule_files가 가리키는 app-rules.yml 파일이다.

groups:
  - name: app-rules
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_server_requests_seconds_count{job="farm-predict-kamis-dev",status=~"5.."}[1m]))
          /
          sum(rate(http_server_requests_seconds_count{job="farm-predict-kamis-dev"}[1m]))
          > 0.05
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "farm-predict-kamis-dev 5xx error rate > 5%"

한 줄씩 따라가면 이렇다.

  • groups: / - name: app-rules — 룰은 그룹으로 묶는다. 그룹 하나가 같은 주기로 평가되는 룰 묶음이고, 주기를 따로 적지 않으면 1번의 evaluation_interval(15초)을 따른다.
  • rules: / - alert: HighErrorRate — 그룹 안의 룰 목록이다. alert:로 시작하면 알림 룰이고, HighErrorRate가 알림 이름이다.
  • expr: | — 조건 식이다. |는 YAML에서 「다음 줄부터 여러 줄 글을 그대로 받는다」는 표시라, 긴 식을 줄을 나눠 적을 수 있다.
  • http_server_requests_seconds_count — Spring Boot는 HTTP 요청을 받을 때마다 그 요청이 몇 초 걸렸는지를 http.server.requests라는 타이머로 잰다. 스프링이 메트릭을 잴 때 쓰는 라이브러리 Micrometer는 타이머 하나를 Prometheus에 _seconds_count(잰 횟수), _seconds_sum(걸린 시간의 합), _seconds_max(가장 오래 걸린 시간) 세 줄로 나눠 내놓는다. 이름의 seconds는 「초를 재는 타이머」라서 붙고, _count는 그중 잰 횟수, 곧 지금까지 받은 요청 수다. 계속 늘어나기만 하는 누적값이다.
  • {job="...",status=~"5.."} — 중괄호 안은 라벨 조건이다. =는 값이 같은 것, =~는 정규식에 맞는 것을 고른다. "5.."는 5로 시작하는 세 글자, 곧 500~599 상태 코드다.
  • [1m] — 지금부터 1분 전까지의 값들을 잘라 온다.
  • rate(...) — 그 1분 동안 누적값이 초당 평균 얼마씩 늘었는지 계산한다. 누적 요청 수를 「초당 요청 수」로 바꾸는 것이다. 제목의 「1분 평균」이 이것이다.
  • sum(...) — 라벨이 다른 여러 줄(예: 요청 주소별, GET · POST 같은 HTTP 메서드별)을 하나로 더한다.
  • / 와 > 0.05 — 위 줄(초당 5xx 요청 수)을 아래 줄(초당 전체 요청 수)로 나눈 값, 곧 에러율이 0.05(5%)를 넘는지 본다.
  • for: 2m — 조건이 처음 참이 된 순간 바로 알리지 않고, 2분 동안 계속 참일 때 알림을 울린다. 그 전까지는 대기(pending) 상태다. 「1분 평균」은 에러율을 어떻게 재느냐이고, 「2분」은 그 값이 얼마나 오래 넘어 있어야 알리느냐다. 잠깐 튀는 값으로 알림이 울리는 것을 막는다.
  • labels: severity: warning — 이 알림에 severity="warning" 라벨을 붙인다. Alertmanager가 이 라벨을 보고 받을 곳을 고른다(라우팅).
  • annotations: summary: — 알림을 받은 사람이 읽을 설명 글이다. 라벨과 달리 알림을 고르는 데는 쓰지 않는다.

알림을 누구에게 보낼지는 Alertmanager에서 정한다(alertmanager_config).

3) 자주 쓰는 PromQL 스니펫 (job만 교체)

아래는 자주 쓰는 짧은 식(스니펫)이다. 다른 앱에 쓸 때는 job="farm-predict-kamis-dev" 부분만 그 앱의 job 이름으로 바꾸면 된다.

  • 수집 상태: up{job="farm-predict-kamis-dev"} — up은 Prometheus가 스크레이프할 때마다 스스로 남기는 숫자로, 가져오기에 성공하면 1, 실패하면 0이다.
  • RPS: sum by (instance) (rate(http_server_requests_seconds_count{job="farm-predict-kamis-dev"}[5m])) — 초당 요청 수를 by (instance), 곧 서버 주소별로 따로 더한다. 서버가 여러 대면 서버마다 한 줄씩 나온다.
  • 에러율: sum(rate(http_server_requests_seconds_count{job="farm-predict-kamis-dev",status=~"5.."}[5m])) / sum(rate(http_server_requests_seconds_count{job="farm-predict-kamis-dev"}[5m])) — 2번 알림 룰과 같은 식인데 구간이 5분이다. 구간이 길수록 잠깐 튀는 값이 묻혀 선이 매끈해지고, 짧을수록 변화를 빨리 보여 준다.
  • p95 응답시간: histogram_quantile(0.95, sum by (le) (rate(http_server_requests_seconds_bucket{job="farm-predict-kamis-dev"}[5m]))) — 요청 100개 중 95번째로 빠른 요청이 걸린 시간이다. _bucket은 「0.1초 이하가 몇 개, 0.5초 이하가 몇 개」처럼 응답시간을 칸(버킷)별로 센 숫자이고, le 라벨이 그 칸의 상한이다. histogram_quantile은 이 칸별 개수로 95% 지점을 추정한다. le가 없으면 칸을 구분할 수 없어서 by (le)로 남겨 둔다. _bucket 줄은 기본으로 나오지 않는다. Micrometer 문서에 따르면 앱 쪽에서 percentile histogram을 켜야 _seconds_bucket 줄이 더해진다.
  • JVM 힙: jvm_memory_used_bytes{job="farm-predict-kamis-dev",area="heap"} — JVM이 쓰는 메모리 중 new로 만든 객체가 들어가는 힙 영역의 사용량이다.
  • 스크레이프 샘플 수: scrape_samples_post_metric_relabeling{job="farm-predict-kamis-dev"} — 한 번 스크레이프할 때 가져와 저장한 숫자(샘플) 개수다(가져온 뒤 라벨을 고치거나 버리는 relabeling을 거친 뒤의 개수). 라벨 값의 조합마다 시계열이 따로 하나씩 생기므로, 값이 많은 라벨이 새로 붙으면 이 개수가 확 는다. 그래서 이 값이 갑자기 커지면 라벨 때문에 시계열이 불어났다는 신호다(아래 결론의 카디널리티 폭발).

4) 데이터가 정말 들어오는지 확인하는 빠른 절차

  • Prometheus 웹 화면(기본 주소 http://<Prometheus 서버>:9090)을 열고 Status → Targets에서 해당 job이 UP인지 확인한다. 최신 버전 화면에서는 이 메뉴 이름이 「Target health」다. UP은 위 up 값이 1이라는 뜻이다.
  • 같은 웹 화면의 Graph 탭(식을 넣고 결과를 보는 곳)에서 up{job="farm-predict-kamis-dev"} 실행(1이면 수집 중).
  • 주요 메트릭 쿼리로 값이 그려지는지 확인. 없으면 식에 적은 라벨 값(job 이름 등)이 실제 숫자에 붙은 라벨과 다른지, 앱이 인증을 요구해 요청이 막혔는지, 네트워크가 막혔는지를 점검.
  • /actuator/prometheus를 curl로 직접 열어(curl http://farm.example.com:8080/actuator/prometheus) 실제 메트릭이 노출되는지 확인. 위 「제공하는 것」의 예시처럼 # HELP · # TYPE 줄과 http_server_requests_seconds_count{...} 숫자 같은 줄이 보이면 앱은 제대로 내놓고 있다. 여기서 숫자가 안 보이면 Prometheus가 아니라 앱 쪽 문제다.

흐름 한눈에 보기

flowchart LR A[Spring Boot Actuator /actuator/prometheus]:::node --> B[Prometheus 스크레이프/TSDB/알림 평가]:::node B --> C[Alertmanager 라우팅/사일런스]:::node B --> D[Grafana 쿼리·대시보드·Alerting]:::node classDef node fill:#1e293b,stroke:#94a3b8,color:#e2e8f0; classDef edge stroke:#94a3b8,color:#94a3b8;

그림에서 Grafana에도 Alerting이 붙어 있는 것은 Grafana에도 자기 알림 기능(Grafana Alerting)이 있어서다. Grafana Alerting은 여러 데이터 소스(Prometheus처럼 Grafana가 숫자를 읽어 오는 저장소)에 질의한 결과로 조건을 걸고 알림을 어디로 보낼지도 스스로 정한다. 이 글의 예제는 Prometheus 알림 룰 → Alertmanager 쪽 길만 다뤘고, Grafana 쪽 길은 Grafana 편에서 다룬다.


결론/팁 — 수집·저장·평가는 Prometheus, 화면과 라우팅은 다른 도구에 맡긴다

  • Prometheus는 메트릭 수집·저장·PromQL 평가까지가 역할이다. 여기서 평가는 질의한 식을 정해 둔 주기마다 계산해 룰의 조건을 확인하는 일이다. 대시보드와 알림 라우팅은 Grafana/Alertmanager에 맡긴다.
  • 장기 보관·확장이 필요하면 remote_write 뒤에 TSDB를 붙인다.
  • 라벨 설계와 스크레이프 주기를 조정해 부하와 카디널리티 폭발을 방지한다. 라벨 값의 조합마다 시계열이 하나씩 따로 생기므로 값이 끝없이 많은 것(사용자 ID 등)을 라벨로 달지 않는다. 스크레이프 주기를 15초에서 1분으로 늘리면 같은 시간 동안 요청과 저장할 샘플이 4분의 1로 준다.
  • 알림은 “조건”을 PromQL로 짜고, 라우팅/사일런스 정책은 Alertmanager에서 다룬다.

다음 단계로 Grafana 편에서 PromQL을 그대로 그래프 칸(대시보드의 패널)과 Grafana Alerting에 넣는 방법과, 알림 문구 틀(알림 템플릿)과 대시보드 구성 포인트를 이어서 다룰 예정이다.


자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기