MeetBack 2026.08.28 6 min read

Prometheus 설정은 맞는데 Target이 없었던 이유

설정 검사와 reload가 성공했는데 Prometheus 대상은 비어 있었다. 호스트와 컨테이너의 파일 해시를 비교해 bind mount 문제를 찾고 설정 검증 순서를 수정했다.

프로젝트 기간: 2026-08-18 ~ 2026-09-02
글 범위: Prometheus·Grafana·Loki·Alloy 모니터링

MeetBack의 모니터링은 Prometheus, Grafana, Alertmanager, Loki, Alloy로 구성했다. 장애가 났을 때 노드, DB, 저장소, 컨테이너 중 어디부터 확인할지 알 수 있도록 필요한 정보를 모았다.

구성 요소는 모니터링 서버의 compose.yaml에서 관리했다. 이 환경의 실제 파일명이 docker-compose.yml이 아닌 compose.yaml이라 운영 명령과 문서도 이에 맞췄다.

어떤 지표를 수집했는가

수집 대상을 역할별로 나눴다.

node_exporter
  → CPU, memory, disk, filesystem, network, systemd/NFS 상태

mysqld_exporter
  → MySQL up, connection, replication, read-only, process 상태

cAdvisor
  → container CPU, memory, network, filesystem

Prometheus self metrics
  → scrape, rule evaluation, storage 상태

Ubuntu 노드 전체에 node_exporter를 설치했다. DB는 전용망 주소로 수집하고 망 전환이 끝난 뒤 예전 서비스망 주소를 대상에서 제거했다. 같은 서버가 두 주소로 남으면 장애가 아닌데도 중단된 대상으로 보이거나 역할이 중복될 수 있었다.

Exporter는 모두 열리는데 Target 목록은 비어 있었다

노드의 /metrics를 직접 curl로 호출하면 응답했다. promtool check config도 통과했고 reload API는 HTTP 200이었다. 그런데 Prometheus API에서 node-exporter 작업을 조회하면 수집 대상이 없었다.

처음에는 YAML 들여쓰기와 레이블 구조를 확인했다. 대상과 레이블을 잘못 붙여 쓴 적이 있어 파일도 다시 정리했다. 그래도 호스트 파일은 문법 검사를 통과하는데 실행 중인 설정에는 작업이 나타나지 않았다.

설정이 적용되는 과정을 아래 세 단계로 나눠 확인했다.

1. Exporter endpoint가 응답하는가
2. Prometheus가 어떤 configuration을 실제로 읽었는가
3. 그 configuration의 target scrape가 healthy인가

Exporter에 직접 요청한 결과는 첫 번째 단계만 확인한 것이었다. 실제로 읽은 설정과 수집 상태는 각각 API에서 조회했다.

curl -fsS http://127.0.0.1:9090/api/v1/status/config
curl -fsS http://127.0.0.1:9090/api/v1/targets

Host와 Container의 같은 경로가 다른 파일이었다

호스트 설정 파일을 Prometheus 컨테이너에 bind mount한 상태였다. 호스트 파일에는 새 대상이 있었지만 컨테이너 내부 파일에서는 찾을 수 없었다. 두 파일의 SHA-256 해시를 비교하니 값이 달랐다.

sha256sum /opt/meetback-monitoring/prometheus/prometheus.yml
docker exec <prometheus> sha256sum /etc/prometheus/prometheus.yml

편집기가 새 파일을 만든 뒤 이름을 바꿔 기존 파일을 교체한 것이 원인이었다. bind mount 중인 컨테이너는 교체 전 inode를 계속 참조할 수 있다. 호스트 경로에는 새 내용이 보이지만 컨테이너는 이전 내용을 읽고 있었다.

/-/reload는 컨테이너가 참조하는 파일을 다시 읽는다. 새 inode로 마운트를 다시 연결하지 않으므로 HTTP 200이 나와도 호스트의 최신 파일을 읽었다고 볼 수 없었다.

아래 명령으로 컨테이너를 재생성해 새 파일을 연결했다.

docker compose -f compose.yaml up -d --force-recreate prometheus

재생성 후 양쪽 파일 해시가 같아졌다. 실행 중인 설정에도 작업이 나타났고 전체 수집 대상이 UP으로 바뀌었다.

설정 변경 절차를 고정했다

이후 설정을 바꿀 때는 아래 순서로 확인했다.

1. 기존 설정 backup
2. YAML 수정
3. promtool check config
4. host와 container file hash 비교
5. hash가 같으면 lifecycle reload
6. 다르면 container recreate
7. loaded config 확인
8. active target과 lastError 확인
9. alert rule health 확인

문법 검사, 파일 연결 상태, 실제 로드한 설정, 수집 결과를 각각 확인하도록 절차에 넣었다.

Grafana dashboard는 장애 확인 순서에 맞췄다

대시보드는 장애가 났을 때 확인할 순서에 맞춰 배치했다.

첫 줄에서 전체 대상과 노드 상태를 보고, 이어서 CPU·메모리·파일시스템을 확인하게 했다. MySQL은 Primary/Replica 역할과 IO/SQL 스레드, 복제 지연, 연결 수, 읽기 전용 상태를 모았다. NFS는 서비스 상태와 실제 /srv/nfs 마운트, 읽기 전용 여부, 용량·inode를 나눠 봤다.

NFS 서비스가 실행 중이어도 데이터 볼륨이 마운트되지 않을 수 있다. 그때 App이 마운트 지점의 로컬 디렉터리에 쓰면 파일 저장 위치가 달라진다. 이를 구분하려고 프로세스 실행과 실제 마운트 존재 여부에 별도 알림을 뒀다.

Loki와 Alloy를 함께 둔 이유

Prometheus에는 수치로 집계할 지표를 모으고 Loki에서는 로그를 검색하도록 했다. Alloy는 호스트와 컨테이너에서 수집한 정보를 해당 저장소로 보내는 에이전트로 사용했다.

이 단계에서는 Loki와 Alloy 서비스가 실행되는 것까지 확인했다. 실제 Spring Boot 로그 연결은 App 배포 후 작업이었다. 구성 요소의 UP 상태와 업무 로그의 전체 수집 경로는 구분해 기록했다.

현재 상태와 남은 검증

구축 시점에 Prometheus 대상 23개가 모두 UP이었고 알림 규칙 21개는 health=ok였다. 이 숫자는 당시 점검 결과이며 수집 대상과 서비스가 추가되면 달라진다.

App 지표와 로그, 요청 지연과 오류 비율, 실제 사용자 동작 점검이 남아 있었다. 서버 지표만으로는 사용자가 정상적으로 약속을 만들 수 있는지 확인하기 어려웠다.

이번 문제는 실행 중인 컨테이너가 어느 파일을 읽는지 비교하면서 해결했다. reload의 HTTP 200, Exporter의 HTTP 200, 수집 대상의 UP은 각각 다른 단계의 결과였다. 이후 설정 변경 절차에서도 이 세 가지를 따로 확인했다.

SEARCH JOURNAL

기록 검색

글 제목, 요약, 태그에서 찾습니다.

검색을 준비하고 있어요.

검색어를 입력해 주세요.

    Ctrl/⌘ K 검색 Esc 닫기 ↑ ↓ 결과 이동