nekop's blog

OpenShift / JBoss / WildFly / Infinispanの中の人 http://twitter.com/nekop

OpenShiftやKubernetes上でJavaを動かす際の注意

OpenShift 全部俺 Advent Calendar 2017

OpenShiftやKubernetes上でリソース制限を設定したコンテナでJavaを動かすとき、デフォルト設定のままだとパフォーマンスが悪くなったり、oom-killerに殺されたりします。これはコンテナのcgroupsの制限をJavaが考慮しないためです。

Javaはデフォルトでホストのメモリの1/4を最大Javaヒープメモリに設定し、Java VM本体、スタック、Metaspaceなどの非Javaヒープ領域を含めると最終的にその倍程度のメモリを利用します。たとえば16GBのマシンだと8GBくらいです。これを4GBなどの制限で動作させるとメモリが確保できずエラー終了もしくはoom-killerに殺されます。また、GCThreadsなども効率を最大化するためにCPUコア数とスレッド数を同一に設定します(厳密には8コア超えると変わりますが省略します)。これはメモリほどではありませんが、パフォーマンスを低下させる原因となることがあります。詳細は以下の記事にまとまっています。

https://engineering.linkedin.com/blog/2016/11/application-pauses-when-running-jvm-inside-linux-control-groups

JavaヒープだけならJava 8u131以降なら-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeapで調整してくれるようですが、大抵それだけではなくて他にも調整したいので起動スクリプトでいろいろやることになります。

Red Hatがメンテナンスしている起動スクリプトなどは以下のリポジトリに置いてあります。

https://github.com/jboss-openshift/cct_module/blob/master/os-java-run/added/java-default-options

CPUコア数にもとづいた自動調整はJava VMだけではなく、各種ライブラリでも利用されています。例えば非同期I/Oのワーカースレッドなどは大抵CPUコア数を取得してスレッドプールのサイズを設定します。そういったライブラリの設定もチューニングする必要がでてくるかもしれません。以下のAPIを利用しているものが要注意です。

Runtime.getRuntime().getMaxMemory()
Runtime.getRuntime().availableProcessors()

タイトルには「Javaの」と書きましたが、Javaがデフォルトでオートチューニングするためハイライトしただけで、他の言語やライブラリでも同じことをしていれば同じ問題は発生するのでJavaに限定した問題ではないことに注意してください。ライブラリ実装者などはこのようなオートチューニングを実装する時はcgroups対応を考慮すると良いでしょう。