MeetBack 2026.08.31 5 min read

cAdvisor Dashboard와 Telegram Alert까지 연결한 운영 가시성

Docker host의 cAdvisor 지표를 Grafana Dashboard로 구성하고 MySQL·NFS Alert를 Alertmanager와 Telegram으로 연결한 과정과 관련 문제를 정리했다.

프로젝트 기간: 2026-08-18 ~ 2026-09-02
글 범위: cAdvisor Dashboard와 Alertmanager Telegram 알림

node_exporter만으로는 host 전체의 CPU와 memory는 볼 수 있지만 어떤 container가 resource를 쓰는지 알기 어렵다. MeetBack은 Docker Swarm service와 monitoring container를 함께 운영하므로 container 단위 metric이 필요했다.

나는 Docker host마다 cAdvisor를 실행하고 Prometheus target으로 등록했다. Swarm 5개 node에는 global service 성격으로 배치했고, Swarm member가 아닌 monitoring server에는 별도 container로 실행했다.

배포 전에 host 조건을 확인했다

cAdvisor는 Docker metadata, cgroup과 host filesystem 일부를 읽어야 한다. 바로 container를 띄우기 전에 각 host에서 다음을 확인했다.

  • Docker Engine 실행 상태와 root directory
  • cgroup v2 사용 여부
  • /dev/kmsg 접근 가능 여부
  • service address와 사용할 listener port
  • monitoring server에서 endpoint 접근 가능 여부

container에는 필요한 host path를 read-only로 mount하고 restart policy와 healthcheck를 넣었다. endpoint는 내부 service network address에만 publish하고, 최종 firewall 단계에서는 monitoring server만 접근하도록 제한할 계획을 세웠다.

Metric이 보이는데 curl: (23)이 발생했다

cAdvisor 확인 중 첫 metric만 찾기 위해 다음 형태의 pipeline을 사용했다.

curl -fsS http://<node>:8080/metrics |
grep -m 1 '^container_cpu_usage_seconds_total'

metric 한 줄은 정상적으로 출력됐지만 마지막에 다음 오류가 보였다.

curl: (23) Failure writing output to destination

cAdvisor 장애처럼 보였지만 원인은 pipeline이었다. grep -m 1은 첫 일치 후 즉시 종료한다. 뒤에서 계속 response를 쓰던 curl은 닫힌 pipe에 쓰려다 error 23을 반환한다.

나는 service health와 metric 존재를 분리해서 확인했다.

curl -fsS http://<node>:8080/healthz
curl -fsS http://<node>:8080/metrics -o /tmp/cadvisor.metrics
grep -m 1 '^container_cpu_usage_seconds_total' /tmp/cadvisor.metrics

container health는 healthy, /healthzok, metric file에도 series가 있었고 Prometheus target도 UP이었다. stderr의 한 줄만 보고 service를 재시작하지 않은 것이 중요했다.

Container dashboard는 node와 workload를 함께 봤다

Grafana dashboard에서는 다음 정보를 중심으로 구성했다.

  • node별 running container 수
  • container CPU 사용량
  • working set memory와 limit 대비 비율
  • network receive/transmit
  • filesystem read/write
  • restart와 scrape 상태

Swarm service name과 compose label이 없는 system container도 있어 label이 비어 있을 수 있다. dashboard query는 특정 label만 존재한다고 가정하지 않고 name, id, node label을 함께 사용할 수 있게 구성했다.

root cgroup metric은 host 전체와 가까워 application container 집계에 섞이면 이중으로 보일 수 있다. 실제 service container panel에서는 빈 image와 root id를 제외하고, host 관점 panel에서는 별도로 유지했다.

Grafana 비밀번호를 .env에서 바꿔도 401이 계속됐다

Dashboard를 API로 넣고 확인하는 과정에서 .env의 admin password를 변경한 뒤 Grafana container를 restart했다. 하지만 Basic Auth API는 계속 HTTP 401을 반환했다.

원인은 Grafana의 영속 database였다. admin user와 password 환경변수는 일반적으로 최초 database 초기화에 사용된다. 이미 SQLite database가 volume에 존재하면 .env를 바꾸고 restart해도 저장된 account password가 자동으로 덮어써지지 않는다.

나는 container 내부 Grafana CLI로 영속 account password를 변경하고 API health와 search를 다시 확인했다.

docker exec <grafana> \
  grafana cli admin reset-admin-password '<new-secret>'

실제 secret은 문서와 shell history에 남기지 않았다. 이 경험으로 deployment environment와 application database state를 같은 것으로 간주하면 안 된다는 점을 확인했다.

Alert rule을 상태별로 나눴다

Prometheus alert는 세 group으로 구성했다.

Basic
  → scrape target down

MySQL
  → exporter/server down, replication thread, lag,
     read-only role, connection, backup freshness

NFS
  → exporter/service down, mount missing, read-only,
     capacity, inode

warning은 대응 준비가 필요한 상태, critical은 service나 data 경로가 실제로 위험한 상태로 나눴다. 단순 임계치뿐 아니라 일정 시간 지속되는 조건을 사용해 순간 spike가 바로 paging으로 이어지지 않게 했다.

다만 warning과 critical alert name이 서로 다르면 Alertmanager inhibit rule이 생각한 방식으로 중복을 억제하지 못할 수 있다. 실제 alert를 보면서 동일 사건의 label 설계를 더 정리해야 한다.

Telegram credential은 file로 분리했다

Alertmanager receiver는 Telegram으로 구성했다. bot token과 chat ID를 compose.yaml이나 Git에 직접 적지 않고 monitoring host의 제한된 file에 보관했다. container에는 /run/secrets 아래 read-only로 mount하고 Alertmanager configuration에서는 file path만 참조했다.

검증 단계도 나눴다.

  1. Telegram Bot API 직접 message 전송
  2. Alertmanager configuration syntax 검사
  3. 실행 중 Alertmanager의 loaded configuration 확인
  4. receiver가 secret file path를 읽는지 확인
  5. 의도적인 비핵심 target 장애로 pending → firing 확인
  6. target 복구 뒤 resolved 확인

직접 Bot API 전송 성공은 token과 chat이 맞다는 뜻이지 Prometheus에서 Alertmanager까지 전체 경로가 정상이라는 뜻은 아니다. loaded config 역시 실제 condition 발생을 증명하지 않는다. 그래서 각 단계를 별도 증거로 기록했다.

운영 가시성의 현재 한계

현재 node, container, MySQL, NFS의 infrastructure metric과 alert 기반은 구성했다. 그러나 application이 아직 배포 전이므로 다음 항목은 남아 있다.

  • HTTP request latency와 error rate
  • Spring Boot JVM·connection pool metric
  • business transaction failure
  • application log와 trace correlation
  • service replica 감소와 rolling update 영향
  • 사용자 관점 synthetic check

Observability를 구축하며 나는 “dashboard가 있다”보다 “어떤 질문에 답할 수 있는가”를 기준으로 봤다. node가 느린지, container가 resource를 쓰는지, DB replication이 멈췄는지, NFS mount가 사라졌는지를 서로 다른 signal로 찾을 수 있게 한 것이 이번 단계의 결과다.