Slide 1

Slide 1 text

© 2023 Fujitsu Limited 2023/06/04 数村憲治 コンテナ環境での Javaチューニング JJUG CCC Spring 2023 @kkzr

Slide 2

Slide 2 text

© 2023 Fujitsu Limited 自己紹介 Jakarta EE 仕様策定委員 MicroProfile ステコミ委員 JCP Executive Committee メンバー Eclipse Foundation ボードディレクター EclipseCon、JakartaOne、JJUGなどで登壇 2

Slide 3

Slide 3 text

本資料での注意事項 © 2023 Fujitsu Limited 本資料は、OpenJDK実装をベースに記載しています。 単に、「JDK」、「Java」と記載している場合、 OpenJDKの実装を意味していることがあります。 OpenJDK 17は、17.0.7 OpenJDK 8は、8u372 の各バージョンを使用しています。 マイナーバージョンが違う場合は、 本資料の記載動作と違う場合があります。 3

Slide 4

Slide 4 text

アジェンダ © 2023 Fujitsu Limited Java エルゴノミクス Dockerでのリソース制御 サマリ Kubernetesでのリソース制御 4

Slide 5

Slide 5 text

コンテナと、Javaエルゴノミクス © 2023 Fujitsu Limited コンテナ コンテナに割当てられた メモリ・CPUリソース 整合性 Java Javaが使えると思っている メモリ・CPUリソース コンテナ起動のたびに 変わる可能性あり 他のコンテナに影響される 可能性あり エルゴノミクス または 手動で設定 5

Slide 6

Slide 6 text

アジェンダ © 2023 Fujitsu Limited Java エルゴノミクス Dockerでのリソース制御 サマリ Kubernetesでのリソース制御 6

Slide 7

Slide 7 text

Javaのエルゴノミクス © 2023 Fujitsu Limited GC種別 Javaヒープサイズ GCスレッド数 並行処理数 ・・・ Javaでのエルゴノミクスとは、各種パラメタを JVMが自身が動作する環境をベースに自動的に設定すること エルゴミクス対象 例 7

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

コンテナ環境でのコア数 © 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

Slide 11

Slide 11 text

Javaヒープサイズ © 2023 Fujitsu Limited 初期値:物理メモリの1/64 最大値:物理メモリの1/4 -XX:MaxRAMPercentage -Xmx エルゴノミクスによる設定 -XX:InitialRAMPercentage -Xms 気にいらなければ、自分で設定 Javaヒープサイズの指定方法 (JDK 10 以降) 11

Slide 12

Slide 12 text

コンテナ環境でのJavaヒープサイズ © 2023 Fujitsu Limited https://newrelic.com/resources/report/2023-state-of-the-java-ecosystem 使 用 率 JVMヒープサイズ設定 30% 20% 10% ■コンテナ環境 ■非コンテナ環境 MB GB GB GB GB GB GB GB GB GB 12

Slide 13

Slide 13 text

論理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

Slide 14

Slide 14 text

アジェンダ © 2023 Fujitsu Limited Java エルゴノミクス Dockerでのリソース制御 サマリ Kubernetesでのリソース制御 14

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

CPU1 CPU2 CPU Shares (ビジー) © 2023 Fujitsu Limited A:B:C=2:1:1でCPU時間が配分される コンテナC share:1024 コンテナB share:1024 それぞれのコンテナがビジー状態であれば、 コンテナA share:2048 16

Slide 17

Slide 17 text

CPU2 アイドル CPU1 CPU Shares (アイドルあり) © 2023 Fujitsu Limited コンテナB share:1024 コンテナA、Cがアイドル状態になると・・・ コンテナBがCPU時間を総どり コンテナC share:1024 コンテナA share:2048 17

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Java における CPU Share 注意事項 © 2023 Fujitsu Limited 実際にコンテナに割当てられるCPU時間は、 他のコンテナの状態に依存する => 非決定的 Javaプロセスからは、他のコンテナの状態はわからない => 変動するCPUを前提にプログラムするのは難しい 21

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Docker CPU Quota © 2023 Fujitsu Limited quota : period間にコンテナが消費できるCPU時間(マイクロセカンド) runサブコマンドに、「--cpu-quota=」を指定 quotaを使い切るとコンテナにCPUは割り当てられない CPUスロットリング period : デフォルトは100マイクロセカンド runサブコマンドに、「--cpu-period=」を指定 24

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

CPUスロットリング © 2023 Fujitsu Limited スレッド1 スレッド2 時間 0μ 1000μ 2000μ quota=1000、period=1000 マイクロセカンドの場合 スレッド3 500μ消費 500μ消費 処理開始 たとえ、他のコンテナがアイドル 状態でも、CPUは割り当てられない 処理開始 処理開始 Javaはマルチスレッド 28

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

CPU Quotaを設定すると、 CPUスロットリングの可能性あり CPU Shareだけだと、 実際のCPU割当が決定的にならない Javaでの注意事項 - CPUリソース © 2023 Fujitsu Limited ⚫ ベストエフォートでの対応 ⚫ 問題発生時は、ActiveProcessorCountや、各種パラメタの 手動設定も検討 ⚫ Javaでは発生しやすい マルチスレッドで、知らない所でCPUを使っている ⚫ 性能調査が難しくなる可能性あり 30

Slide 31

Slide 31 text

Dockerでのメモリ割当て © 2023 Fujitsu Limited docker run --memory=2048mb docker run --memory-swap=2048mb コンテナへの物理メモリ割当て コンテナへの物理メモリ+スワップ割当て スワッピングをさけるめに、両者を同一にする 例 例 31

Slide 32

Slide 32 text

Javaでの注意事項 - メモリリソース © 2023 Fujitsu Limited -Xmxではなく、-XX:MaxRAMPercentageの方が安全 docker inspectで、OOM killer発生を確認 コンテナ割当メモリより、大きなヒープサイズ指定が可能 OutOfMemoryErrorが出ない場合あり 32

Slide 33

Slide 33 text

アジェンダ © 2023 Fujitsu Limited Java エルゴノミクス Dockerでのリソース制御 サマリ Kubernetesでのリソース制御 33

Slide 34

Slide 34 text

KubernetesでのCPUリソース割当て © 2023 Fujitsu Limited ⚫ Requests :スケジューリングに使用 ⚫ Limits:CPU割当上限に使用 RequestsとLimitsの2種類の設定 RequestsとLimitsの両方を、 コンテナごとに設定可能 34

Slide 35

Slide 35 text

スケジューリング © 2023 Fujitsu Limited CPU1 ノード1 ノード2 CPU4 CPU2 CPU3 CPU1 CPU4 CPU2 CPU3 35

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

スケジューリング © 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

Slide 40

Slide 40 text

スケジューリング © 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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Requestsの実装 © 2023 Fujitsu Limited JDK 17では、デフォルトでは、プロセッサ数の計算に使用しない。 JDK 8では、Limitsが指定されていなければ、プロセッサ数の計算 に使用される。 CPU Sharesなので、 実際のCPU割当はRequest以上になることが可能 Kubernetesの実装 1CPU(=1000m)は、1024 CPU Sharesに換算 Javaの実装 42

Slide 43

Slide 43 text

Limitsの実装 © 2023 Fujitsu Limited 変換されたquotaが、プロセッサ数の計算に使用される。 ただし、利用可能なプロセッサ数を超えない。 periodは、100ミリ秒 quotaは、Limits値 X 100 (ミリ秒) に変換 quotaを使い切ると、PodにCPUは割当てられない (CPUスロットリング) Kubernetesの実装 Javaの実装 43

Slide 44

Slide 44 text

KubernetesでのCPUリソース設定 © 2023 Fujitsu Limited 指定しないと、CPUリソースが非決定的 指定すると、CPUスロットリングになる可能性あり 指定するか・しないかは、 ノード内で他に何が動いているかによりけり 指定すると、CPUリソース割り当ては流動的になる 指定しないと、ノード内でのCPU割り当ては保証されない Requestsの指定 Limitsの指定 ---> 性能調査が難しくなるかも 44

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

KubernetesでのメモリRequests © 2023 Fujitsu Limited スケジューリングに使用、 ノードに空きがなければPending Requests以上のメモリを使用可能 ノードでメモリがなくなったとき、 Requestsを超えたPodは、終了させられる(evicted) 46

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

メモリRequests © 2023 Fujitsu Limited ノード(8GB) Pod1 Request 4GB Pod2 Request 4GB デプロイ デプロイ 2GB OOM-killed 50

Slide 51

Slide 51 text

メモリRequests © 2023 Fujitsu Limited ノード(8GB) Pod1 Request 4GB Pod2 Request 4GB デプロイ リスタート 3GB 51

Slide 52

Slide 52 text

KubernetesでのメモリLimits © 2023 Fujitsu Limited メモリ割当上限に使用。 Limits以上のメモリを使用すると、killされる。 52

Slide 53

Slide 53 text

Kubernetesでのメモリリソース設定 © 2023 Fujitsu Limited 上限を適切に設定する 指定しいないと、ホスト上のメモリをベースに Javaのエルゴノミクスが作動 Limitsと同じ値を設定する 途中で移動・Pendingになることを防ぐ Requestsの指定 Limitsの指定 53

Slide 54

Slide 54 text

アジェンダ © 2023 Fujitsu Limited Java エルゴノミクス Dockerでのリソース制御 サマリ Kubernetesでのリソース制御 54

Slide 55

Slide 55 text

サマリ © 2023 Fujitsu Limited メモリ不足は、コンテナダウン(OOM-kill)ですぐわかる コンテナとJavaの整合性 CPUスロットリングは、性能に影響するが検出が難しい ⚫ Share/Quotaの動作原理をふまえたJava設定 ⚫ Javaは生来的に、マルチスレッド ActiveProcessor、コンパイラスレッド、GCスレッドチューニング ⚫ OOM-killされないようにRequests/Limitsを設定する ⚫ JavaヒープサイズはMaxRAMPercentageなどでOOM-kill防止 ⚫ 使用するJavaの版で動作確認する(マイナーアップでの非互換あり) 55

Slide 56

Slide 56 text

© 2023 Fujitsu Limited Question? 56

Slide 57

Slide 57 text

Thank you © 2023 Fujitsu Limited