Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

コンテナ環境でのJavaチューニング

 コンテナ環境でのJavaチューニング

Javaをコンテナ環境で使う際に、知らないうちに発生してしまうCPUスロットリングやOOM-killerなど、発生メカニズムやJavaVMのチューニング対処について。

Kenji Kazumura

June 05, 2023
Tweet

More Decks by Kenji Kazumura

Other Decks in Programming

Transcript

  1. © 2023 Fujitsu Limited 自己紹介 Jakarta EE 仕様策定委員 MicroProfile ステコミ委員

    JCP Executive Committee メンバー Eclipse Foundation ボードディレクター EclipseCon、JakartaOne、JJUGなどで登壇 2
  2. 本資料での注意事項 © 2023 Fujitsu Limited 本資料は、OpenJDK実装をベースに記載しています。 単に、「JDK」、「Java」と記載している場合、 OpenJDKの実装を意味していることがあります。 OpenJDK 17は、17.0.7

    OpenJDK 8は、8u372 の各バージョンを使用しています。 マイナーバージョンが違う場合は、 本資料の記載動作と違う場合があります。 3
  3. コンテナと、Javaエルゴノミクス © 2023 Fujitsu Limited コンテナ コンテナに割当てられた メモリ・CPUリソース 整合性 Java

    Javaが使えると思っている メモリ・CPUリソース コンテナ起動のたびに 変わる可能性あり 他のコンテナに影響される 可能性あり エルゴノミクス または 手動で設定 5
  4. Javaのエルゴノミクス © 2023 Fujitsu Limited GC種別 Javaヒープサイズ GCスレッド数 並行処理数 ・・・

    Javaでのエルゴノミクスとは、各種パラメタを JVMが自身が動作する環境をベースに自動的に設定すること エルゴミクス対象 例 7
  5. GC種別 (OpenJDK 8) © 2023 Fujitsu Limited 利用可能 GC エルゴノミクス

    対象 条件 Serial ✔ Parallel ✔ CMS N/A G1 N/A CPU数 < 2 || メモリ < 1792MB CPU数 ≧ 2 && メモリ ≧ 1792MB 8
  6. GC種別 (OpenJDK 17) © 2023 Fujitsu Limited 利用可能 GC エルゴノミクス

    対象 条件 Serial ✔ Parallel N/A G1 ✔ Z N/A N/A CPU数 < 2 || メモリ < 1792MB CPU数 ≧ 2 && メモリ ≧ 1792MB Shenandoah 9
  7. コンテナ環境でのコア数 © 2023 Fujitsu Limited 40% 30% ▪コンテナ環境 ▪非コンテナ環境 20%

    10% 使 用 率 コア数 https://newrelic.com/resources/report/2023-state-of-the-java-ecosystem 3 5 6 7 10 11 12 15 20 24 36 48 10
  8. Javaヒープサイズ © 2023 Fujitsu Limited 初期値:物理メモリの1/64 最大値:物理メモリの1/4 -XX:MaxRAMPercentage -Xmx エルゴノミクスによる設定

    -XX:InitialRAMPercentage -Xms 気にいらなければ、自分で設定 Javaヒープサイズの指定方法 (JDK 10 以降) 11
  9. 論理CPU数の影響 © 2023 Fujitsu Limited エルゴノミクスによる設定 (OpenJDK 17) GC スレッド数

    ・複雑 ・おおよそ、8+(n-8)*(5/8) コンパイラ スレッド数 ・かなり複雑 ・おおよそ、 int(log2 n) * (int)(log2 int(log2 n)) * 3 /2 ForkJoinPool 並行度数 ・おおよそ、n - 1 GC種別 前掲 n = 論理CPU数 13
  10. DockerでのCPUリソース割当て © 2023 Fujitsu Limited docker run --cpu-period=100000 --cpu-quota=200000 CPU

    Share CPU Quota docker run --cpu-shares=2048 コンテナに割り当てるCPU数を相対的に指定する デフォルト値は1024 period間に消費可能なCPU時間を指定する CPUスロットリングが発生しやすい CPU Sets 使用するCPUセットを指定する 一般的には使わない 例 例 15
  11. CPU1 CPU2 CPU Shares (ビジー) © 2023 Fujitsu Limited A:B:C=2:1:1でCPU時間が配分される

    コンテナC share:1024 コンテナB share:1024 それぞれのコンテナがビジー状態であれば、 コンテナA share:2048 16
  12. CPU2 アイドル CPU1 CPU Shares (アイドルあり) © 2023 Fujitsu Limited

    コンテナB share:1024 コンテナA、Cがアイドル状態になると・・・ コンテナBがCPU時間を総どり コンテナC share:1024 コンテナA share:2048 17
  13. Javaプロセスが認識するCPU数(cgroup v2) © 2023 Fujitsu Limited 1024*n ± 512 がおおよその境

    docker run java docker run --cpu-shares=1024 java docker run --cpu-shares=1536 java docker run --cpu-shares=2048 java docker run --cpu-shares=2560 java ---> ホストCPU数 ---> 1CPU ---> 2CPU ---> 2CPU ---> 3CPU JDK 8、JDK 17 with -XX:+UseContainerCpuShares の場合 18
  14. Javaプロセスが認識するCPU数(cgroup v1) © 2023 Fujitsu Limited 1024がマジックナンバー docker run java

    docker run --cpu-shares=1023 java docker run --cpu-shares=1024 java docker run --cpu-shares=1025 java docker run --cpu-shares=2049 java ---> ホストCPU数 ---> 1CPU ---> ホストCPU数 ---> 2CPU ---> 3CPU JDK 8、JDK 17 with -XX:+UseContainerCpuShares の場合 19
  15. Javaプロセスが認識するCPU数 © 2023 Fujitsu Limited デフォルトでは、CPU Sharesは参照しない docker run java

    docker run --cpu-shares=1023 java docker run --cpu-shares=1024 java docker run --cpu-shares=1025 java docker run --cpu-shares=2049 java ---> ホストCPU数 --->ホストCPU数 ---> ホストCPU数 --->ホストCPU数 --->ホストCPU数 JDK 17 (デフォルト -XX:-UseContainerCpuShares) の場合 20
  16. Java における CPU Share 注意事項 © 2023 Fujitsu Limited 実際にコンテナに割当てられるCPU時間は、

    他のコンテナの状態に依存する => 非決定的 Javaプロセスからは、他のコンテナの状態はわからない => 変動するCPUを前提にプログラムするのは難しい 21
  17. Java と CPU Share ミスマッチ (1) © 2023 Fujitsu Limited

    ホストマシンのCPU数が3の場合 コンテナA コンテナB --cpu-shares= 1024 2048 コンテナに割当てられる CPU数(CPU時間) 1 2 Javaが認識するCPU数 3 2(JDK8) or 3(JDK17) 例 ミスマッチ ミスマッチ cgroup v1 の例 22
  18. Java と CPU Share ミスマッチ (2) © 2023 Fujitsu Limited

    ホストマシンのCPU数が3の場合 コンテナA コンテナB --cpu-shares= 1500 3000 コンテナに割当てられる CPU数(CPU時間) 1 2 Javaが認識するCPU数 1 3 例 ミスマッチ cgroup v2 の例 23
  19. Docker CPU Quota © 2023 Fujitsu Limited quota : period間にコンテナが消費できるCPU時間(マイクロセカンド)

    runサブコマンドに、「--cpu-quota=」を指定 quotaを使い切るとコンテナにCPUは割り当てられない CPUスロットリング period : デフォルトは100マイクロセカンド runサブコマンドに、「--cpu-period=」を指定 24
  20. 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 n Javaの計算が気にいらなければ、ActiveProcessorCountを使用。 25
  21. CPU Javaプロセスが認識するCPU数 (JDK 8) © 2023 Fujitsu Limited docker run

    --cpu-quota=300000 java CPU docker run --cpu-quota=300000 --cpu-shares=2048 java CPU docker run --cpu-quota=300000 --cpu-shares=2048 java -XX:-PreferContainerQuotaForCPUCount CPU docker run --cpu-quota=100000 --cpu-shares=2048 java -XX:-PreferContainerQuotaForCPUCount CPU docker run --cpu-quota=300000 --cpu-shares=2048 java -XX:ActiveProcessorCount=4 26
  22. CPU Javaプロセスが認識するCPU数 (JDK 17) © 2023 Fujitsu Limited docker run

    --cpu-quota=300000 java CPU docker run --cpu-quota=300000 --cpu-shares=2048 java CPU docker run --cpu-quota=300000 --cpu-shares=2048 java -XX:+UseContainerCpuShares -XX:-PreferContainerQuotaForCPUCount CPU docker run --cpu-quota=100000 --cpu-shares=2048 java -XX:+UseContainerCpuShares -XX:-PreferContainerQuotaForCPUCount CPU docker run --cpu-quota=300000 --cpu-shares=2048 java -XX:ActiveProcessorCount=4 27
  23. CPUスロットリング © 2023 Fujitsu Limited スレッド1 スレッド2 時間 0μ 1000μ

    2000μ quota=1000、period=1000 マイクロセカンドの場合 スレッド3 500μ消費 500μ消費 処理開始 たとえ、他のコンテナがアイドル 状態でも、CPUは割り当てられない 処理開始 処理開始 Javaはマルチスレッド 28
  24. 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
  25. CPU Quotaを設定すると、 CPUスロットリングの可能性あり CPU Shareだけだと、 実際のCPU割当が決定的にならない Javaでの注意事項 - CPUリソース ©

    2023 Fujitsu Limited ⚫ ベストエフォートでの対応 ⚫ 問題発生時は、ActiveProcessorCountや、各種パラメタの 手動設定も検討 ⚫ Javaでは発生しやすい マルチスレッドで、知らない所でCPUを使っている ⚫ 性能調査が難しくなる可能性あり 30
  26. Dockerでのメモリ割当て © 2023 Fujitsu Limited docker run --memory=2048mb docker run

    --memory-swap=2048mb コンテナへの物理メモリ割当て コンテナへの物理メモリ+スワップ割当て スワッピングをさけるめに、両者を同一にする 例 例 31
  27. Javaでの注意事項 - メモリリソース © 2023 Fujitsu Limited -Xmxではなく、-XX:MaxRAMPercentageの方が安全 docker inspectで、OOM

    killer発生を確認 コンテナ割当メモリより、大きなヒープサイズ指定が可能 OutOfMemoryErrorが出ない場合あり 32
  28. KubernetesでのCPUリソース割当て © 2023 Fujitsu Limited ⚫ Requests :スケジューリングに使用 ⚫ Limits:CPU割当上限に使用

    RequestsとLimitsの2種類の設定 RequestsとLimitsの両方を、 コンテナごとに設定可能 34
  29. スケジューリング © 2023 Fujitsu Limited Pod1 Request 3CPU デプロイ CPU1

    ノード1 ノード2 CPU4 CPU2 CPU3 CPU1 CPU4 CPU2 CPU3 36
  30. スケジューリング © 2023 Fujitsu Limited Pod1 Request 3CPU デプロイ CPU1

    ノード1 ノード2 CPU4 CPU2 CPU3 CPU1 CPU4 CPU2 CPU3 Pod1 Pod1 Pod1 37
  31. スケジューリング © 2023 Fujitsu Limited Pod1 Request 3CPU デプロイ Pod2

    Request 3CPU デプロイ CPU1 ノード1 ノード2 CPU4 CPU2 CPU3 CPU1 CPU4 CPU2 CPU3 Pod1 Pod1 Pod1 38
  32. スケジューリング © 2023 Fujitsu Limited Pod1 Request 3CPU デプロイ Pod2

    Request 3CPU デプロイ CPU1 ノード1 ノード2 CPU4 CPU2 CPU3 CPU1 CPU4 CPU2 CPU3 Pod1 Pod1 Pod1 Pod2 Pod2 Pod2 39
  33. スケジューリング © 2023 Fujitsu Limited CPU1 ノード1 ノード2 Pod1 Request

    3CPU CPU4 CPU2 CPU3 CPU1 CPU4 CPU2 CPU3 デプロイ Pod1 Pod1 Pod1 Pod2 Request 3CPU デプロイ Pod2 Pod2 Pod2 Pod3 Request 3CPU ペンディング 40
  34. KubernetesでのCPU指定 © 2023 Fujitsu Limited CPU数単位、または、ミリコア単位で指定 (「2.5」と「2500m」は同値 ) 指定例 ノードに

    0.5CPU分の空きがあれば、 デプロイされる。 0.5CPUの空きがなければ、 ペンディング。 spec: containers: - name: myapp image: myimage resource: requests: cpu: “500m” limits: cpu: “1000m” 41
  35. Requestsの実装 © 2023 Fujitsu Limited JDK 17では、デフォルトでは、プロセッサ数の計算に使用しない。 JDK 8では、Limitsが指定されていなければ、プロセッサ数の計算 に使用される。

    CPU Sharesなので、 実際のCPU割当はRequest以上になることが可能 Kubernetesの実装 1CPU(=1000m)は、1024 CPU Sharesに換算 Javaの実装 42
  36. Limitsの実装 © 2023 Fujitsu Limited 変換されたquotaが、プロセッサ数の計算に使用される。 ただし、利用可能なプロセッサ数を超えない。 periodは、100ミリ秒 quotaは、Limits値 X

    100 (ミリ秒) に変換 quotaを使い切ると、PodにCPUは割当てられない (CPUスロットリング) Kubernetesの実装 Javaの実装 43
  37. Javaでの注意事項 – Requests & Limits © 2023 Fujitsu Limited Requests

    Limits Javaのデフォルト Limitsを用いたCPU数を計算 それだけのCPUが割り当てられるとは限らない K8S設定 Javaで-PreferContainerQuotaForCPUCount設定 Requestsを用いたCPU数を計算 それ以上のCPUが割り当てられるかもしれない RequestsとLimitsが違う場合 RequestsとLimitsが同じ場合 Requests Limits Limitsを用いたCPU数を計算 決定的にCPUが割り当てられる CPUスロットリングになる可能性あり ノード全体で有効利用できない可能性あり CPU リ ソ ス ー CPU リ ソ ス ー 45
  38. メモリRequests © 2023 Fujitsu Limited Pod1 Request 4GB Pod2 Request

    4GB デプロイ デプロイ 2GB 2GB ノード(8GB) 47
  39. メモリRequests © 2023 Fujitsu Limited ノード(8GB) Pod1 Request 4GB Pod2

    Request 4GB デプロイ デプロイ ここで、Pod2がさらに1GB(合計3GB)使おうとすると。。。 6GB 2GB 49
  40. メモリRequests © 2023 Fujitsu Limited ノード(8GB) Pod1 Request 4GB Pod2

    Request 4GB デプロイ デプロイ 2GB OOM-killed 50
  41. サマリ © 2023 Fujitsu Limited メモリ不足は、コンテナダウン(OOM-kill)ですぐわかる コンテナとJavaの整合性 CPUスロットリングは、性能に影響するが検出が難しい ⚫ Share/Quotaの動作原理をふまえたJava設定

    ⚫ Javaは生来的に、マルチスレッド ActiveProcessor、コンパイラスレッド、GCスレッドチューニング ⚫ OOM-killされないようにRequests/Limitsを設定する ⚫ JavaヒープサイズはMaxRAMPercentageなどでOOM-kill防止 ⚫ 使用するJavaの版で動作確認する(マイナーアップでの非互換あり) 55