컨테이너에서 Java 힙 덤프 뜨기

Kubernetes 에서 Java 애플리케이션을 운영할때에, Java 힙 덤프를 떠야하는 경우가 있다. 하지만 다음과 같이 덤프를 떠지지 않는다.

$ kubectl exec -it employee-consumer-68cfc9864-kgx4w -- sh
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      3:01 java -XX:+UseG1GC -XX:+UseStringDeduplication -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M -Djava.security.egd=file:/dev/./urandom -jar /app/employee-consumer.jar --spring.active.profile=${SPRING_PROFILES_ACTIVE}
   57 root      0:00 sh
   63 root      0:00 ps aux
/ # jcmd 1 VM.flags
1:
com.sun.tools.attach.AttachNotSupportedException: Unable to get pid of LinuxThreads manager thread
        at sun.tools.attach.LinuxVirtualMachine.(LinuxVirtualMachine.java:86)
        at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:63)
        at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:208)
        at sun.tools.jcmd.JCmd.executeCommandForPid(JCmd.java:147)
        at sun.tools.jcmd.JCmd.main(JCmd.java:131)
/ #

“Unable to get pid of LinuxThread manager thread” 오류가 발생한다.

이 오류가 나오는 이유는 Java 애플리케이션의 Pid 값이 1이기 때문이다. 이를 해결하는 방법을 소개한다.

Container 이미지에 tini 설치, 배포

먼저 Openjdk 의 컨테이너 이미지에 tini 프로그램을 설치해야 한다. 이 tini 라는 프로그램은 인자값을 받은 프로그램을 실행 시켜 준다. 이렇게 하면 tini 는 Pid 1을 가지지만 tini 가 실행시킨 프로그램은 1보다 큰 Pid 값을 가지게 된다.

문제는 Openjdk 에 tini 라는 프로그램이 없다. 더군다나 아무 컨테이너 이미지에 이것을 설치할 수 있는게 아니라 Alpine 기반 이미지에서 쉽게 설치가 가능하다. Alpine 에서는 패키지로 제공하기 때문에 명령어로 간단하게 설치할 수 있다.

Dockerfile 을 다음과 같이 수정한다.

FROM openjdk:8-jdk-alpine
+ RUN apk add --no-cache tini
RUN mkdir -p /app
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} /app/employee-consumer.jar
ENV SPRING_PROFILES_ACTIVE=kubernetes

- ENTRYPOINT ["java","-XX:+UseG1GC", "-XX:+UseStringDeduplication", "-XX:MetaspaceSize=128M", "-XX:MaxMetaspaceSize=128M", "-Djava.security.egd=file:/dev/./urandom","-jar","/app/employee-consumer.jar","--spring.active.profile=${SPRING_PROFILES_ACTIVE}"]
+ ENTRYPOINT ["/sbin/tini", "--", "java","-XX:+UseG1GC", "-XX:+UseStringDeduplication", "-XX:MetaspaceSize=128M", "-XX:MaxMetaspaceSize=128M", "-Djava.security.egd=file:/dev/./urandom","-jar","/app/employee-consumer.jar","--spring.active.profile=${SPRING_PROFILES_ACTIVE}"]

apk 명령어를 이용해 tini 를 설치해주고 ENTRYPOINT 에 실행 명령어에 tini 를 넣고 인자값으로 java 애플리케이션 명령어를 넣는다.

이렇게 컨테이너 이미지를 제작하고 배포를 한다.

tini 프로그램 이 후 Pid

tini 프로그램으로 Java 애플리케이션이 어떻게 실행되는 다음과 같이 확인 할 수 있다.

$ kubectl exec -it employee-consumer-cbfc7c94b-t65sz -- sh
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/tini -- java -XX:+UseG1GC -XX:+UseStringDeduplication -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M -Djava.security.egd=file:/dev/./urandom -jar /app/employee-consumer.jar --spring.active.profile=${SPRING_PROFILES_ACTIVE}
    7 root      1:04 java -XX:+UseG1GC -XX:+UseStringDeduplication -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M -Djava.security.egd=file:/dev/./urandom -jar /app/employee-consumer.jar --spring.active.profile=${SPRING_PROFILES_ACTIVE}
   56 root      0:00 sh
   63 root      0:00 ps aux
/ # jcmd 7 GC.heap_info
7:
 garbage-first heap   total 162816K, used 62180K [0x00000000f0000000, 0x00000000f01004f8, 0x0000000100000000)
  region size 1024K, 11 young (11264K), 10 survivors (10240K)
 Metaspace       used 53386K, capacity 56950K, committed 57216K, reserved 1099776K
  class space    used 6277K, capacity 6935K, committed 7040K, reserved 1048576K
/ # jcmd 7 VM.flags
7:
-XX:CICompilerCount=2 -XX:CompressedClassSpaceSize=125829120 -XX:ConcGCThreads=1 -XX:G1HeapRegionSize=1048576 -XX:InitialHeapSize=16777216 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=268435456 -XX:MaxMetaspaceSize=134217728 -XX:MaxNewSize=160432128 -XX:MetaspaceSize=134217728 -XX:MinHeapDeltaBytes=1048576 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC -XX:+UseStringDeduplication

jcmd 뿐만 아니라 jstat, jmap 등 모든 명령어를 사용할 수 있다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다