Docker 서비스 운영을 위해 모니터링을 확장하며 배운 것들
2026-07-04 · INFRA · 약 12분 읽기
기존 Prometheus와 Grafana 기반 모니터링 경험을 바탕으로 Docker 서비스 운영 환경에 Loki, Promtail, Node Exporter, cAdvisor를 붙이며 배운 점을 정리했습니다.
이전에 MySQL을 대상으로 Prometheus와 Grafana를 붙여본 적이 있었습니다.
그때는 mysqld_exporter를 통해 MySQL 지표를 Prometheus가 수집하고, Grafana에서 Dashboard로 확인하는 흐름을 만드는 것이 핵심이었습니다. 그래서 이번 작업을 시작할 때도 처음부터 Grafana나 Prometheus가 낯설지는 않았습니다.
하지만 실제 Docker 서비스 운영 환경에 모니터링을 붙이는 일은 이전 경험과 조금 달랐습니다.
MySQL 모니터링은 비교적 대상이 명확했습니다.
MySQL -> mysqld_exporter -> Prometheus -> Grafana반면 이번에는 운영 중인 Docker 서비스 전체를 봐야 했습니다. 서버 리소스도 봐야 했고, 컨테이너별 리소스도 봐야 했고, 애플리케이션 로그도 한곳에서 검색할 수 있어야 했습니다.
즉, 단일 DB 모니터링에서 Docker 서비스 운영 모니터링으로 범위가 넓어진 작업이었습니다.
기존 방식의 한계는 로그와 메트릭이 분리되어 있다는 점이었다
서비스를 운영하면서 가장 자주 사용한 명령어는 여전히 docker logs였습니다.
docker logs -f my-service서비스가 실행되지 않거나 에러가 발생하면 먼저 로그를 확인했습니다. 프로젝트가 하나일 때는 이 방식도 충분했습니다.
하지만 운영하는 서비스와 컨테이너가 늘어나면서 점점 불편해졌습니다. 문제가 생길 때마다 서버에 SSH로 접속하고, 실행 중인 컨테이너를 확인한 뒤, 관련 있어 보이는 로그를 하나씩 열어봐야 했습니다.
docker ps
docker logs api
docker logs nginx
docker logs worker여기서 가장 불편했던 점은 로그를 보는 행위와 리소스를 보는 행위가 완전히 분리되어 있다는 점이었습니다.
예를 들어 응답 속도가 갑자기 느려졌다고 해보겠습니다. docker logs에서는 에러 로그를 찾을 수 있지만, 그 시점에 CPU가 높았는지, 메모리가 계속 증가하고 있었는지, 네트워크 트래픽이 튀었는지는 바로 알 수 없습니다.
반대로 리소스 사용량만 보면 CPU나 Memory가 높았다는 사실은 알 수 있지만, 그 시점에 어떤 로그가 함께 발생했는지는 다시 따로 찾아봐야 합니다.
결국 이번에 해결하고 싶었던 문제는 단순히 Dashboard를 하나 더 만드는 것이 아니었습니다.
메트릭과 로그를 같은 시간축에서 함께 볼 수 있는 운영 환경을 만드는 것이 목표였습니다.
이번에는 Grafana가 아니라 수집 구조가 핵심이었다
이전 모니터링 글에서는 Grafana datasource 설정, Dashboard 구성, exporter 연결 같은 기본 흐름 자체가 중요한 내용이었습니다.
이번에는 그보다 수집 구조를 어떻게 나눌지가 더 중요했습니다.
처음 정리한 목표는 아래와 같았습니다.
1. 서버의 CPU, Memory, Disk, Network 상태를 확인한다.
2. Docker 컨테이너별 리소스 사용량을 확인한다.
3. Application Log를 중앙에서 검색한다.
4. 장애 시점의 Metric과 Log를 같은 기준으로 추적한다.이를 위해 구성한 역할은 다음과 같았습니다.
Prometheus -> Metric 수집과 저장
Loki -> Log 저장과 검색
Promtail -> Docker Log 수집과 전달
Node Exporter -> 서버 리소스 Metric 노출
cAdvisor -> Docker 컨테이너 Metric 노출
Grafana -> Metric과 Log를 같은 화면에서 확인여기서 새로 중요해진 것은 Loki와 Promtail이었습니다.
이전에는 MySQL 지표만 보면 됐기 때문에 Prometheus와 Grafana만으로도 충분했습니다. 하지만 이번에는 애플리케이션 로그까지 함께 봐야 했습니다. 그래서 로그 저장소로 Loki를 붙이고, Docker 로그를 Loki로 전달하기 위해 Promtail을 추가했습니다.
Monitoring과 Runtime을 분리했다
처음에는 모니터링 도구들을 프로젝트 컨테이너 안에서 함께 실행하는 구조도 생각했습니다.
Project CT
- API
- Nginx
- PostgreSQL
- Grafana
- Prometheus
- Loki한곳에 모두 넣으면 단순해 보였습니다. 하지만 운영 관점에서는 좋은 구조가 아니라고 판단했습니다.
모니터링 시스템도 결국 리소스를 사용합니다. Prometheus는 Metric을 저장하고, Loki는 로그를 저장합니다. Grafana도 메모리를 사용합니다.
서비스와 모니터링 시스템을 같은 CT 안에 두면 실제 서비스와 모니터링 도구가 리소스를 경쟁하게 됩니다.
더 큰 문제는 장애 상황이었습니다. Project CT 자체가 내려가면 서비스뿐 아니라 Grafana와 Loki도 함께 내려갑니다. 정작 모니터링이 가장 필요한 순간에 모니터링 화면을 볼 수 없는 구조가 됩니다.
그래서 Monitoring 전용 CT를 따로 만들고, 실제 서비스가 실행되는 Project CT와 역할을 분리했습니다.
최종 구조는 아래처럼 가져갔습니다.
Monitoring CT
+-------------------------------+
| Grafana |
|-------------------------------|
| Prometheus |
|-------------------------------|
| Loki |
+---------------+---------------+
|
-----------------------------------------
Pull Push
-----------------------------------------
| |
| |
+---------------------------+ +---------------------------+
| Project CT | | Project CT |
|---------------------------| |---------------------------|
| Node Exporter | | Promtail |
| cAdvisor | | Docker Logs |
| Docker Containers | | |
+---------------------------+ +---------------------------+Monitoring CT는 데이터를 저장하고 보여주는 역할만 담당합니다. Project CT는 자신의 상태를 노출하고 로그를 전달하는 역할만 담당합니다.
이렇게 나누면 새로운 프로젝트가 추가되더라도 Project CT에 Node Exporter, cAdvisor, Promtail만 붙이면 기존 Monitoring CT에서 같은 방식으로 수집할 수 있습니다.
이번 구조에서 가장 중요했던 기준은 “설치가 쉬운가”보다 “서비스가 늘어나도 같은 방식으로 확장할 수 있는가”였습니다.
Node Exporter만으로는 부족했다
처음에는 Project CT마다 Node Exporter만 붙이면 어느 정도 충분하지 않을까 생각했습니다.
Node Exporter를 붙이면 서버의 CPU, Memory, Filesystem, Network Interface, Load Average 같은 값은 확인할 수 있습니다.
하지만 Docker 서비스 운영에서는 이것만으로 부족했습니다.
서버 전체 메모리 사용량이 높다는 사실을 알아도, 어떤 컨테이너가 메모리를 많이 쓰는지는 알기 어려웠습니다. CPU 사용량도 마찬가지였습니다. 서버 전체 CPU가 높은 것과 특정 컨테이너가 CPU를 많이 쓰는 것은 운영할 때 완전히 다른 정보입니다.
그래서 cAdvisor를 추가했습니다.
cAdvisor를 붙인 뒤에는 컨테이너별 CPU, Memory, Network 사용량을 나눠서 볼 수 있었습니다. 이때부터 Dashboard가 단순한 서버 리소스 현황판이 아니라, 서비스 단위로 문제를 좁혀가는 도구가 되었습니다.
Node Exporter -> 서버 전체 상태 확인
cAdvisor -> 컨테이너별 상태 확인이 둘은 서로 대체 관계가 아니라 보완 관계에 가까웠습니다.
PromQL은 보여줄 대상을 걸러내는 데 더 많이 썼다
이번 Dashboard를 만들면서 가장 많이 한 일은 새로운 그래프를 많이 추가하는 것이 아니었습니다.
오히려 불필요한 대상을 계속 제외하는 일이었습니다.
처음 컨테이너 메모리 사용량을 조회했을 때는 실제 서비스 컨테이너뿐 아니라 모니터링용 컨테이너와 빌드용 컨테이너까지 함께 표시되었습니다.
buildx_buildkit
cadvisor
node-exporter
promtail운영 중인 API나 Nginx 상태를 보고 싶은데, 이런 컨테이너들이 함께 보이면 Dashboard가 금방 복잡해졌습니다.
그래서 PromQL에서 실제로 보고 싶은 컨테이너만 남기도록 정리했습니다.
container_memory_usage_bytes{
image!="",
name!~".*(cadvisor|node-exporter|buildx).*"
}CPU도 같은 기준으로 구성했습니다.
rate(container_cpu_usage_seconds_total{
image!="",
name!~".*(cadvisor|node-exporter|buildx).*"
}[5m])처음에는 순간 CPU 사용량을 그대로 보려고 했습니다. 하지만 그래프가 너무 심하게 흔들려서 실제 추세를 판단하기 어려웠습니다.
그래서 rate()를 사용해 일정 시간 동안의 변화량을 보도록 바꿨습니다. 운영할 때는 순간적인 Spike보다 일정 시간 동안 계속 높게 유지되는지가 더 중요했기 때문입니다.
Network도 컨테이너 단위로 확인했습니다.
rate(container_network_receive_bytes_total{
image!="",
name!~".*(cadvisor|node-exporter|buildx).*"
}[5m])이전 MySQL 모니터링에서는 특정 exporter가 제공하는 DB 지표를 보는 것이 핵심이었다면, 이번에는 여러 컨테이너 중에서 운영 판단에 필요한 대상만 남기는 것이 더 중요했습니다.
Loki를 붙인 뒤 확인 순서가 달라졌다
이번 작업에서 가장 체감이 컸던 부분은 Loki였습니다.
이전에는 Dashboard에서 이상 징후를 발견해도 다시 서버에 접속해야 했습니다.
ssh project-server
docker logs api
docker logs nginx
docker logs worker그리고 Dashboard에서 본 시간대와 로그의 시간대를 맞춰가며 원인을 찾아야 했습니다.
Loki를 붙인 뒤에는 Grafana Explore에서 바로 같은 시간대의 로그를 볼 수 있었습니다.
예를 들어 Error 로그만 보고 싶다면 아래처럼 검색할 수 있었습니다.
level="error"특정 컨테이너만 좁혀서 볼 수도 있고, 장애가 발생한 시간대로 범위를 제한해서 볼 수도 있었습니다.
이전에는 “리소스 이상 확인 → SSH 접속 → docker logs 확인 → 시간대 맞추기” 순서였다면, 이제는 “Metric 확인 → 같은 시간대 Log 검색”으로 흐름이 줄었습니다.
이 차이가 생각보다 컸습니다.
운영에서 중요한 것은 정보를 많이 모으는 것보다, 문제가 생겼을 때 확인 순서를 줄이는 것이라는 생각이 들었습니다.
구축하면서 막혔던 지점들
이번에도 큰 구조보다 작은 설정에서 시간을 많이 썼습니다.
1. Dashboard에 불필요한 컨테이너가 계속 보였다
처음에는 모든 Docker 컨테이너가 그래프에 표시되었습니다.
서비스 컨테이너만 보고 싶은데 cadvisor, node-exporter, promtail, buildx_buildkit 같은 컨테이너도 함께 표시되었습니다.
처음에는 Panel을 나누는 방식도 생각했지만, 결국 Query 단계에서 제외하는 것이 더 깔끔했습니다.
이후 Dashboard에서 실제 서비스 상태만 빠르게 확인할 수 있게 되었습니다.
2. Loki가 Volume 권한 문제로 실행되지 않았다
Loki를 처음 실행했을 때 정상적으로 올라오지 않았습니다.
처음에는 Docker Compose 설정 문제라고 생각했습니다. 하지만 확인해보니 Loki가 사용하는 Volume 권한이 맞지 않았습니다.
Loki는 로그 데이터를 디스크에 저장합니다. 따라서 실행 계정이 해당 디렉터리에 접근할 수 있어야 합니다.
Volume 권한을 수정한 뒤 정상적으로 실행되었습니다.
이번 경험을 통해 로그 수집 시스템에서는 컨테이너 설정뿐 아니라 저장 경로 권한도 함께 봐야 한다는 점을 다시 확인했습니다.
3. Grafana에서 로그가 안 보인다고 Grafana 문제가 아니었다
처음에는 Grafana Explore에서 로그가 보이지 않으면 Grafana 설정을 먼저 의심했습니다.
하지만 실제 흐름은 아래와 같았습니다.
Docker Logs
↓
Promtail
↓
Loki
↓
GrafanaGrafana는 마지막에 보여주는 역할만 합니다. 중간에서 Promtail이 로그를 읽지 못하거나, Loki로 전송하지 못하면 Grafana에서는 당연히 로그가 보이지 않습니다.
이 구조를 이해한 뒤에는 확인 순서가 바뀌었습니다.
1. Docker 로그가 실제로 쌓이고 있는지 확인
2. Promtail이 해당 로그 파일을 읽고 있는지 확인
3. Loki로 전송되고 있는지 확인
4. Grafana Explore에서 조회 조건이 맞는지 확인문제를 추적하는 순서가 명확해진 것이 가장 큰 수확이었습니다.
좋은 Dashboard는 정보를 줄여주는 Dashboard였다
처음에는 더 많은 Panel을 만들수록 좋은 Dashboard라고 생각했습니다.
하지만 실제 운영에서는 반대에 가까웠습니다. 너무 많은 그래프가 있으면 오히려 어디를 봐야 할지 헷갈렸습니다.
운영하면서 필요 없는 Panel은 제거했고, 자주 확인하는 Metric은 위쪽으로 올렸습니다. 장애를 분석할 때 실제로 도움이 되었던 값만 남겼습니다.
결국 좋은 Dashboard는 화려한 Dashboard가 아니라, 지금 어디가 이상한지 빠르게 좁혀주는 Dashboard였습니다.
이번에 남긴 기준은 단순했습니다.
1. 서버 전체에 문제가 있는가?
2. 특정 컨테이너에 문제가 있는가?
3. 같은 시간대에 에러 로그가 있는가?
4. 배포 이후 특정 지표가 달라졌는가?이 네 가지 질문에 빠르게 답할 수 있으면 운영 Dashboard로 충분히 가치가 있다고 느꼈습니다.
회고
이번 작업은 Prometheus와 Grafana를 처음 배운 경험은 아니었습니다.
이미 이전에 MySQL 모니터링을 구성하면서 exporter, datasource, Dashboard 흐름은 한 번 경험했습니다. 그래서 이번 글에서는 도구 자체보다 운영 환경에서 모니터링 범위가 어떻게 달라지는지를 더 많이 배웠습니다.
단일 DB 지표를 보는 것과 Docker 서비스 전체를 보는 것은 달랐습니다.
서버 전체 상태, 컨테이너별 상태, 애플리케이션 로그가 함께 있어야 실제 장애 상황을 이해할 수 있었습니다.
특히 Loki와 Promtail을 붙이면서 “로그를 중앙에서 본다”는 것의 의미를 체감했습니다. 단순히 docker logs를 대체하는 것이 아니라, Metric과 Log를 같은 시간축에서 이어서 볼 수 있게 된 것이 가장 큰 변화였습니다.
아직 남은 작업도 있습니다.
- Alertmanager를 이용한 장애 알림
- Prometheus Recording Rule 적용
- Log Retention 정책 구성
- Reverse Proxy와 HTTPS 환경 모니터링
- OpenTelemetry를 활용한 Trace 수집하지만 최소한 여러 Docker 서비스를 운영하기 위한 관찰 기반은 만들었습니다.
이번 작업은 “Grafana를 설치했다”가 아니라, 기존 모니터링 경험을 실제 Docker 운영 환경에 맞게 확장해본 과정이었습니다.
긴 글 읽어주셔서 감사합니다.
같은 카테고리의 글
INFRA 글 더 읽기
이전 글
배포만 하던 개발자가 홈서버를 구축하며 배운 것들
다음 글
이어지는 글이 없습니다