Infra

[Prometheus] Docker 컨테이너 자원 cgroup v2 직독 cAdvisor 대체

jykim23 2026. 6. 25. 21:35
반응형

컨테이너별 메모리·CPU를 모니터링하려고 했다. 보통은 cAdvisor 같은 익스포터를 붙이는데, 이 호스트에선 그게 잘 안 맞았다. 그래서 커널의 cgroup을 직접 읽기로 했다.

 

왜 cAdvisor를 안 썼나

이 호스트는 컨테이너 이미지 저장에 containerd-snapshotter(overlayfs)를 쓴다. 이 조합에서 cAdvisor는 컨테이너를 제대로 못 봤다.

  • 도커 방식으로 조회하면 컨테이너를 0개로 인식했다.
  • containerd 방식으로 조회하면 이름이 해시로 잡혀, compose 서비스 라벨이 사라졌다. 서비스 단위(api, postgres …)로 묶어 보는 대시보드를 만들 수가 없다.

새 컨테이너(cAdvisor 등)를 여럿 띄우는 것도 부담이라, 작은 수집기 하나만 추가해서 cgroup을 직접 읽기로 했다.

cgroup이란

cgroup(control groups)은 리눅스 커널이 프로세스 그룹(여기선 컨테이너)의 자원 사용을 격리하고 집계하는 기능이다. 요즘 배포판이 쓰는 cgroup v2는 이 정보를 파일시스템처럼 노출한다. 컨테이너 하나가 이런 경로에 대응된다.

/sys/fs/cgroup/system.slice/docker-<컨테이너ID>.scope/

이 디렉터리 안의 파일을 그냥 cat하면 값이 나온다.

파일내용

memory.current 현재 메모리 사용량(바이트)
memory.peak 최고치 메모리(바이트)
cpu.stat 누적 CPU 시간 (usage_usec 등, 마이크로초)
memory.events OOM 이벤트(oom_kill 카운트)

커널이 실시간으로 갱신해 주는 값이라, 별도 에이전트 없이 읽기만 하면 된다.

어떻게 내보내나

수집기는 60초마다 두 가지를 한다. 실행 중인 컨테이너 목록을 가져오고(라벨도 같이), 각 컨테이너의 cgroup 파일을 읽는다. 읽은 값을 Prometheus가 이해하는 텍스트 형식으로 파일에 쓴다.

cg="/sys/fs/cgroup/system.slice/docker-${cid}.scope"
mem="$(cat "$cg/memory.current")"
printf 'docker_container_memory_bytes{name="%s",service="%s"} %s\n' "$name" "$service" "$mem"

cpu_usec="$(awk '/^usage_usec /{print $2}' "$cg/cpu.stat")"
# 마이크로초 → 초
printf 'docker_container_cpu_usage_seconds_total{name="%s",service="%s"} %s\n' \
  "$name" "$service" "$(awk "BEGIN{printf \"%.6f\", $cpu_usec/1000000}")"

이 파일을 node-exporter의 textfile collector가 주워 가고, Prometheus가 그걸 긁어 간다. 새 scrape 대상도, 새 포트도 필요 없다. 기존 수집 경로에 파일 하나만 얹는 구조다.

라벨은 수집기가 직접 붙인다. compose 서비스명을 라벨로 달아두면, 배포할 때마다 컨테이너 이름이 바뀌어도 서비스 단위로 묶어 볼 수 있다. (cAdvisor에서 잃었던 바로 그 라벨이다.)

서비스가 통째로 사라지는 경우

한 가지 사각지대가 있었다. 컨테이너를 docker rm으로 지워 버리면, 그 컨테이너의 메트릭 자체가 사라진다. 값이 0이 되는 게 아니라 시리즈가 없어지는 거라, "running == 0" 같은 조건에도 안 걸린다. 아무도 다시 띄우기 전까지 조용히 방치된다.

그래서 "있어야 할 서비스 목록"을 따로 만들어 뒀다. compose 정의에서 재시작 정책이 있는 서비스만 뽑아 기대 목록으로 파일에 적고, 배포할 때마다 갱신한다. 그러면 "기대 목록엔 있는데 떠 있는 게 없는" 서비스를 잡아낼 수 있다.

# 기대되는데 실행 중인 컨테이너가 0개인 서비스
monitoring_expected_service == 1
  unless on(service) (sum by (service)(docker_container_running) > 0)

정리

  • 특정 호스트 환경(containerd-snapshotter/overlayfs)에선 cAdvisor가 컨테이너를 못 보거나 라벨을 잃는다
  • cgroup v2는 자원 사용량을 파일로 노출한다 — cat만으로 메모리·CPU·OOM을 읽을 수 있다
  • node-exporter textfile collector에 얹으면 새 scrape 대상 없이 수집 경로를 재사용한다
  • docker rm처럼 시리즈가 통째로 사라지는 부재는 "있어야 할 목록"을 따로 둬야 잡힌다

도구가 환경에 안 맞을 땐, 그 도구가 대신 읽어 주던 커널의 원본을 직접 읽는 선택지가 있다.

반응형