Javaをコンテナ環境で使う際に、知らないうちに発生してしまうCPUスロットリングやOOM-killerなど、発生メカニズムやJavaVMのチューニング対処について。
© 2023 Fujitsu Limited2023/06/04数村憲治コンテナ環境でのJavaチューニングJJUG CCC Spring 2023@kkzr
View Slide
© 2023 Fujitsu Limited自己紹介Jakarta EE 仕様策定委員MicroProfile ステコミ委員JCP Executive Committee メンバーEclipse Foundation ボードディレクターEclipseCon、JakartaOne、JJUGなどで登壇2
本資料での注意事項© 2023 Fujitsu Limited本資料は、OpenJDK実装をベースに記載しています。単に、「JDK」、「Java」と記載している場合、OpenJDKの実装を意味していることがあります。OpenJDK 17は、17.0.7OpenJDK 8は、8u372の各バージョンを使用しています。マイナーバージョンが違う場合は、本資料の記載動作と違う場合があります。3
アジェンダ© 2023 Fujitsu LimitedJava エルゴノミクスDockerでのリソース制御サマリKubernetesでのリソース制御4
コンテナと、Javaエルゴノミクス© 2023 Fujitsu Limitedコンテナコンテナに割当てられたメモリ・CPUリソース整合性JavaJavaが使えると思っているメモリ・CPUリソースコンテナ起動のたびに変わる可能性あり他のコンテナに影響される可能性ありエルゴノミクスまたは手動で設定5
アジェンダ© 2023 Fujitsu LimitedJava エルゴノミクスDockerでのリソース制御サマリKubernetesでのリソース制御6
Javaのエルゴノミクス© 2023 Fujitsu LimitedGC種別JavaヒープサイズGCスレッド数並行処理数・・・Javaでのエルゴノミクスとは、各種パラメタをJVMが自身が動作する環境をベースに自動的に設定することエルゴミクス対象例7
GC種別 (OpenJDK 8)© 2023 Fujitsu Limited利用可能GCエルゴノミクス対象条件Serial ✔Parallel ✔CMS N/AG1 N/ACPU数 < 2 || メモリ < 1792MBCPU数 ≧ 2 && メモリ ≧ 1792MB8
GC種別 (OpenJDK 17)© 2023 Fujitsu Limited利用可能GCエルゴノミクス対象条件Serial ✔Parallel N/AG1 ✔Z N/AN/ACPU数 < 2 || メモリ < 1792MBCPU数 ≧ 2 && メモリ ≧ 1792MBShenandoah9
コンテナ環境でのコア数© 2023 Fujitsu Limited40%30%■コンテナ環境■非コンテナ環境20%10%使用率コア数 https://newrelic.com/resources/report/2023-state-of-the-java-ecosystem3 5 6 7 10 11 12 15 20 24 36 4810
Javaヒープサイズ© 2023 Fujitsu Limited初期値:物理メモリの1/64最大値:物理メモリの1/4-XX:MaxRAMPercentage-Xmxエルゴノミクスによる設定-XX:InitialRAMPercentage-Xms気にいらなければ、自分で設定Javaヒープサイズの指定方法 (JDK 10 以降)11
コンテナ環境でのJavaヒープサイズ© 2023 Fujitsu Limitedhttps://newrelic.com/resources/report/2023-state-of-the-java-ecosystem使用率JVMヒープサイズ設定30%20%10%■コンテナ環境■非コンテナ環境MB GB GB GB GB GB GB GB GB GB12
論理CPU数の影響© 2023 Fujitsu Limitedエルゴノミクスによる設定 (OpenJDK 17)GCスレッド数・複雑・おおよそ、8+(n-8)*(5/8)コンパイラスレッド数・かなり複雑・おおよそ、int(log2 n) * (int)(log2 int(log2 n)) * 3 /2ForkJoinPool並行度数・おおよそ、n - 1GC種別 前掲n = 論理CPU数13
アジェンダ© 2023 Fujitsu LimitedJava エルゴノミクスDockerでのリソース制御サマリKubernetesでのリソース制御14
DockerでのCPUリソース割当て© 2023 Fujitsu Limiteddocker run --cpu-period=100000 --cpu-quota=200000CPU ShareCPU Quotadocker run --cpu-shares=2048コンテナに割り当てるCPU数を相対的に指定するデフォルト値は1024period間に消費可能なCPU時間を指定するCPUスロットリングが発生しやすいCPU Sets使用するCPUセットを指定する一般的には使わない例例15
CPU1 CPU2CPU Shares (ビジー)© 2023 Fujitsu LimitedA:B:C=2:1:1でCPU時間が配分されるコンテナCshare:1024コンテナBshare:1024それぞれのコンテナがビジー状態であれば、コンテナAshare:204816
CPU2アイドルCPU1CPU Shares (アイドルあり)© 2023 Fujitsu LimitedコンテナBshare:1024コンテナA、Cがアイドル状態になると・・・コンテナBがCPU時間を総どりコンテナCshare:1024コンテナAshare:204817
Javaプロセスが認識するCPU数(cgroup v2)© 2023 Fujitsu Limited1024*n ± 512 がおおよその境docker run javadocker run --cpu-shares=1024 javadocker run --cpu-shares=1536 javadocker run --cpu-shares=2048 javadocker run --cpu-shares=2560 java---> ホストCPU数---> 1CPU---> 2CPU---> 2CPU---> 3CPUJDK 8、JDK 17 with -XX:+UseContainerCpuShares の場合18
Javaプロセスが認識するCPU数(cgroup v1)© 2023 Fujitsu Limited1024がマジックナンバーdocker run javadocker run --cpu-shares=1023 javadocker run --cpu-shares=1024 javadocker run --cpu-shares=1025 javadocker run --cpu-shares=2049 java---> ホストCPU数---> 1CPU---> ホストCPU数---> 2CPU---> 3CPUJDK 8、JDK 17 with -XX:+UseContainerCpuShares の場合19
Javaプロセスが認識するCPU数© 2023 Fujitsu Limitedデフォルトでは、CPU Sharesは参照しないdocker run javadocker run --cpu-shares=1023 javadocker run --cpu-shares=1024 javadocker run --cpu-shares=1025 javadocker run --cpu-shares=2049 java---> ホストCPU数--->ホストCPU数---> ホストCPU数--->ホストCPU数--->ホストCPU数JDK 17 (デフォルト -XX:-UseContainerCpuShares) の場合20
Java における CPU Share 注意事項© 2023 Fujitsu Limited実際にコンテナに割当てられるCPU時間は、他のコンテナの状態に依存する=> 非決定的Javaプロセスからは、他のコンテナの状態はわからない=> 変動するCPUを前提にプログラムするのは難しい21
Java と CPU Share ミスマッチ (1)© 2023 Fujitsu LimitedホストマシンのCPU数が3の場合コンテナA コンテナB--cpu-shares= 1024 2048コンテナに割当てられるCPU数(CPU時間)1 2Javaが認識するCPU数 3 2(JDK8)or3(JDK17)例ミスマッチミスマッチcgroup v1 の例22
Java と CPU Share ミスマッチ (2)© 2023 Fujitsu LimitedホストマシンのCPU数が3の場合コンテナA コンテナB--cpu-shares= 1500 3000コンテナに割当てられるCPU数(CPU時間)1 2Javaが認識するCPU数 1 3例ミスマッチcgroup v2 の例23
Docker CPU Quota© 2023 Fujitsu Limitedquota : period間にコンテナが消費できるCPU時間(マイクロセカンド)runサブコマンドに、「--cpu-quota=」を指定quotaを使い切るとコンテナにCPUは割り当てられないCPUスロットリングperiod : デフォルトは100マイクロセカンドrunサブコマンドに、「--cpu-period=」を指定24
Java における CPU Quota© 2023 Fujitsu Limitedデフォルトでは、CPU Quota が CPU Shares より優先JDK17のデフォルトでは、CPU Shares を参照しない-XX:[+/-]PreferContainerQuotaForCPUCountフラグで、優先変更可能。ただし、「Prefer」であって、quotaを全く使用しないわけではない。Javaフラグ CPU数-XX:+PreferContainerQuotaForCPUCount quota / period-XX:-PreferContainerQuotaForCPUCount min ( quota/period, shares/1024 )-XX:ActiveProcessorCount=n nJavaの計算が気にいらなければ、ActiveProcessorCountを使用。25
CPUJavaプロセスが認識するCPU数 (JDK 8)© 2023 Fujitsu Limiteddocker run --cpu-quota=300000 java CPUdocker run --cpu-quota=300000 --cpu-shares=2048 javaCPUdocker run --cpu-quota=300000--cpu-shares=2048 java -XX:-PreferContainerQuotaForCPUCountCPUdocker run --cpu-quota=100000--cpu-shares=2048 java -XX:-PreferContainerQuotaForCPUCountCPUdocker run --cpu-quota=300000--cpu-shares=2048 java -XX:ActiveProcessorCount=426
CPUJavaプロセスが認識するCPU数 (JDK 17)© 2023 Fujitsu Limiteddocker run --cpu-quota=300000 java CPUdocker run --cpu-quota=300000 --cpu-shares=2048 javaCPUdocker run --cpu-quota=300000--cpu-shares=2048 java -XX:+UseContainerCpuShares-XX:-PreferContainerQuotaForCPUCountCPUdocker run --cpu-quota=100000--cpu-shares=2048 java -XX:+UseContainerCpuShares-XX:-PreferContainerQuotaForCPUCountCPUdocker run --cpu-quota=300000--cpu-shares=2048 java -XX:ActiveProcessorCount=427
CPUスロットリング© 2023 Fujitsu Limitedスレッド1スレッド2時間0μ 1000μ 2000μquota=1000、period=1000 マイクロセカンドの場合スレッド3500μ消費500μ消費処理開始たとえ、他のコンテナがアイドル状態でも、CPUは割り当てられない処理開始処理開始Javaはマルチスレッド28
CPUスロットリングの検出方法© 2023 Fujitsu Limited⚫ nr_periods: フルになったperiodの回数⚫ nr_throttled: スロットリング回数⚫ throttled_time/throttled_usec: スロットリング時間(ナノ秒/マイクロ秒)コンテナ内の/sys/fs/cgroup/cpu/cpu.stat (cgroup v1)/sys/fs/cgroup/cpu.stat (cgroup v2)29
CPU Quotaを設定すると、CPUスロットリングの可能性ありCPU Shareだけだと、実際のCPU割当が決定的にならないJavaでの注意事項 - CPUリソース© 2023 Fujitsu Limited⚫ ベストエフォートでの対応⚫ 問題発生時は、ActiveProcessorCountや、各種パラメタの手動設定も検討⚫ Javaでは発生しやすいマルチスレッドで、知らない所でCPUを使っている⚫ 性能調査が難しくなる可能性あり30
Dockerでのメモリ割当て© 2023 Fujitsu Limiteddocker run --memory=2048mbdocker run --memory-swap=2048mbコンテナへの物理メモリ割当てコンテナへの物理メモリ+スワップ割当てスワッピングをさけるめに、両者を同一にする例例31
Javaでの注意事項 - メモリリソース© 2023 Fujitsu Limited-Xmxではなく、-XX:MaxRAMPercentageの方が安全docker inspectで、OOM killer発生を確認コンテナ割当メモリより、大きなヒープサイズ指定が可能OutOfMemoryErrorが出ない場合あり32
アジェンダ© 2023 Fujitsu LimitedJava エルゴノミクスDockerでのリソース制御サマリKubernetesでのリソース制御33
KubernetesでのCPUリソース割当て© 2023 Fujitsu Limited⚫ Requests :スケジューリングに使用⚫ Limits:CPU割当上限に使用RequestsとLimitsの2種類の設定RequestsとLimitsの両方を、コンテナごとに設定可能34
スケジューリング© 2023 Fujitsu LimitedCPU1ノード1 ノード2CPU4CPU2CPU3CPU1CPU4CPU2CPU335
スケジューリング© 2023 Fujitsu LimitedPod1Request3CPUデプロイCPU1ノード1 ノード2CPU4CPU2CPU3CPU1CPU4CPU2CPU336
スケジューリング© 2023 Fujitsu LimitedPod1Request3CPUデプロイCPU1ノード1 ノード2CPU4CPU2CPU3CPU1CPU4CPU2CPU3Pod1Pod1Pod137
スケジューリング© 2023 Fujitsu LimitedPod1Request3CPUデプロイPod2Request3CPUデプロイCPU1ノード1 ノード2CPU4CPU2CPU3CPU1CPU4CPU2CPU3Pod1Pod1Pod138
スケジューリング© 2023 Fujitsu LimitedPod1Request3CPUデプロイPod2Request3CPUデプロイCPU1ノード1 ノード2CPU4CPU2CPU3CPU1CPU4CPU2CPU3Pod1Pod1Pod1 Pod2Pod2Pod239
スケジューリング© 2023 Fujitsu LimitedCPU1ノード1 ノード2Pod1Request3CPUCPU4CPU2CPU3CPU1CPU4CPU2CPU3デプロイPod1Pod1Pod1Pod2Request3CPUデプロイPod2Pod2Pod2Pod3Request3CPUペンディング40
KubernetesでのCPU指定© 2023 Fujitsu LimitedCPU数単位、または、ミリコア単位で指定 (「2.5」と「2500m」は同値 )指定例ノードに0.5CPU分の空きがあれば、デプロイされる。0.5CPUの空きがなければ、ペンディング。spec:containers:- name: myappimage: myimageresource:requests:cpu: “500m”limits:cpu: “1000m”41
Requestsの実装© 2023 Fujitsu LimitedJDK 17では、デフォルトでは、プロセッサ数の計算に使用しない。JDK 8では、Limitsが指定されていなければ、プロセッサ数の計算に使用される。CPU Sharesなので、実際のCPU割当はRequest以上になることが可能Kubernetesの実装1CPU(=1000m)は、1024 CPU Sharesに換算Javaの実装42
Limitsの実装© 2023 Fujitsu Limited変換されたquotaが、プロセッサ数の計算に使用される。ただし、利用可能なプロセッサ数を超えない。periodは、100ミリ秒quotaは、Limits値 X 100 (ミリ秒) に変換quotaを使い切ると、PodにCPUは割当てられない (CPUスロットリング)Kubernetesの実装Javaの実装43
KubernetesでのCPUリソース設定© 2023 Fujitsu Limited指定しないと、CPUリソースが非決定的指定すると、CPUスロットリングになる可能性あり指定するか・しないかは、ノード内で他に何が動いているかによりけり指定すると、CPUリソース割り当ては流動的になる指定しないと、ノード内でのCPU割り当ては保証されないRequestsの指定Limitsの指定---> 性能調査が難しくなるかも44
Javaでの注意事項 – Requests & Limits© 2023 Fujitsu LimitedRequestsLimitsJavaのデフォルトLimitsを用いたCPU数を計算それだけのCPUが割り当てられるとは限らないK8S設定Javaで-PreferContainerQuotaForCPUCount設定Requestsを用いたCPU数を計算それ以上のCPUが割り当てられるかもしれないRequestsとLimitsが違う場合RequestsとLimitsが同じ場合RequestsLimitsLimitsを用いたCPU数を計算決定的にCPUが割り当てられるCPUスロットリングになる可能性ありノード全体で有効利用できない可能性ありCPUリソスーCPUリソスー45
KubernetesでのメモリRequests© 2023 Fujitsu Limitedスケジューリングに使用、ノードに空きがなければPendingRequests以上のメモリを使用可能ノードでメモリがなくなったとき、Requestsを超えたPodは、終了させられる(evicted)46
メモリRequests© 2023 Fujitsu LimitedPod1Request4GBPod2Request4GBデプロイデプロイ2GB2GBノード(8GB)47
メモリRequests© 2023 Fujitsu Limitedノード(8GB)Pod1Request4GBPod2Request4GBデプロイデプロイ6GB2GB48
メモリRequests© 2023 Fujitsu Limitedノード(8GB)Pod1Request4GBPod2Request4GBデプロイデプロイここで、Pod2がさらに1GB(合計3GB)使おうとすると。。。6GB2GB49
メモリRequests© 2023 Fujitsu Limitedノード(8GB)Pod1Request4GBPod2Request4GBデプロイデプロイ2GBOOM-killed50
メモリRequests© 2023 Fujitsu Limitedノード(8GB)Pod1Request4GBPod2Request4GBデプロイリスタート3GB51
KubernetesでのメモリLimits© 2023 Fujitsu Limitedメモリ割当上限に使用。Limits以上のメモリを使用すると、killされる。52
Kubernetesでのメモリリソース設定© 2023 Fujitsu Limited上限を適切に設定する指定しいないと、ホスト上のメモリをベースにJavaのエルゴノミクスが作動Limitsと同じ値を設定する途中で移動・Pendingになることを防ぐRequestsの指定Limitsの指定53
アジェンダ© 2023 Fujitsu LimitedJava エルゴノミクスDockerでのリソース制御サマリKubernetesでのリソース制御54
サマリ© 2023 Fujitsu Limitedメモリ不足は、コンテナダウン(OOM-kill)ですぐわかるコンテナとJavaの整合性CPUスロットリングは、性能に影響するが検出が難しい⚫ Share/Quotaの動作原理をふまえたJava設定⚫ Javaは生来的に、マルチスレッドActiveProcessor、コンパイラスレッド、GCスレッドチューニング⚫ OOM-killされないようにRequests/Limitsを設定する⚫ JavaヒープサイズはMaxRAMPercentageなどでOOM-kill防止⚫ 使用するJavaの版で動作確認する(マイナーアップでの非互換あり)55
© 2023 Fujitsu LimitedQuestion?56
Thank you© 2023 Fujitsu Limited