컨테이너에서 Java 힙 덤프 뜨기
Kubernetes 에서 Java 애플리케이션을 운영할때에, Java 힙 덤프를 떠야하는 경우가 있다. 하지만 다음과 같이 덤프를 떠지지 않는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ 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.<init>(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) / # </init> |
“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 을 다음과 같이 수정한다.
1 2 3 4 5 6 7 8 9 |
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 애플리케이션이 어떻게 실행되는 다음과 같이 확인 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ 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 등 모든 명령어를 사용할 수 있다.