이야기박스
Docker - SpringBoot JVM Option 이슈 본문
# 개요
Kubernetes의 Pod 컨테이너에서 top을 실행하면, Pod의 메모리가 아닌 노드 전체의 메모리를 보게 됩니다.
그렇기 때문에 Kubernetes 환경에서 자바 애플리케이션을 사용할 때는 Memory에 대한 고려가 충분히 이루어져야 합니다.
이번 포스트는 Spring Boot 앱을 개발할 일이 있어서 -Xms -Xmx, 두 옵션을 통하여 자바 힙 메모리를 제한하려고 하는데, JVM 에러가 발생하여 트러블 슈팅을 하는 내용을 담았습니다.
# Before. 서비스
## Dockerfile
# Use maven to compile the java application.
FROM docker.io/maven AS build-env
# Set the working directory to /app
WORKDIR /app
# copy the pom.xml file to download dependencies
COPY pom.xml ./
# download dependencies as specified in pom.xml
# building dependency layer early will speed up compile time when pom is unchanged
RUN mvn verify --fail-never
# Copy the rest of the working directory contents into the container
COPY . ./
# Compile the application.
RUN mvn -Dmaven.test.skip=true package
# Labeling the maintainer
LABEL maintainer="stroyparks@test.com"
## ------- ##
# Build runtime image.
FROM openjdk:8-jre
WORKDIR /testapp
# Copy the compiled files over.
COPY --from=build-env /app/target/ /testapp/
EXPOSE 8080
# Copy the launch script into the container
COPY ./bin/launch.sh /testapp/bin/launch.sh
ARG SPRING_PROFILE=dev
ENV SPRING_PROFILE=${SPRING_PROFILE}
# Starts java app with debugging server at port 5005.
CMD ["/bin/bash", "/testapp/bin/launch.sh"]
## launch.sh
echo "## Spring Boot Tester Launch with Profile - ${SPRING_PROFILE}"
JAVA_OPTS=" -Dspring.profiles.active=${SPRING_PROFILE}"
JAVA_OPTS="${JAVA_OPTS} -Xms64m -Xmx256m"
java ${JAVA_OPTS} -jar /testapp/testSpringWebApp.jar
서비스는 빌드된 이미지 내, launch.sh 스크립트를 통하여 실행하는 구조입니다.
## 에러 내용
아래의 에러를 뱉으며 프로세스가 종료되었습니다.
Invalid maximum heap size: -Xmx256m
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
해당 에러는 다음과 같은 경우에 발생하는 것 같습니다. (참고)
- 64bit 데이터 모델이 32bit 환경에서 실행하는 경우
- 메모리가 부족한 경우
# 문제 분석
혹시나 메모리가 부족해서 그런가 하여 해당 컨테이너 들어와 용량을 확인해 보았습니다.
(JVM Heap 설정을 하면 fatal error가 발생하기 때문에, 메모리 설정을 하지 않고 실행하였습니다.)
root@1423e0817da1:/testapp# cat /proc/meminfo | grep Mem*
MemTotal: 2047012 kB
MemFree: 168176 kB
MemAvailable: 1277876 kB
실제 호스트에서는 메모리의 여유가 충분한 것으로 확인되었습니다.
문제는 launch.sh에 있었던 것 같습니다. JAVA_OPTS이라는 환경변수를 Dockerfile에서 생성하다 보니, 이미지에 기본적으로 존재하던 환경변수를 덮었어 '-d64' 옵션이 지워진 것 같습니다.
# 조치
JAVA_OPTS를 덮어쓰면, JVM Data Model 뿐 아니라 다른 JVM 옵션에도 영향이 갈 수 있을 것 같았습니다.
그래서 스크립트 기반이 아닌 Runtime에 환경변수를 입력 받아 적용하도록 변경하였습니다.
스크립트를 쓰지 않으니 jre Base 이미지를 alpine으로 변경하여 적용하였습니다.
## Dockerfile 수정
스크립트 실행에서 자바 애플리케이션을 직접 실행할 수 있도록 구성하였습니다.
CMD ["/bin/bash", "/testapp/bin/launch.sh"]
==>
ENTRYPOINT exec java $JAVA_OPTS -jar /testapp/testSpringWebApp.jar
## Java 코드 - 적용 확인
할당받은 메모리가 잘 적용되었는지 확인하는 스크립트입니다. (참조)
Runtime runtime = Runtime.getRuntime();
final NumberFormat format = NumberFormat.getInstance();
final long maxMemory = runtime.maxMemory();
final long allocatedMemory = runtime.totalMemory();
final long freeMemory = runtime.freeMemory();
final long mb = 1024 * 1024;
final String mega = " MB";
log.info("========================== Memory Info ==========================");
log.info("Free memory: " + format.format(freeMemory / mb) + mega);
log.info("Allocated memory: " + format.format(allocatedMemory / mb) + mega);
log.info("Max memory: " + format.format(maxMemory / mb) + mega);
log.info("Total free memory: " + format.format((freeMemory + (maxMemory - allocatedMemory)) / mb) + mega);
log.info("=================================================================\n");
# 후기
Docker 컨테이너가 배포/운영을 보다 쉽게 할 수 있게 도와줄 수 있지만 그 밑바탕에는 기본적인 CS 지식이 있어야 한다는 걸 다시 한번 깨달았습니다.
최근에 밀린 포스팅이 많은데 천천히 처리하도록 하겠습니다.
'Computer & Data > Orchestration' 카테고리의 다른 글
Kubernetes. Helm3 (1) | 2020.01.10 |
---|---|
(작성중) Kubernetes. Pod AutoScaling (0) | 2019.12.06 |
(작성중) Cheating Kubernetes & Docker (0) | 2019.11.19 |
(작성중) Docker <none>:<none> images (0) | 2019.11.14 |
Kubernetes. 포드의 계산 리소스 관리 (3) | 2019.10.30 |