Engineering Note
JVM 메트릭만으로는 컨테이너를 설명할 수 없다
Pletor가 공개한 node-metrics-agent를 소개합니다. Java 애플리케이션에서 host와 container node metric을 JMX로 노출하고 JMX exporter로 Prometheus에 연결하는 방법을 정리합니다.
Java 애플리케이션을 컨테이너로 운영할 때 JVM 메트릭은 중요합니다. heap, GC, thread, class loading, safepoint 같은 신호는 여전히 애플리케이션 상태를 읽는 기본 재료입니다.
하지만 컨테이너 안에서 발생하는 문제는 JVM 안에서만 설명되지 않는 경우가 많습니다.
JVM은 괜찮아 보이는데 컨테이너가 느리다.
heap은 남아 있는데 cgroup memory pressure가 올라간다.
애플리케이션 지연은 늘었는데 원인은 filesystem, fd, I/O 쪽에 있다.
이런 상황에서는 JVM 메트릭과 함께 실행 환경의 신호가 필요합니다. 그래서 Pletor는 node-metrics-agent를 오픈소스로 공개했습니다.
node-metrics-agent는 Java 애플리케이션에 붙이는 lightweight JVM agent입니다. 애플리케이션 프로세스 안에서 host와 container node metric을 읽고, 이를 JMX MBean으로 등록합니다. 자체 HTTP /metrics endpoint를 열지는 않습니다. Prometheus로 수집할 때는 Prometheus JMX exporter와 함께 쓰는 흐름을 권장합니다.
왜 JVM 바깥의 신호가 필요한가
컨테이너로 배포된 Java 애플리케이션을 볼 때 자주 빠지는 구멍이 있습니다.
JVM 메트릭은 JVM 내부를 잘 보여줍니다. 하지만 애플리케이션이 실제로 놓인 실행 환경까지 충분히 설명하지는 못합니다.
예를 들어 이런 질문이 생깁니다.
- 컨테이너에서 보이는 CPU load는 어떤가?
- cgroup memory 사용량은 JVM heap과 어떻게 다르게 움직이는가?
- filesystem path별 여유 공간은 충분한가?
- open file descriptor는 한계에 가까워지고 있는가?
- process 주변의 I/O rate가 애플리케이션 지연과 같이 움직이는가?
- agent 자체가 metric 수집 중 과부하를 만들고 있지는 않은가?
기존에는 이런 신호를 얻기 위해 node_exporter를 별도 프로세스, sidecar, DaemonSet 같은 형태로 붙이는 경우가 많았습니다. 이 방식은 여전히 유효합니다. 특히 host 전체 관측이나 Kubernetes node 단위 관측에는 node_exporter가 더 자연스러운 선택일 수 있습니다.
하지만 Java 애플리케이션 컨테이너 하나를 기준으로 보면, 별도 프로세스를 하나 더 붙이는 일이 항상 편한 것은 아닙니다. JVM 메트릭은 JMX exporter로 보고 있는데, node에 가까운 일부 metric은 다른 exporter에서 보고, dashboard와 alert rule에서 다시 합쳐야 합니다.
node-metrics-agent는 이 간격을 줄이려는 도구입니다.
node-metrics-agent가 하는 일
node-metrics-agent는 JVM agent로 애플리케이션에 attach됩니다.
java \
-javaagent:/path/to/node-metrics-agent-0.8.0-all.jar=/path/to/node-metrics.yml \
-jar your-app.jar
agent는 JVM 안에서 node/container 관련 정보를 읽고 JMX MBean으로 노출합니다. README 기준 현재 버전은 0.8.0이고, runtime target은 Java 11+입니다. build에는 JDK 21이 필요합니다.
등록되는 MBean은 다음 범위입니다.
| MBean | 의미 |
|---|---|
co.pletor.node:type=CpuMetrics | CPU 관련 node metric |
co.pletor.node:type=MemMetrics | memory 관련 node metric |
co.pletor.cgroup:type=MemMetrics | cgroup memory metric |
co.pletor.proc:type=FdMetrics | process file descriptor metric |
co.pletor.node:type=IoRates | I/O rate metric |
co.pletor.node:type=OsInfoMetrics | OS 정보 |
co.pletor.node:type=OsRuntimeMetrics | OS runtime metric |
co.pletor.node:type=FsMetrics,path=<configured path> | 설정된 path별 filesystem metric |
co.pletor.agent:type=TelemetryMode | agent telemetry mode |
co.pletor.agent:type=Observability | agent 내부 queue, drop, latency, staleness 관측 |
중요한 점은 이것이 “JVM 내부 메트릭을 조금 더 늘리는 도구”가 아니라는 것입니다. 애플리케이션이 놓인 컨테이너 실행 환경의 일부를 JMX 관측 흐름 안으로 가져오는 도구입니다.
JMX exporter와 함께 쓰는 형태
node-metrics-agent는 자체 HTTP endpoint를 열지 않습니다. 이 선택은 의도적입니다. 이미 Java 애플리케이션에서 JMX exporter를 쓰고 있다면 같은 흐름으로 metric을 노출할 수 있습니다.
대표적인 실행 형태는 다음과 같습니다.
java \
-javaagent:/opt/jmx-exporter/jmx_prometheus_javaagent.jar=9404:/opt/jmx-exporter/pletor-node-metrics.yml \
-javaagent:/opt/pletor/node-metrics-agent-0.8.0-all.jar=/opt/pletor/node-metrics.yml \
-jar your-app.jar
이렇게 구성하면 애플리케이션 JVM에는 두 agent가 붙습니다.
- JMX exporter는 JMX MBean을 Prometheus scrape endpoint로 바꿉니다.
node-metrics-agent는 node/container metric을 JMX MBean으로 등록합니다.
결과적으로 Prometheus에서는 pletor_* prefix의 metric을 수집할 수 있습니다. README의 예시는 다음과 같습니다.
pletor_node_cpumetrics_systemcpuload
pletor_node_memmetrics_availablememorybytes
pletor_cgroup_memmetrics_memoryusagebytes
pletor_proc_fdmetrics_openfiledescriptorcount
pletor_agent_observability_queuefillratio
JMX exporter를 이미 표준 수집 경로로 쓰는 Java 서비스라면, 별도 HTTP exporter 프로세스를 더 붙이지 않고도 필요한 node/container metric을 같은 scrape 흐름에 태울 수 있습니다.
컨테이너에서 특히 유용한 이유
컨테이너 환경에서는 애플리케이션이 보는 “노드”가 물리 host 전체와 같지 않을 수 있습니다. namespace, cgroup, mounted filesystem, container runtime 설정에 따라 애플리케이션이 체감하는 환경이 달라집니다.
이때 유용한 질문은 “host 전체가 어떤가”가 아니라 “이 JVM이 놓인 실행 환경이 어떤 제약을 받고 있는가”입니다.
node-metrics-agent가 특히 맞는 경우는 이런 상황입니다.
- Java 애플리케이션을 컨테이너로 배포합니다.
- 이미 JMX exporter로 JVM metric을 수집합니다.
- 애플리케이션 컨테이너 안에서 보이는 CPU, memory, cgroup memory, fd, filesystem, I/O 신호를 함께 보고 싶습니다.
- 별도 node_exporter sidecar를 붙이기보다 JVM agent 형태가 배포와 운영에 더 단순합니다.
- Prometheus metric 이름을
pletor_*prefix로 분리해 dashboard와 alert rule에서 관리하고 싶습니다.
반대로 모든 host metric을 완전히 대체하려는 목적이라면 조심해야 합니다. host 전체 관측, network device 세부 metric, 여러 process를 포괄하는 node 단위 수집에는 기존 node_exporter나 Kubernetes/cAdvisor 계열 metric이 더 적합할 수 있습니다.
node-metrics-agent의 자리는 조금 다릅니다.
애플리케이션 JVM이 자기 주변 실행 환경을 JMX로 말하게 한다.
production JVM에 붙일 때 중요한 점
운영 JVM에 agent를 붙이는 일은 늘 조심스럽습니다. metric을 얻으려고 붙인 agent가 애플리케이션 hot path를 흔들면 안 됩니다.
node-metrics-agent는 README에서 fail-open posture를 명시합니다.
- bounded asynchronous refresh pipeline
- overload mode:
NORMAL,DEGRADED,BYPASS - filesystem metric 설정의 runtime reload
- queue pressure, drops, latency, staleness를 위한 self-observability MBean
이 방향은 마음에 듭니다. metric agent는 “항상 정확한 값을 무조건 계산한다”보다 “애플리케이션을 방해하지 않는 범위에서 관측을 유지한다”가 더 중요할 때가 많습니다. 특히 컨테이너에서 filesystem path를 여러 개 보거나, I/O 주변 신호를 읽거나, refresh 주기가 짧아질 때는 agent 자체의 압력을 함께 봐야 합니다.
그래서 co.pletor.agent:type=Observability 같은 agent self metric은 부록이 아닙니다. 운영에서는 이 값도 같이 봐야 합니다.
설정 파일과 filesystem metric
filesystem metric은 path 설정이 중요합니다. 모든 path를 무작정 훑기보다, 애플리케이션이 실제로 쓰는 mount path를 명확히 지정하는 편이 좋습니다.
예시는 단순합니다.
fsmetrics_max_partitions: 32
fsmetrics_paths:
- /data/kafka-logs
- /boot
JAR는 config 생성 CLI도 제공합니다.
java -jar node-metrics-agent-0.8.0-all.jar init-config --fs-path /data,/var
java -jar node-metrics-agent-0.8.0-all.jar init-kafka-config \
--server-properties /opt/kafka/config/server.properties
Kafka처럼 storage path가 명확한 Java 기반 시스템에서는 이런 설정 생성이 특히 유용할 수 있습니다. 다만 생성된 설정은 그대로 끝이 아니라 운영 환경의 mount, volume, container path와 맞는지 한 번 더 확인해야 합니다.
운영에서 볼 체크리스트
도입할 때는 다음을 확인하면 좋습니다.
| 항목 | 확인할 것 |
|---|---|
| Java version | runtime Java 11+를 만족하는가 |
| exporter 구성 | JMX exporter와 함께 실행할 것인가 |
| port | JMX exporter HTTP port가 기존 port와 충돌하지 않는가 |
| filesystem path | 실제 container mount path를 보고 있는가 |
| cardinality | path별 MBean과 Prometheus label이 과도하게 늘지 않는가 |
| self-observability | queue fill, drop, latency, staleness를 같이 보는가 |
| fallback | agent 문제 시 애플리케이션 영향 없이 지나가는가 |
| 기존 node metric | node_exporter, cAdvisor, kubelet metric과 역할이 겹치지 않게 정리했는가 |
이런 운영 관점은 Konduo에서도 중요하게 다룹니다. Konduo는 Kafka, PostgreSQL, host 같은 운영 대상을 플러그인으로 연결하고, 리소스 상태와 메트릭 근거, 알림 대응을 하나의 흐름에서 볼 수 있도록 설계된 통합 관리 운영 플랫폼입니다. JVM 내부 지표와 컨테이너 실행 환경 지표를 함께 해석해야 하는 상황에서도 이런 연결된 관점이 필요합니다.
결론
컨테이너에서 Java 애플리케이션을 운영할 때 JVM 메트릭은 충분조건이 아닙니다. JVM이 건강해 보여도 cgroup memory, filesystem, fd, I/O, OS runtime 신호가 애플리케이션의 실제 상태를 설명할 수 있습니다.
node-metrics-agent는 그 신호를 Java 애플리케이션의 관측 흐름 안으로 가져오는 작은 도구입니다.
JVM 안의 메트릭과 JVM 바깥의 실행 환경 메트릭을
같은 JMX/Prometheus 경로로 연결한다.
Java 컨테이너를 운영하면서 “JVM은 괜찮아 보이는데 왜 서비스는 느리지?”라는 질문을 자주 만난다면, 이제 애플리케이션이 자기 주변 환경을 직접 말하게 만들 수 있습니다.
함께 읽기 좋은 글
이 글의 관점과 이어지는 주제를 더 보려면 아래 글도 함께 읽어볼 수 있습니다.
- Kafka는 메모리가 남아 있는데 왜 OOM이 났을까 - heap 밖 direct buffer까지 포함해 Kafka broker 메모리를 다시 봅니다.
- Kafka Broker가 느려졌지만 Kafka가 원인은 아니었다 - Kafka 증상 뒤에 숨어 있던 storage path와 I/O 문제를 추적합니다.
- 컨테이너 설정 주입의 마지막 한 끗: env에서 config로 - 컨테이너 환경 변수에서 실제 설정 파일을 만드는 패턴을 다룹니다.