Slide 1

Slide 1 text

2023/10/19 Kubernetes Meetup Tokyo #61 JRE しか入ってない Pod で Java の heap dump を取りたい #k8sjp

Slide 2

Slide 2 text

Who am I. Name: こたつ&&みかん Account: @kota2and3kan WorkAt: Scalar, Inc Job: [Technical Support, Infra Engineer] Like: DB: [PostgreSQL, CockroachDB, ScalarDB, ScalarDL] Bouldering: 5Q Dislike: Real Cockroach

Slide 3

Slide 3 text

今日は Java App on k8s 環境でのデ バッグ的なお話をします

Slide 4

Slide 4 text

[前提] ちょっとだけ弊社のプロダクトを紹介 ● ScalarDB ○ 分散 Tx マネージャー。 ○ 複数種類の DB を跨いだ Tx が実現できる。 ○ 複数のマイクロサービスを跨いだ Tx も実現できる。 ● ScalarDL ○ DB に格納するデータの改ざん検知 (Byzantine Fault Detection) を可能にす るミドルウェア。 ○ 内部で ScalarDB を利用しており、複数種類の DB に対応している。 ● どちらもコンテナイメージを提供しており Kubernetes 上にデプロイできる。

Slide 5

Slide 5 text

[前提] ちょっとだけ ScalarDB を紹介 ScalarDB Cluster ScalarDB Cluster ScalarDB Cluster PostgreSQL MySQL DynamoDB Cosmos DB Client Tx Tx And so on…

Slide 6

Slide 6 text

ScalarDL Auditor [前提] ちょっとだけ ScalarDL を紹介 Database Database Client ScalarDL Ledger Read/Write & Tamper detection

Slide 7

Slide 7 text

[前提] ちょっとだけ弊社のプロダクトを紹介 ● どちらのプロダクトも VLDB (Database 分野のトップカンファレンスの 1つ) に論文が Accept されている。 ○ ScalarDB ■ https://www.vldb.org/pvldb/vol16/p3768-yamada.pdf ○ ScalarDL ■ https://www.vldb.org/pvldb/vol15/p1324-yamada.pdf

Slide 8

Slide 8 text

弊社製品の細かいことは 気にしなくてよいです              /) 
            ///) 
           /,.=゙''"/ 
    /     i f ,.r='"-‐'つ____   こまけぇこたぁいいんだよ!! 
   /      /   _,.-‐'~/⌒  ⌒\ 
     /   ,i   ,二ニ⊃( ●). (●)\ 
    /    ノ    il゙フ::::::⌒(__人__)⌒::::: \ 
       ,イ「ト、  ,!,!|     |r┬-|     | 
      / iトヾヽ_/ィ"\      `ー'´     / 
 


Slide 9

Slide 9 text

Pod Container 今日大事なこと ScalarDL Ledger JRE Pod Container ScalarDB Cluster JRE Pod Container ScalarDL Auditor JRE

Slide 10

Slide 10 text

Pod Container 今日大事なこと -> Java で書かれている ScalarDL Ledger JRE Pod Container ScalarDB Cluster JRE Pod Container ScalarDL Auditor JRE

Slide 11

Slide 11 text

ある日ユーザーから 1件の問い合わせが

Slide 12

Slide 12 text

普通は起き得ない 変なエラーが発生していた

Slide 13

Slide 13 text

いろいろ調べた結果 メモリ上のデータが壊れてる疑惑 が浮上した

Slide 14

Slide 14 text

Java の heap dump を取って調査をし ようということになった

Slide 15

Slide 15 text

手元の検証環境で一旦検証 $ kubectl get pod NAME READY STATUS RESTARTS AGE scalardb-cluster-node-5bf4676577-gdtjs 1/1 Running 0 8m8s scalardb-cluster-node-5bf4676577-hp8jd 1/1 Running 0 8m8s scalardb-cluster-node-5bf4676577-l9fxp 1/1 Running 0 8m8s $ kubectl exec -it scalardb-cluster-node-5bf4676577-gdtjs -- bash $ jps bash: jps: command not found $ jcmd bash: jcmd: command not found

Slide 16

Slide 16 text

_人人人人人人人人人人_ > command not found <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^

Slide 17

Slide 17 text

Pod Container ScalarDB Cluster JRE Pod (container) の中に入ってるのは...

Slide 18

Slide 18 text

Pod Container ScalarDB Cluster JRE Pod (container) の中に入ってるのは... -> JRE

Slide 19

Slide 19 text

JRE に jps や jcmd 等のデバッグツー ルは入ってない

Slide 20

Slide 20 text

jps や jcmd 等のデバッグツールを使う 場合 JDK が必要

Slide 21

Slide 21 text

詰み

Slide 22

Slide 22 text

と、諦める訳にはいかず...

Slide 23

Slide 23 text

● 正攻法は「shareProcessNamespace: true を設定して Pod を起動する」 「サイドカーとして JDK のコンテナを起動する」っぽい。 ○ https://speakerdeck.com/hhiroshell/jvm-on-kubernetes?slide=54 ○ 既存の Pod に shareProcessNamespace を設定するには Pod の再起動が 必要。 ○ 既存の Pod にサイドカーを追加するには Pod の再起動が必要。 ○ 手元の環境で検証してみたところ、Java 11 では jcmd で heap dump を取得 できたが、Java 8 ではできなかった... (原因不明) いろいろ調べてみた結果 (1)

Slide 24

Slide 24 text

● Kubernetes v1.26 以前の場合、「エフェメラルコンテナ (kubectl debug) で JDK のコンテナを起動する」という方法はできない模様。 ○ https://charleysdiary.hatenablog.com/entry/2019/12/19/033307 ○ jps 等の利用には SYS_PTRACE の capability が必要だが、v1.26 ではエ フェメラルコンテナに SYS_PTRACE を設定する方法が無いため、この方法 はつかない模様。 いろいろ調べてみた結果 (2)

Slide 25

Slide 25 text

● Kubernetes v1.27 以降の場合、「エフェメラルコンテナ (kubectl debug) で JDK のコンテナを起動する」という方法ができそう。 ○ v1.27 で kubectl debug に「--profile general」というオプションが追加されて いる。 ■ https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.2 7.md ○ この「--profile general」オプションを使うと、エフェメラルコンテナに SYS_PTRACE を設定できる。 ■ https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/1441-kubectl-debu g#debugging-profiles ○ ただし、このパターンでも「shareProcessNamespace: true」の設定は必要。 いろいろ調べてみた結果 (3)

Slide 26

Slide 26 text

● Kubernetes v1.27 以降の場合、「エフェメラルコンテナ (kubectl debug) で JDK のコンテナを起動する」という方法ができそう。 ○ 実際に v1.27 で検証したところ、Java 11 では jcmd で heap dump を 取得できたが、Java 8 ではできなかった... (原因不明) いろいろ調べてみた結果 (4)

Slide 27

Slide 27 text

● 事象が発生している環境が Kubernetes v1.27 未満だったので、「エフェメ ラルコンテナ (kubectl debug --profile general) で JDK のコンテナを起動 する」という方法は使えない。 ● 「サイドカーとして JDK のコンテナを起動する」方法を試してみたが、プロ ダクトが利用している Java 8 では何故かうまくいかなかった... (原因不明 / base image を Java 11 にしたらうまくいった) ● そもそも Pod に「shareProcessNamespace: true」が設定されていなかっ たので、どちらの方法でも Pod の再作成は避けられない。 弊社プロダクトの場合は...

Slide 28

Slide 28 text

諸々設定を変更して Pod を再作成す れば heap dump が取れそうなことは わかったが...

Slide 29

Slide 29 text

今回の事象は謎が多くこの時点では再 現手順も分かっていなかった

Slide 30

Slide 30 text

事象が発生している Pod のメモリ上に あるデータしか手掛かりがない

Slide 31

Slide 31 text

メモリ上のデータが失われてしまうので Pod を再起動することはできない

Slide 32

Slide 32 text

再び詰み

Slide 33

Slide 33 text

と思われたが...

Slide 34

Slide 34 text

jattach というツールを発見 https://github.com/jattach/jattach

Slide 35

Slide 35 text

● C 言語で書かれたツール。 ● README 曰く、 ○ The utility to send commands to a JVM process via Dynamic Attach mechanism. ○ All-in-one jmap + jstack + jcmd + jinfo functionality in a single tiny program. ○ No installed JDK required, works with just JRE. ● この jattach をコンテナの中にコピーすれば heap dump が取れる (jcmd が実行できる) という情報があった。 jattach

Slide 36

Slide 36 text

検証 (jattach をダウンロード) $ curl -s -OL https://github.com/jattach/jattach/releases/download/v2.1/jattach $ chmod +x ./jattach $ ls -l ./jattach -rwxr-xr-x 1 ubuntu ubuntu 24850 Oct 6 19:19 ./jattach

Slide 37

Slide 37 text

検証 (Pod 内にコピーして --help を実行) $ kubectl cp ./jattach scalardb-cluster-node-5bf4676577-gdtjs:/tmp $ kubectl exec -it scalardb-cluster-node-5bf4676577-gdtjs -- bash $ /tmp/jattach --help jattach 2.1 built on Jul 25 2022 Copyright 2021 Andrei Pangin Usage: jattach [args ...] Commands: load threaddump dumpheap setflag properties jcmd inspectheap datadump printflag agentProperties

Slide 38

Slide 38 text

検証 (PID を確認) $ ls -l /tmp/hsperfdata_scalardb/ total 32 -rw------- 1 scalardb scalardb 32768 Oct 6 10:26 1 ※PID 1 問題については別途対応予定 https://text.superbrothers.dev/200328-how-to-avoid-pid-1-problem-in-kubernetes/

Slide 39

Slide 39 text

検証 (メモリの情報を取得) $ /tmp/jattach $(basename $(ls -1 /tmp/hsperfdata_scalardb/*)) jcmd "GC.heap_info" Connected to remote JVM JVM response code = 0 def new generation total 122368K, used 78283K [0x0000000369e00000, 0x00000003722c0000, 0x00000004dbea0000) eden space 108800K, 71% used [0x0000000369e00000, 0x000000036ea72ff0, 0x0000000370840000) from space 13568K, 0% used [0x0000000370840000, 0x0000000370840000, 0x0000000371580000) to space 13568K, 0% used [0x0000000371580000, 0x0000000371580000, 0x00000003722c0000) tenured generation total 271744K, used 25951K [0x00000004dbea0000, 0x00000004ec800000, 0x00000007c0000000) the space 271744K, 9% used [0x00000004dbea0000, 0x00000004dd7f7c48, 0x00000004dd7f7e00, 0x00000004ec800000) Metaspace used 40442K, capacity 41092K, committed 41472K, reserved 1085440K class space used 4642K, capacity 4828K, committed 4864K, reserved 1048576K

Slide 40

Slide 40 text

検証 (Heap Dump を取得) $ /tmp/jattach 1 jcmd "GC.heap_dump /tmp/heap_dump" Connected to remote JVM JVM response code = 0 File exists $ ls -l /tmp/heap_dump -rw------- 1 scalardb scalardb 22499468 Oct 6 10:31 /tmp/heap_dump

Slide 41

Slide 41 text

検証 (Heap Dump ローカルにダウンロード) $ kubectl cp scalardb-cluster-node-5bf4676577-gdtjs:/tmp/heap_dump ./download_heap_dump tar: Removing leading `/' from member names $ file ./download_heap_dump ./download_heap_dump: Java HPROF dump, created Fri Oct 6 10:31:15 2023

Slide 42

Slide 42 text

検証 (Heap Dump 解析) $ jhat ./download_heap_dump Reading from ./download_heap_dump... Dump file created Fri Oct 06 19:31:15 JST 2023 Snapshot read, resolving... Resolving 201222 objects... Chasing references, expect 40 dots........................................ Eliminating duplicate references........................................ Snapshot resolved. Started HTTP server on port 7000 Server is ready.

Slide 43

Slide 43 text

jattach を使って無事 heap dump の取 得と解析ができた!!!

Slide 44

Slide 44 text

おまけ (時間があれば) ● jattach は C 言語で書かれているのに、動作要件が「JRE が入っているこ と」なのは何故なのか...? ● そもそも C 言語でかかれたバイナリを他の環境 (ビルドした環境とは違う 環境) にコピーして使うことにちょっと違和感がある...

Slide 45

Slide 45 text

おまけ (時間があれば) 試しに依存してる共有ライブラリを調べてみると、割とシンプルな雰囲気に見え る。 $ ldd ./jattach linux-vdso.so.1 (0x00007ffeb55d7000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f14c1ba0000) /lib64/ld-linux-x86-64.so.2 (0x00007f14c1dd2000)

Slide 46

Slide 46 text

シンプルな Hello World のバイナリと比較してみる。 $ gcc ./hello.c $ ./a.out Hello World $ ldd ./a.out linux-vdso.so.1 (0x00007ffd8397d000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff716fbb000) /lib64/ld-linux-x86-64.so.2 (0x00007ff7171f2000) おまけ (時間があれば) #include int main() { printf("Hello World\n"); return 0; }

Slide 47

Slide 47 text

おまけ (時間があれば) シンプルな Hello World と同じ共有ライブラリ、つまり libc にのみ依存している 感じだと思われる。 $ ldd ./jattach linux-vdso.so.1 (0x00007ffeb55d7000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f14c1ba0000) /lib64/ld-linux-x86-64.so.2 (0x00007f14c1dd2000) $ ldd ./a.out linux-vdso.so.1 (0x00007ffd8397d000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff716fbb000) /lib64/ld-linux-x86-64.so.2 (0x00007ff7171f2000)

Slide 48

Slide 48 text

● jattach は Hello World が動く環境であれば動きそうに見える (C の標準 Library である libc にしか依存してなさそう)。 ● (推測)「JRE が入ってる環境」->「JVM が動く環境」->「libc が入ってる環境 (JVM は C 言語で書かれてるっぽいので)」という感じになるはずなので、 「JRE が入ってれば動く」という感じなのかもしれない。 ● この辺りについては全然詳しくないので、C 言語のバイナリが動く条件等 について何かご存知の方がいれば教えていただけると嬉しいです。 おまけ (時間があれば)

Slide 49

Slide 49 text

● Kubernetes 上の (JRE しか入ってない) Pod で jcmd 等のデバッグツー ルを使うための正攻法は「shareProcessNamespace: true を設定」かつ 「サイドカーとして JDK のコンテナを起動」だと思われる。 ● Kubernetes v1.27 以降であれば「shareProcessNamespace: true を設 定」かつ「kubectl debug コマンドに『--profile general』オプションを付ける」 ことで、エフェメラルコンテナとして起動した JDK のコンテナから jcmd 等 のツールが実行できそう。 ● 何故か Java 8 ではこれらの方法が使えなかったが、詳細不明... ○ 何かご存じの方がいれば教えていただきたいです。 まとめ (1)

Slide 50

Slide 50 text

● jattach を使うと JRE しか入ってない Pod の中で jcmd を実行できた。 ● jattach は kubectl cp でバイナリを Pod 上にコピーするだけで使えるの で、Pod の再起動 (shareProcessNamespace の設定変更やサイドカー の追加) ができない場合でも heap dump が取れた。 ● 普段 Java App on k8s をやってるみなさん、この辺どうしてますか? ○ 他に良い方法がありそうな場合、教えていただけると嬉しいです。 まとめ (2)

Slide 51

Slide 51 text

Thank you!