Docker 환경에서의 자원 관리
JVM 운영 환경에서 적절한 heap size를 지정하는 것은 매우 중요하다. 그런데 fusion과 같은 container 환경에서는 몇 가지 추가적인 고려가 필요하다.
Docker 환경에서의 자원 관리
먼저 Docker는 일반적인 가상화와 다르게 완전히 격리된 host 환경을 제공하지 않는다. 다음 명령을 실행 해보자.
% docker run --memory 512m -it --rm ubuntu:18.04 /bin/bash
root@baf2168834a3:/# free
total used free shared buff/cache available
Mem: 2046940 233420 606064 860 1207456 1652744
Swap: 1048572 208 1048364
분명 메모리 사이즈를 512m로 제한했지만 free 명령으로 확인하면 2G로 나온다(여기서 2G는 테스트 장비의 전체 메모리 크기). /proc/meminfo도 마찬가지 정보를 return한다.
root@baf2168834a3:/# cat /proc/meminfo
MemTotal: 2046940 kB
MemFree: 606888 kB
MemAvailable: 1653544 kB
...
...
하지만 메모리를 512m 이상 사용하면 OOM이 발생한다.
root@efc87a14ea9d:/# apt-get
update && apt-get
install -y stress
root@21617e58fe2a:/# stress -m 1 --vm-bytes 1G --vm-keep
stress: info: [239] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: FAIL: [239] (415) <-- worker 240 got signal 9
stress: WARN: [239] (417) now reaping child worker processes
stress: FAIL: [239] (451) failed run completed in
2s
이는 /proc의 정보들은 host의 정보를 보여주기 때문으로 Docker 내부에서 사용 가능한 진짜 제한은 cgroup에서 봐야 한다.
root@21617e58fe2a:~# cat /sys/fs/cgroup/memory/memory.limit_in_bytes
536870912
Docker 환경에서 JVM 메모리 관리
최근 버전의 JVM들은 OS의 메모리에 맞춰 memory 크기를 자동으로 설정하게 되어 있는데 fusion에서 default 설정으로 사용한다면 모든 container가 128G의 메모리를 가지고 있다고 착각을 하게 된다.
이 경우 메모리가 매우 많기 때문에 GC를 거의 안하게 되고 성능은 좋아질 수 있겠으나 조금만 시간이 지나도 메모리가 부족해서 OOM이 발생하기 시작할 것이다.
따라서 -Xmx 옵션으로 정확한 메모리를 할당하는 것이 가장 확실하나 다음과 같은 방법을 사용하면 향후 메모리 할당량 변경을 fusion에서 동적으로 할 수 있으니 같이 검토할 필요가 있다.
Java 8
Java 8에서는 다음 옵션을 주면 cgroup의 정보를 기준으로 메모리 크기를 결정한다.
-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
그리고 다음 옵션으로 전체 메모리중 얼마를 JVM이 사용할지 결정한다.
-XX:MaxRAMFraction=1
-XX:MaxRAMFraction=2
-XX:MaxRAMFraction=3
여기서 이 옵션이 좀 문제가 있는데 이 의미가 1%, 2%, 3%가 아니라 1/1, 1/2, 1/3의 의미로 각각 100%, 50%, 33%의 설정이 된다.
왜 이렇게 만들어 놨는지 도무지 이해할 수 없는 설정인데 Java 8에서는 어쩔 수 없이 이 설정을 사용할 수 밖에 없다.
문제는 100%는 JVM 이외의 시스템이 사용할 메모리가 전혀 없다는 문제가 있고 50%는 또 너무 작다는 문제가 있다.
따라서 Docker에서 필요한 메모리 보다 좀더 여유있게 할당하고 그의 50%로 설정 하는 꼼수를 사용 해야 한다.
~ % docker run -m 512m -it --rm openjdk:8-jre-alpine java -XshowSettings:vm -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -version
VM settings:
Max. Heap Size (Estimated): 455.50M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (IcedTea 3.6.0) (Alpine 8.151.12-r0)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)
~ % docker run -m 800m -it --rm openjdk:8-jre-alpine java -XshowSettings:vm -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -version
VM settings:
Max. Heap Size (Estimated): 711.50M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (IcedTea 3.6.0) (Alpine 8.151.12-r0)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)
~ % docker run -m 1800m -it --rm openjdk:8-jre-alpine java -XshowSettings:vm -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -version
VM settings:
Max. Heap Size (Estimated): 800.00M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (IcedTea 3.6.0) (Alpine 8.151.12-r0)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)
Java 10 이후
Java 10에서는 위의 이상한 옵션을 deprecation 시키고 percent 단위 옵션이 추가되었다. Java 8에 backport가 될지는 알 수 없으나 향후 사용이 가능해 진다면 이게 가장 좋은 옵션이다.
-XX:MaxRAMPercentage=80
그리고 Java 10 부터는 옵션을 사용하지 않아도 자동으로 cgroup의 정보를 사용한다. 따라서 다음과 같이 간단해 진다.
~ % docker run -m 512m -it --rm openjdk:10-jre java -XshowSettings:vm -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -version
OpenJDK 64-Bit Server VM warning: Option UseCGroupMemoryLimitForHeap was deprecated in
version 10.0 and will likely be removed in
a future release.
OpenJDK 64-Bit Server VM warning: Option MaxRAMFraction was deprecated in
version 10.0 and will likely be removed in
a future release.
VM settings:
Max. Heap Size (Estimated): 247.50M
Using VM: OpenJDK 64-Bit Server VM
openjdk version "10.0.1"
2018-04-17
OpenJDK Runtime Environment (build 10.0.1+10-Debian-4)
OpenJDK 64-Bit Server VM (build 10.0.1+10-Debian-4, mixed mode)
~ % docker run -m 512m -it --rm openjdk:10-jre java -XshowSettings:vm -XX:MaxRAMPercentage=100 -version
VM settings:
Max. Heap Size (Estimated): 494.94M
Using VM: OpenJDK 64-Bit Server VM
openjdk version "10.0.1"
2018-04-17
OpenJDK Runtime Environment (build 10.0.1+10-Debian-4)
OpenJDK 64-Bit Server VM (build 10.0.1+10-Debian-4, mixed mode)
~ % docker run -m 512m -it --rm openjdk:10-jre java -XshowSettings:vm -XX:MaxRAMPercentage=50 -version
VM settings:
Max. Heap Size (Estimated): 247.50M
Using VM: OpenJDK 64-Bit Server VM
openjdk version "10.0.1"
2018-04-17
OpenJDK Runtime Environment (build 10.0.1+10-Debian-4)
OpenJDK 64-Bit Server VM (build 10.0.1+10-Debian-4, mixed mode)
~ % docker run -m 512m -it --rm openjdk:10-jre java -XshowSettings:vm -XX:MaxRAMPercentage=80 -version
VM settings:
Max. Heap Size (Estimated): 396.38M
Using VM: OpenJDK 64-Bit Server VM
openjdk version "10.0.1"
2018-04-17
OpenJDK Runtime Environment (build 10.0.1+10-Debian-4)
OpenJDK 64-Bit Server VM (build 10.0.1+10-Debian-4, mixed mode)