Table of Contents
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 등 모든 명령어를 사용할 수 있다.