KubernetesでのJava Flight Recorderの手動記録

これは、追加のツールが利用できないコンテナ化された環境でKeycloakのJava Flight Recorder記録を作成する方法について説明しています。

概要

Java Flight Recorder(JFR)は、Java仮想マシンのイベント(フレイムグラフに組み立てることができるスレッドダンプを含む)を記録し、パフォーマンス分析に使用できます。本格的なセットアップでは、Cryostatを使用したパフォーマンスメトリクスのキャプチャが自動化された方法を提供し、カスタムKeycloakイメージは不要です。そのようなセットアップが利用できない場合は、以下の手順に従ってJFRをキャプチャしてください。

これは、OpenShift内ではasync profilingが利用できないため(私の知る限り)、async profilingを使用していません。したがって、記録にはセーフポイントバイアス問題が発生します。コンテナ内のJavaプロファイリングを参照してください。

Keycloakイメージの準備

次の手順では、Java Flight Recordingを開始するためにコンテナ内にjcmdが存在し、コンテナから記録を取得するためにkubectl cpを使用できるようにtarが存在する必要があります。

古いバージョンのKeycloakにはこれらのツールが含まれていますが、新しいバージョンのKeycloakイメージには、イメージをより小さく、より安全にするために含まれていません。したがって、最初のステップは、これらのツールを使用してカスタムKeycloakイメージを作成することです。これを行うには、Keycloakイメージをスクラッチから作成するか、必要なパッケージでKeycloakイメージを更新する2つの方法があります。

Keycloakをスクラッチからビルドする

KeycloakのメインリポジトリからKeycloakのカスタムディストリビューションをビルドしている場合は、quarkus/container/Dockerfileファイルを変更し、次の行を交換します。

RUN bash /tmp/ubi-null.sh java-17-openjdk-headless glibc-langpack-en

を次のように変更します。

RUN bash /tmp/ubi-null.sh java-17-openjdk-devel tar glibc-langpack-en

次に、KubernetesでのデプロイメントにカスタムKeycloakイメージを使用するの説明に従って、イメージをビルドします。

イメージにRPMパッケージを追加する

コンテナに関するKeycloakドキュメントには、パッケージを追加する方法に関するセクションが含まれています。2つのパッケージjava-17-openjdk-devel tarを追加するには、次のようなDockerfileを使用します。

FROM registry.access.redhat.com/ubi9 AS ubi-micro-build
RUN mkdir -p /mnt/rootfs
RUN dnf install --installroot /mnt/rootfs java-17-openjdk-devel tar --releasever 9 --setopt install_weak_deps=false --nodocs -y; dnf --installroot /mnt/rootfs clean all

FROM quay.io/keycloak/keycloak
COPY --from=ubi-micro-build /mnt/rootfs /

次に、KubernetesでのデプロイメントにカスタムKeycloakイメージを使用するの説明に従って、イメージを使用します。

JVMオプションの更新

Keycloak 23以降では、Keycloakがデフォルトで512を使用するため、-XX:FlightRecorderOptions=stackdepth JVMオプションをオーバーライドする必要はなくなりました。

Keycloakは、その呼び出しに非常に深いスタックトレースを使用します。フレイムグラフを使用できるようにするには、次のJVMオプションを追加してスタックフレームの数を増やします。

-XX:FlightRecorderOptions=stackdepth=512

Keycloak Operatorを使用している場合、これはKeycloakのCustomResourceを介してKeycloakイメージに渡すことができます。

apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
spec:
  unsupported:
    podTemplate:
      spec:
        containers:
          - env:
              - name: JAVA_OPTS_APPEND
                value: >
                  -XX:FlightRecorderOptions=stackdepth=512

Operatorによって管理されているStatefulSetまたはDeploymentを実行している場合は、Operatorを停止し、StatefulSetまたはDeploymentを手動で更新して、Javaオプションを追加または拡張することを検討してください。

記録の開始

記録を開始するには、コンテナ内でコマンドを発行します。

kubectl exec -n namespace pod -- jcmd 1 JFR.start duration=60s filename=/tmp/recording.jfr settings=/usr/lib/jvm/java/lib/jfr/profile.jfc

1は、すべてのQuarkusベースのKeycloakコンテナのデフォルトであるJavaプロセスのプロセスIDです。Wildflyベースのディストリビューションの場合、これは異なるプロセスIDである可能性があります。探しているプロセスIDを見つけるには、パラメータなしでjcmdを使用して、すべてのJavaプロセスIDを一覧表示します。

Podで複数のコンテナが実行されている場合は、CLIオプション-c containerを追加します。

profile.jfcには、キャプチャする内容に関する指示が含まれています。profile.jfcは、JVMに同梱されている標準プロファイルの1つであり、「プロファイリング」を意味します。多くの情報を収集し、数分間情報を収集することを目的としています。1分間の記録でも約5メガバイトのデータが収集されるため、時間間隔を短くしてください。必要な情報を収集するために必要に応じて調整してください。

記録の取得

記録を取得するには、次のコマンドを発行します。

kubectl cp -n namespace keycloak pod:/tmp/recording.jfr recording.jfr --retries 999

Podで複数のコンテナが実行されている場合は、CLIオプション-c containerを追加します。

CLIオプション--retries 999は、そうしないと失敗する可能性がある大きなファイルのダウンロードを再開するのに役立ちます。

記録の分析

詳細については、Java Flight Recorder記録の分析を参照してください。