はじめに
Java 8においても、
[Oracle Blogs 日本語のまとめ - [Java] Java SE support for Docker CPU and memory limits] (https://orablogs-jp.blogspot.com/2017/05/java-se-support-for-docker-cpu-and.html)
の記事から、8u131以降はある程度 container.resource.limit.cpu、container.resource.limit.memoryを見て上手い事やってくれるのかな?
と思いきや、いまいちな挙動だったので、kubernetesで動かした際のjava8、java10、java11(ea)でのCPUの認識、及びメモリサイズの確保部分について調べてみました。
特に、GCの並列実行(スレッド数)や、各種並列スレッドのプール数等にも影響がある、 Runtime#availableProcessorsの動きがバージョンで変わるのか?という部分が
https://bugs.openjdk.java.net/browse/JDK-8140793
を見ても、2018/7/31時点でまだfixedVersionが出ていないのでどのバージョンでもまともに動かないのかどうかが気になった次第です。
結論
- kubernetesでJavaアプリケーションを動作させる際、CPU、メモリを正しく認識させたいならjava10以降ならちゃんと認識してくれる。
- java8でもメモリはXmx、Xmsなどで制御可能。
- java8ではRuntime#availableProcessorsは正しく制限かける事はできないが、GCThread等は制御可能。
前提Javaアプリケーション
適当なSpring Boot アプリケーションを動かしてそのmetrics情報と、java -XX:+PrintGCDetails -XX:+PrintFlagsFinal -XX:+UnlockExperimentalVMOptions
をcontainer上で動かした際の情報を確認します。
Spring Bootのアプリケーションは、
https://github.com/h-r-k-matsumoto/spring-boot-sample
をベースとして、それぞれ下記のように変更しています。また、cointainerに対するリソース割当は、下記のようになります。
resources:
requests:
cpu: 150m
memory: 512Mi
limits:
cpu: 900m
memory: 512Mi
deploymentsの全文は、
https://github.com/h-r-k-matsumoto/spring-boot-sample/blob/master/kubernetes/020_deployments.yml
です。
java8 (no cgroup option)
pom.xmlのfrom imageを下記に変更します。特にJavaオプションでは何も制御しません。デフォルトのままです。
<image>openjdk:8u171-jre-alpine</image>
java8
pom.xmlのfrom imageを下記に変更します。
<image>openjdk:8u171-jre-alpine</image>
更に、下記jvmFlagオプションを追加します。ポイントは、 UseCGroupMemoryLimitForHeap
です。
後は・・・私がいつもdocker 環境で動かす際につけているものです。
<jvmFlag>-XX:+UnlockExperimentalVMOptions</jvmFlag>
<jvmFlag>-XX:+UseCGroupMemoryLimitForHeap</jvmFlag>
<jvmFlag>-XX:ParallelGCThreads=1</jvmFlag>
<jvmFlag>-XX:CICompilerCount=2</jvmFlag>
<jvmFlag>-Djava.util.concurrent.ForkJoinPool.common.parallelism=1</jvmFlag>
java10
pom.xmlのfrom imageを下記に変更します。オプションは指定無しです。
<image>openjdk:10-jre-slim</image>
java11 (ea)
pom.xmlのfrom imageを下記に変更します。オプションは指定無しです。
<image>openjdk:11-jre-slim</image>
計測内容
[Spring Boot Actuatorのmetrics] (https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-metrics.html) 機能と、
kubectl exec {pod-name} -- java -XX:+PrintGCDetails -XX:+PrintFlagsFinal -XX:+UnlockExperimentalVMOptions
を実行して取得した情報を利用します。
Spring Boot Actuator - metrics
system.cpu.count
http://pod-ip:port/actuator/metrics/system.cpu.count
で取得します。Runtime#availableProcessors
の実行結果です。
ソースはmicrometer - ProcessorMetrics.java。
jvm.memory.max (heap)
http://pod-ip-port/actuator/metrics/jvm.memory.max?tag=area:heap
で取得します。ヒープ領域の最大サイズです。
ソースは、micrometer - JvmMemoryMetrics.java。 MemoryUsage#getMax
の結果です。
PrintFlagsFinal結果
MaxHeapSize
最大ヒープ管理領域のサイズです。jvm.memory.maxと同じになる・・・はず。
UseParallelGC
GCを並列実行するかどうかです。
参考:https://docs.oracle.com/javase/jp/8/docs/technotes/guides/vm/gctuning/collectors.html
ParallelGCThreads
パラレルGCの際のスレッド数です。
計測結果
実行するノードのspecは、vCPUx2、メモリ7.5GBです。
java version | system.cpu.count | jvm.memory.max(heap):MiB | MaxHeapSize:MiB | UseParallelGC | ParallelGCThreads |
---|---|---|---|---|---|
java 11(ea) | 1 | 123.75 | 128.00 | false | 0 |
java 10 | 1 | 123.75 | 128.00 | false | 0 |
java 8 | 2 | 120.00 | 128.00 | true | 2 |
java 8 (no cgroup option) | 2 | 1,857.00 | 1,870.00 | true | 2 |
※java 8でparallelになってしまっているのは、-XX:+UseSerialGC のオプションでserialに変更可能。というか -XX:ParallelGCThreads=1 が効いていない…。
java11、java10は、特にオプション指定をしなかった場合は、containersに割り当てられたメモリサイズの 512Mi
から正しく計算され、MaxRAMFraction=4となるため、512/4 = 128 となっていました。
また、CPUに関しても、900mとしましたが、1コア分として判定されています。
java11、java10は、XmxやらXmsは指定せずとも、container上で正しく動きそうです。
java8の場合は、メモリに関しては制御可能です。ただし、CPU数に関しては上手い事手出しができないっぽいので… Runtime#availableProcessors
から計算していてリソース確保している部分は、都度システムプロパティ等で設定した方がよさそうです。
jvm.memory.max(heap)と、MaxHeapSizeで多少の誤差が出てるのは・・・気にしないです!
もし「このようなオプションを指定した方が良い」、「ここ、間違ってるよ」とかあればご指摘お願いします。