본문 바로가기

Prometheus·Grafana

Micrometer 측정을 Prometheus가 수집하고 Grafana가 시각화하는 경로를 연결해 비율·히스토그램·레이블 비용을 검증합니다.

애플리케이션 레지스트리에 값이 보인다고 대시보드가 자동으로 채워지지는 않습니다.

Prometheus용 레지스트리가 미터를 Prometheus 형식의 텍스트로 만들고, Actuator 엔드포인트가 이를 노출하며, Prometheus 대상이 주기적으로 수집하고, Grafana가 시계열을 쿼리해야 합니다.

어느 단계가 끊겨도 패널은 비지만 원인과 복구 방법은 다릅니다.

패널의 선은 계측·노출·수집·쿼리 전 구간이 최신 상태일 때만 의미가 있다

레지스트리·엔드포인트·Prometheus pull·Grafana query에서 같은 관측 값이 어디까지 준비됐는지 달라진다.

PROMETHEUS · SCRAPE AND QUERY

빈 패널은 레지스트리부터 쿼리까지 경계를 좁혀 진단한다

Prometheus는 엔드포인트를 주기적으로 가져오고 Grafana는 저장된 시계열을 쿼리합니다. 데이터 없음과 정상 트래픽 0을 구분합니다.

수집과 조회 요청 방향을 데이터 이동과 구분한다실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.레지스트리실제 미터수집 엔드포인트노출 텍스트Prometheus수집·저장Grafana쿼리·표시수집 요청조회 요청
수집과 조회 요청 방향을 데이터 이동과 구분한다실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.레지스트리실제 미터수집 엔드포인트노출 텍스트Prometheus수집·저장Grafana쿼리·표시수집 요청조회 요청

앱 · 계측과 노출

  1. 레지스트리의 실제 미터

    이름·제한된 태그·값을 확인합니다. 애노테이션은 aspect와 proxy가 필요하고 자기 호출·직접 new는 별도 시험합니다.

  2. Prometheus exposition

    레지스트리 의존성·access·웹 노출·보안을 확인하고 실제 HELP/TYPE/표본 이름과 레이블을 읽습니다.

  3. 중복과 표본 경계

    같은 사건에 수동과 애노테이션 계측을 겹치지 않습니다. 15초 수집은 2초 게이지 급증을 놓칠 수 있습니다.

수집 · 저장과 조회

  1. Prometheus Targets

    인증·TLS·service discovery와 최근 scrape, 지속 시간, last error, up을 확인합니다. 브라우저 200과 주기 수집은 다른 증거입니다.

  2. 원시 PromQL

    counter는 rate/increase로 reset을 처리합니다. 수집 간격에 비해 충분한 창을 사용하고 필요한 레이블만 집계합니다.

  3. Grafana 패널

    실제 쿼리 결과 뒤에 단위·시간 범위·변환·범례를 확인합니다. 빈 결과를 0으로 채우지 않고 최신성을 함께 표시합니다.

여러 인스턴스 p95는 발행한 히스토그램 버킷을 올바르게 집계해 계산합니다. 클라이언트 백분위수를 단순 평균하지 않습니다. 수집·쿼리·표시 주체와 인증 정보는 별도입니다.

이 예의 조건

  • 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
  • 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
  • 실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트의 자료를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드와 5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹 노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.
관계도의 보충 설명
원래 전체 그림의 표시 문구·조건·주석
순번원문 표시
1패널의 선은 계측·노출·수집·쿼리 전 구간이 최신 상태일 때만 의미가 있다
4Prometheus는 엔드포인트의 자료를 주기적으로 가져오고 Grafana는 저장된 시계열을 쿼리합니다. 데이터 없음과 정상 트래픽 0을 구분합니다.
6실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트의 자료를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드와 5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹 노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.
단계별 상태·값과 전환 이유
장면관찰 값전환 이유
관측 값 준비

registry 값이 노출 endpoint에 준비됩니다.

자료: registry→endpoint · 웹 노출·auth: 별도 조건수집할 값의 노출 경계가 생깁니다.
Prometheus pull

Prometheus가 endpoint에 요청하여 자료를 가져옵니다.

요청: Prometheus→endpoint · 전달: 능동 push 아님수집 자료가 Prometheus에 도달합니다.
Grafana query

Grafana가 Prometheus를 조회합니다.

요청: Grafana→Prometheus · 조건: 이름·시간 범위 일치전 구간에서 최신 상태인 자료만 패널에 나타납니다.

패널의 선은 계측·노출·수집·쿼리 전 구간이 최신 상태일 때만 의미가 있다

레지스트리·엔드포인트·Prometheus pull·Grafana query에서 같은 관측 값이 어디까지 준비됐는지 달라진다.

PROMETHEUS · SCRAPE AND QUERY

빈 패널은 레지스트리부터 쿼리까지 경계를 좁혀 진단한다

Prometheus는 엔드포인트를 주기적으로 가져오고 Grafana는 저장된 시계열을 쿼리합니다. 데이터 없음과 정상 트래픽 0을 구분합니다.

수집과 조회 요청 방향을 데이터 이동과 구분한다실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.레지스트리실제 미터수집 엔드포인트노출 텍스트Prometheus수집·저장Grafana쿼리·표시수집 요청조회 요청
수집과 조회 요청 방향을 데이터 이동과 구분한다실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.레지스트리실제 미터수집 엔드포인트노출 텍스트Prometheus수집·저장Grafana쿼리·표시수집 요청조회 요청

앱 · 계측과 노출

  1. 레지스트리의 실제 미터

    이름·제한된 태그·값을 확인합니다. 애노테이션은 aspect와 proxy가 필요하고 자기 호출·직접 new는 별도 시험합니다.

  2. Prometheus exposition

    레지스트리 의존성·access·웹 노출·보안을 확인하고 실제 HELP/TYPE/표본 이름과 레이블을 읽습니다.

  3. 중복과 표본 경계

    같은 사건에 수동과 애노테이션 계측을 겹치지 않습니다. 15초 수집은 2초 게이지 급증을 놓칠 수 있습니다.

수집 · 저장과 조회

  1. Prometheus Targets

    인증·TLS·service discovery와 최근 scrape, 지속 시간, last error, up을 확인합니다. 브라우저 200과 주기 수집은 다른 증거입니다.

  2. 원시 PromQL

    counter는 rate/increase로 reset을 처리합니다. 수집 간격에 비해 충분한 창을 사용하고 필요한 레이블만 집계합니다.

  3. Grafana 패널

    실제 쿼리 결과 뒤에 단위·시간 범위·변환·범례를 확인합니다. 빈 결과를 0으로 채우지 않고 최신성을 함께 표시합니다.

여러 인스턴스 p95는 발행한 히스토그램 버킷을 올바르게 집계해 계산합니다. 클라이언트 백분위수를 단순 평균하지 않습니다. 수집·쿼리·표시 주체와 인증 정보는 별도입니다.

이 예의 조건

  • 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
  • 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
  • 실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트의 자료를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드와 5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹 노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.

관계도의 보충 설명

원래 전체 그림의 표시 문구·조건·주석
순번원문 표시
1패널의 선은 계측·노출·수집·쿼리 전 구간이 최신 상태일 때만 의미가 있다
4Prometheus는 엔드포인트의 자료를 주기적으로 가져오고 Grafana는 저장된 시계열을 쿼리합니다. 데이터 없음과 정상 트래픽 0을 구분합니다.
6실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트의 자료를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드와 5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹 노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.
단계별 상태·값과 전환 이유
장면관찰 값전환 이유
관측 값 준비

registry 값이 노출 endpoint에 준비됩니다.

자료: registry→endpoint · 웹 노출·auth: 별도 조건수집할 값의 노출 경계가 생깁니다.
Prometheus pull

Prometheus가 endpoint에 요청하여 자료를 가져옵니다.

요청: Prometheus→endpoint · 전달: 능동 push 아님수집 자료가 Prometheus에 도달합니다.
Grafana query

Grafana가 Prometheus를 조회합니다.

요청: Grafana→Prometheus · 조건: 이름·시간 범위 일치전 구간에서 최신 상태인 자료만 패널에 나타납니다.

Prometheus 수집 엔드포인트

Prometheus 레지스트리 의존성과 Actuator가 있으면 /actuator/prometheus 엔드포인트가 후보가 됩니다.

웹 노출 허용 목록, 관리 보안, 서비스 탐색 대상을 모두 통과해야 수집됩니다.

브라우저에서 한 번 200을 받은 것보다 Prometheus Targets 화면의 최근 수집, 지속 시간, 오류, up을 확인합니다.

src/main/resources/application.yml
management:
  endpoints:
    web:
      exposure:
        include: "health,prometheus"

Spring 애플리케이션 설정은 Prometheus 엔드포인트를 웹에 노출합니다.

prometheus.yml
scrape_configs:
  - job_name: board
    metrics_path: /actuator/prometheus
    scrape_interval: 15s
    static_configs:
      - targets: ["board-management:9091"]

운영 환경은 정적 대상보다 오케스트레이터의 서비스 탐색을 사용하고 URL 스킴, 인증, TLS, 레이블 재작성을 함께 구성합니다.

수집 간격을 15초로 두면 2초 동안 발생한 순간 급증이 평균이나 게이지 표본에서 사라질 수 있음을 이해합니다.


메트릭 이름·레이블 변환

앞 절에서 baseUnit("entries")로 등록한 board.entry.processed 카운터는 기본 Prometheus naming에서 board_entry_processed_entries_total로 노출됩니다. 별도 naming convention이나 필터를 설정했다면 아래 표본 이름과 실제 노출 결과를 대조하세요.

동일 미터의 태그는 레이블이 됩니다.

대시보드 쿼리를 Java 이름만 보고 작성하지 말고 실제 노출 형식의 HELP, TYPE, 표본 이름과 레이블을 확인합니다.

Prometheus exposition 예
# HELP board_entry_processed_entries_total Completed entry processing attempts
# TYPE board_entry_processed_entries_total counter
board_entry_processed_entries_total{outcome="accepted"} 42.0
board_entry_processed_entries_total{outcome="rejected"} 3.0

레이블 값에 게시글 ID를 넣으면 수집 페이로드와 백엔드 시계열이 계속 증가합니다.

경로, 메서드, 정해진 결과 코드처럼 집계 가능한 레이블을 선택하고 원시 식별자는 트레이스나 로그로 보냅니다.


카운터 쿼리

누적 카운터 42만으로 최근 트래픽을 알 수 없습니다.

rate(...[5m])는 초당 평균 증가율, increase(...[1h])는 구간의 증가량을 묻습니다.

프로세스 재시작으로 값이 0으로 내려가는 초기화를 PromQL 함수가 처리하므로 게이지처럼 마지막 값끼리 단순 빼지 않습니다.

구간이 수집 간격보다 너무 짧으면 표본이 부족하고 그래프가 흔들립니다.

경보에는 즉시 반응과 안정성의 절충을 고려해 여러 구간을 사용합니다.

sum by (outcome)처럼 필요한 레이블만 유지하고 인스턴스를 합칠지 상세 분석할지 패널 목적에 맞춥니다.

예시 위 job_name=board의 허용·거부 두 결과만 분모에 넣는 비율입니다. 실제 노출 이름이 앞 표본과 같고 필요한 시계열이 있다는 조건에서 다음 PromQL로 인스턴스를 합칠 수 있습니다.

board 작업의 최근 5분 허용 비율
sum(rate(board_entry_processed_entries_total{job="board",outcome="accepted"}[5m]))
/
sum(rate(board_entry_processed_entries_total{job="board",outcome=~"accepted|rejected"}[5m]))

허용 8건/초·거부 2건/초라면 8/(8+2)=0.8, 표시 단위가 percent라면 80%입니다. failed까지 분모에 넣을지는 이벤트 계약을 먼저 바꿔야 합니다. 이 예시는 허용·거부 두 결과의 합만 셉니다. 두 증가율이 모두 0이면 0/0이므로 정상 비율 0%로 채우지 말고 무트래픽을 별도로 표현합니다. 시계열 자체가 없는 경우와도 구분합니다.

Prometheus의 increase 설명은 초기화를 보정하고 범위 양 끝까지 외삽하므로 정수 카운터에서도 소수가 나올 수 있다고 명시합니다. 따라서 increase의 화면 숫자를 영속 사건 원장의 정확한 건수로 단정하지 않습니다.

src/main/java/board/metrics/Outcome.java
package board.metrics;

public enum Outcome {
    ACCEPTED("accepted"),
    REJECTED("rejected"),
    FAILED("failed");

    private final String tagValue;

    Outcome(String tagValue) {
        this.tagValue = tagValue;
    }

    public String tagValue() {
        return tagValue;
    }
}

열거형에서 레이블 값을 만들면 오타와 임의 예외 이름의 유입을 막습니다.


타이머 지연 분포

타이머의 호출 개수와 전체 시간만 있으면 평균은 계산할 수 있지만 느린 소수 요청을 숨깁니다.

서비스 수준의 지연 시간을 판단하려면 히스토그램 버킷을 발행하고 histogram_quantile로 전체 인스턴스의 집계 백분위수를 계산하거나 SLO 경계 버킷 비율을 사용합니다.

클라이언트 측 백분위수는 인스턴스 간 합산이 어렵다는 점을 구별합니다.

버킷을 모든 URI와 상태 레이블에 무분별하게 켜면 시계열 수가 버킷 개수만큼 늘어납니다.

중요한 작업에만 SLO 경계를 설정하고 실제 트래픽 범위로 버킷을 조정합니다.

최댓값은 시간 구간에 따라 사라질 수 있으므로 영구적인 최악값으로 해석하지 않습니다.

클래식 히스토그램을 실제로 발행했고 내보낸 이름·le 레이블을 확인한 조건에서는 다음 형태로 인스턴스의 버킷 증가율을 먼저 합칩니다. 여기서는 다음 절 PostImportMetrics의 board.import.latency가 board_import_latency_seconds_bucket으로 노출된 경우를 가정합니다. 앞 절 ImportTimer의 builder에는 publishPercentileHistogram()이 없으므로 버킷이 자동으로 있다고 가정하지 않습니다.

발행된 클래식 가져오기 히스토그램의 p95
histogram_quantile(0.95,
  sum by (le) (rate(board_import_latency_seconds_bucket{job="board"}[5m])))

공식 histogram_quantile 설명처럼 클래식 버킷 집계에는 le를 남겨야 합니다. 결과는 버킷 안 분포를 추정한 백분위수이고 관측 0건에서는 NaN일 수 있습니다. 인스턴스별 p95의 평균으로 전체 p95를 계산하지 않습니다.


@Counted·@Timed 프록시

애노테이션 계측은 업무 코드 침투를 줄이지만 애스펙트 빈, 프록시 적용 가능성, 포인트컷, 내부 호출 제약을 가집니다.

애노테이션 이름과 태그 규칙을 코드 검토하고 실패·예외 태그 값이 제한적인지 확인합니다.

src/main/java/board/metrics/MeasuredImportService.java
package board.metrics;

import io.micrometer.core.annotation.Counted;
import io.micrometer.core.annotation.Timed;
import org.springframework.stereotype.Service;

@Service
public class MeasuredImportService {
    @Counted(value = "board.import.invocations", extraTags = {"format", "markdown"})
    @Timed(value = "board.import.duration", extraTags = {"format", "markdown"})
    public int importMarkdown(String body) {
        if (body.isBlank()) {
            throw new IllegalArgumentException("empty markdown");
        }
        return body.lines().mapToInt(line -> 1).sum();
    }
}

같은 이벤트에 수동 카운터와 애노테이션 카운터를 동시에 적용해 두 배 기록하지 않습니다.

자기 호출과 직접 new 테스트에서 애노테이션이 실행되지 않는 신호를 별도 런타임 테스트로 둡니다.


Grafana 패널 의미

“주문 수” 패널 대신 “최근 5분의 허용 비율은 기준선에서 벗어났는가”처럼 답하려는 질문을 제목으로 씁니다.

쿼리, 범례, 단위, 최솟값·최댓값, 임계값, 대응 절차 링크를 버전 관리합니다.

바이트를 십진 또는 이진 단위 중 무엇으로 표시하는지 정하고 지연 시간의 초를 밀리초처럼 잘못 표시하지 않습니다.

0과 데이터 없음은 다릅니다.

수집이 끊긴 패널을 0으로 채우면 트래픽이 사라진 장애를 정상 유휴로 오해합니다.

up, 표본 경과 시간, 애플리케이션 메트릭을 함께 배치하고 배포 애노테이션으로 변화 시점을 표시합니다.


수집 실패 진단

빈 Grafana panel 진단
application registry has meter=false -> INSTRUMENTATION_MISSING
prometheus endpoint lacks sample -> EXPORT_FILTER_OR_NAMING
target up=0 -> SCRAPE_AUTH_NETWORK_FAILURE
Prometheus query returns series=true, panel empty=true -> GRAFANA_QUERY_OR_TIME_RANGE
sample age > 2 * scrape interval -> STALE_DATA

대시보드만 새로고침하지 말고 애플리케이션 레지스트리부터 노출 형식, 대상, 원시 쿼리, 패널 변환 순으로 추적합니다.


연습 문제

회원 게시판 허용·거부 카운터와 가져오기 타이머를 Prometheus로 수집하세요.

최근 5분 비율, 1시간 increase, p95 지연 시간을 Grafana 패널로 만들고 인스턴스 초기화와 대상 중단을 재현하세요.

애노테이션 방식은 프록시 호출과 자기 호출에서 미터 개수가 어떻게 다른지 검증하세요.

해설 보기

패널을 만들기 전에 원시 PromQL 결과와 레이블 집합을 확인합니다.

카운터에는 초기화를 처리하는 함수를 사용하고, 타이머에는 여러 인스턴스를 집계할 수 있는 히스토그램을 사용합니다.

package board.metrics;

import java.time.Duration;

public record ScrapeFreshness(Duration interval, Duration age) {
    public boolean stale() {
        return age.compareTo(interval.multipliedBy(2)) > 0;
    }

    public String displayState() {
        return stale() ? "NO_CURRENT_DATA" : "CURRENT_DATA";
    }
}

트래픽 0과 수집 중단을 같은 색으로 표시하지 않습니다.

독립 확인: 원 ScrapeFreshness에 interval=15초를 넣습니다. age=30초와 31초의 displayState를 구하세요. 앞 비율 쿼리에서 허용 6건/초·거부 4건/초일 때 percent 표시와, 두 결과가 모두 0일 때의 의미도 쓰세요.

풀이 확인

30초는 CURRENT_DATA, 31초는 NO_CURRENT_DATA입니다. 원 비교는 두 간격보다 큰 경우만 stale입니다. 비율은 6/(6+4)=0.6이므로 60%입니다. 두 증가율 모두 0인 상황은 분모 0이라 비율을 계산할 수 없으며, 수집 상태와 절대 트래픽을 확인해 무트래픽인지 데이터 공백인지 구분합니다.