컨테이너 환경에서 Java Heap Memory 관리

xgro·2023년 5월 15일
1

DevOps

목록 보기
8/9

본 블로그는 baeldung 홈페이지에 게시되어 있는 글을 참조하여 실습한 경험을 작성하였습니다.

📌 Summary

  • Java 옵션 중 힙 메모리를 관리하는 -XX:+UseContainerSupport에 대해 이해합니다.
  • Java 8 버전을 통해 UseContainerSupport 백포팅이 적용됨을 확인합니다.
  • AWS ECS에 컨테이너를 직접 배포하여 JAVA_OPTS의 ["-Xms", "-Xmx"] 관계를 파악할 수 있습니다.


📌 Java heap memory

Java 컨테이너의 기본 힙 설정
과거에는 JVM이 컨테이너에 할당된 메모리와 CPU를 인식하지 못했습니다.
Java 10은 근본 원인을 수정하기 위해 +UseContainerSupport (기본적으로 활성화됨) 라는 새로운 설정을 도입했으며 개발자는 수정 사항을 8u191Java 8로 백포트했습니다.
JVM은 이제 컨테이너에 할당된 메모리를 기반으로 메모리를 계산합니다.

컨테이너 환경에서 Java를 실행할 때 사용가능한 리소스를 지정할 수 있습니다.

Java 프로세스를 실행하는 컨테이너에서 JVM 매개 변수를 설정하는 방법을 살펴봅니다. 

특정 버전의 Java로 실행되는 프로그램을 컨테이너화하는 일반적인 문제와 일부 컨테이너화된 Java 애플리케이션에서 플래그를 설정하는 방법을 살펴봅니다.

테스트를 위한 코드는 github repo 에 배포되어 있습니다.

Xms, Xmx 및 UseContainerSupport 의 설정에 대해서 각각 로컬에서의 동작에 대한 테스트와 AWS ECS에서의 테스트로 진행하였습니다.



📗 Oldjava (openjdk 1.8.0_92)

👉 Step 01. 기본 이미지 실행

조건
JDK 8u191

JAVAContainer
nonenone

Result

Initial MemoryMax Memory
250mb3545mb

-Xmx 또는 -Xms JVM 플래그를 제공하지 않았기 때문에 메모리 설정이 기본값으로 설정됩니다.

oldjava로 빌드된 기본 이미지로 컨테이너를 실행해 보겠습니다.
컨테이너는 현재 설정된 Initial MemoryMax Memory에 대한 내용을 출력하고 종료되며, 종료 즉시 삭제 됩니다.(--rm 옵션)


👉 Step 02. 컨테이너 메모리 제한(1g)

조건
JDK 8u191

JAVAContainer
none1G Limit

Result

Initial MemoryMax Memory
250mb3545mb

컨테이너 메모리를 1GB로 제한하여 다시 이미지를 실행합니다.

컨테이너의 메모리를 제한해도 결과는 동일한 것을 확인할 수 있습니다. JVM이 컨테이너의 메모리 할당을 고려하고 있지 않는 것을 알 수 있습니다.



📘 Newjava (openjdk 1.8.0_212)

동일한 테스트 프로그램으로 Dockerfile 의 첫 번째 줄을 변경하여 최신 JVM 8을 사용하여 테스트를 진행합니다.

FROM openjdk:8-jdk-alpine

👉 Step 01. 기본 이미지 실행

조건
JDK 8u130

JAVAContainer
nonenone

Result

Initial MemoryMax Memory
250mb3545mb

oldjava의 경우와 동일하게 여기서도 컨테이너의 리소스를 제한하지 않고 별도의 설정이 없는 경우 전체 도커 호스트 메모리를 사용하여 JVM 힙 크기를 계산하는것을 확인할 수 있습니다.


👉 Step 02. 컨테이너 메모리 제한(1g)

조건
JDK 8u130

JAVAContainer
none1G Limit

Result

Initial MemoryMax Memory
16mb247.5mb

컨테이너 메모리를 1GB로 제한하여 다시 이미지를 실행합니다.

컨테이너에 1GB의 RAM을 할당하는 경우 JVM이 컨테이너에서 사용할 수 있는 1GB RAM을 기준으로 힙 크기를 계산하는것을 확인 할 수 있습니다.



👉 Step 03. JAVA_OPTS (Xms,Xmx)

조건
JDK 8u130

JAVAContainer
-Xms=500 -Xmx=2G1G Limit

Result

Initial MemoryMax Memory
500mb1979.75mb

JAVA_OPTS 환경 변수를 지정하여 런타임에 메모리 설정을 선택할 수 있습니다.

Xmx 매개변수와 JVM에서 보고하는 최대 메모리 사이에는 약간의 차이가 발생합니다. 이는 Xmx가 힙, 가비지 수집기의 생존 공간 및 기타 풀을 포함하는 메모리 할당 풀의 최대 크기를 설정하기 때문입니다.

Xms, Xms 매개변수 사용하는 경우

docker run -it --rm -e JAVA_OPTS="-Xms500M -Xmx2G" --memory=1G newjava

컨테이너 메모리 제한과는 무관하게 매개변수의 값으로 자바의 메모리가 할당되는 것을 확인할 수 있습니다.



📕 AWS ECS

AWS ECS에서 해당 매개변수를 가지고 어떤 방식으로 관리할 수 있는지 확인합니다.

이미지는 newjava를 이용하여 테스트를 진행하였습니다.

Taskdefinition에서 할당한 Fargate의 크기는 아래와 같습니다.

  • vCPU - 0.5G
  • Memory - 1G

ECS 작업정의서에는 메모리의 자원을 관리하기 위한 두가지 매개변수가 존재합니다. 소프트 제한, 하드 제한

소프트 제한이란?
taskdefinition.jsonmemoryReservation에 해당하며, 컨테이너가 확보(예약)할 메모리를 설정하는 매개변수입니다.

하드 제한이란?
taskdefinition.jsonmemory에 해당하며, 컨테이너가 확장할 수 있는 제한을 설정합니다.


👉 Step 01. Default

조건
JDK 8u130

JAVAContainer
nonenone

Result

Initial MemoryMax Memory
124mb1894.688mb

newjava 이미지를 ECS에서 실행합니다.

별도의 매개변수 없이 컨테이너를 실행 할 경우 파게이트의 리소스 제한인 1G를 넘어선 1894mb가 Max Memory로 설정되는 것을 확인할 수 있었습니다.

실제 환경에서 아무런 설정없이 구동시 힙메모리가 상승한다면 컨테이너가 불시에 종료 될 수 있는 장애 포인트가 될 수 도 있습니다.


👉 Step 02. Soft Limit

조건
JDK 8u130

JAVAContainer
none(soft)1G Limit

Result

Initial MemoryMax Memory
124mb1894.688mb

Taskdefinition의 소프트 제한1024로 설정하고 컨테이너를 실행하였습니다.

Default로 실행한 컨테이너와 동일한 구성으로 동작하는것을 확인할 수 있습니다.


👉 Step 03. Hard Limit

조건
JDK 8u130

JAVAContainer
none(hard)1G Limit

Result

Initial MemoryMax Memory
16mb247.5mb

Taskdefinition의 하드 제한1024로 설정하고 컨테이너를 실행하였습니다.

위의 두 케이스와는 달리 JVM이 컨테이너의 Initial / Max Memory 설정 값을 조정한 것을 확인할 수 있습니다.


👉 Step 04. JAVA_OPTS

조건
JDK 8u130

JAVAContainer
-Xms50m -Xmx50m(hard)1G Limit

Result

Initial MemoryMax Memory
50mb48.375mb

Taskdefinition의 하드 제한1024로 설정하고 환경변수로 JAVA_OPTS="-Xms50m -Xmx50m"을 적용하여 컨테이너를 실행하였습니다.

위의 두 케이스와는 달리 JVM이 컨테이너의 Initial / Max Memory 설정 값을 조정한 것을 확인할 수 있습니다.


👉 Step 05. JAVA_OPTS(over limit)

조건
JDK 8u130

JAVAContainer
-Xms2048m -Xmx2048m(hard)1G Limit

Result

Initial MemoryMax Memory
2048mb1979.75mb

Taskdefinition의 하드 제한1024로 설정하고 환경변수로 JAVA_OPTS="-Xms2048m -Xmx2048m" 태스크에 할당된 리소스보다 더 큰 값으로 매개변수를 적용하여 실행하였습니다.

태스크에 할당된 리소스보다 더 큰 값으로 적용되어 자바가 동작된 것을 확인할 수 있습니다.



📌 Conclusion

JDK 8u130 이후 버전을 사용하는 경우 -Xms, -Xmx 설정을 통해 서비스의 크기를 조정할 수 있었습니다.

-XX:+UseContainerSupport설정과 함께 -XX:InitialRAMPercentage, -XX:MinRAMPercentage, -XX:MaxRAMPercentage 파라미터를 이용하여 컨테이너의 크기에 따라 서비스의 메모리를 동적으로 관리할 수 있습니다.

이 블로그를 통해 컨테이너 환경에서 잘 동작하는 기본 메모리 설정 -XX:+UseContainerSupport 이 포함된 JVM의 버전을 확인하여 사용해야 하는 필요성에 대해 알아보았습니다.

또한 사용자 지정 컨테이너 이미지에서 -Xms 및 -Xmx를 설정하는 사례와 기존 Java 애플리케이션 컨테이너를 사용하여 JVM 옵션을 설정하는 방법을 살펴보았습니다.

마지막으로 AWS ECS에 컨테이너를 직접 배포하여 ECS에서 -XX:+UseContainerSupport 매개변수를 동작 시킬 수 있는 환경을 파악하고 JAVA_OPTS 매개변수(-Xms,-Xmx)를 환경변수로 적용하여 사용하는 방법을 파악할 수 있었습니다.



🔗 Reference

profile
안녕하세요! DevOps 엔지니어 이재찬입니다. 블로그에 대한 피드백은 언제나 환영합니다! 기술, 개발, 운영에 관한 다양한 주제로 함께 나누며, 더 나은 협업과 효율적인 개발 환경을 만드는 과정에 대해 인사이트를 나누고 싶습니다. 함께 여행하는 기분으로, 즐겁게 읽어주시면 감사하겠습니다! 🚀

0개의 댓글