kubernetesでjavaアプリケーションを動かすときのリソース設計
概要
javaアプリケーションをkubernetesで動かしたときにメモリの設定で苦労したのでその時調べたことなどをまとめました
- javaのヒープメモリ(-Xms/-Xmx)
- k8sのメモリ制限(resource.request/limit)
- メモリ使用量調査関連
- Pod内でps/top
- docker stats/(kubectl top)
- (その他)jmap/jconsole
起こったこと
定期的にPodが再起動していて原因を調べるとjava heapのOutOfMemoryとoom-killerが発生していました
javaのヒープメモリ設定(-Xms/-Xmx)
javaのOutOfMemoryが発生していたのは-Xms/-Xmxを設定していなかったことが原因でした。
- -Xms:最低限確保するメモリ量。ガベージコレクション時にはこの値を目標にメモリが開放される。デフォルトはメモリの1/64(※)
- -Xmx:Javaヒープの最大値。-Xmsで指定したメモリでは不足する場合、この値までメモリを取得します。不要になった分はXmsの値までガベージコレクションによってメモリは開放されますが、開放されないメモリが増えて-Xmxの値を超えるとOutOfMemoryになる可能性があります。デフォルトはメモリの1/4(※)
例)
java -jar test.jar -Xms1024m -Xmx1024m
※搭載メモリ量などによっても変わります。参考(http://n-agetsuma.hatenablog.com/entry/2015/12/30/185405)
k8sのメモリ制限
アプリケーションの特性上、動かしているホストマシンのメモリを使い切ることはない想定だったため、メモリ制限は設定する必要ないだろうということで、最初特に何も設定していませんでした。
逆に言うと設定しなければホストVMのメモリ量内であれば必要分メモリを使用できると思っていたのですがそうではなく、正しくリソース制限を設定することでそこまで使用させてくれる状態になるようです。設定しないと、Pod間でのリソース使用優先度が下がり、優先的にkillされる対象になるようです。
(また、明示的に設定しなくても自動でcgroupが設定されてリソース制限されるようですが、詳細を理解しきれていないため別途追記したいと思います)
今回のケースではデフォルトで設定されたJavaのヒープ上限がk8sの制限を超えていたためk8sの制限以上にメモリを確保しようとしてoom-killerが発生していたようでした。
- spec.containers.resource.request:デプロイ時に必要な空き容量。必ずこの分が確保されるわけではないので注意。
- spec.containers.resource.limit:Podが使用できる最大容量。この値を超えて使用しようとした場合、CPUの場合は遅くなるだけだが、メモリの場合はoom-killerでkillされるため注意
まとめ
- JavaヒープのXmxは対象のアプリが使用する分+aとなるように設定する。
- XmsはXmxと同一値を設定するのが推奨されているようですが、Xmsに収まる場合、ガベージコレクションが行われないため、実際に使用するメモリ量を測定したい場合は、小さめにして測定するのがよさそうです。
- k8sはJavaヒープのXmxより大きな値を設定しておく(Javaがそれ以上の値のメモリを確保しようとしてoom-killerになってしまうため)
- また、正しく設定しておくことで優先的にkillされるのを防ぐためきちんと設定しておくほうがよい。
物理メモリに余裕があるから大丈夫と高を括らず、リソース制限は各種設定がどんなものかちゃんと調べて、設定しておきましょうという反省でした。
kubernetesについては以下の本で勉強中です。アーキテクチャのイメージから詳細までしっかり書かれているので、読み応えありますがしっかり勉強したいと思っています。
コメント