#chiroito ’s blog

Java を中心とした趣味の技術について

コンテナ時代における最新のJava&JVM監視

私は、OpenJDKのCommitter業や仕事でミドルウェアのSolution Architectとして活動している関係上、最近はコンテナ上でJavaアプリケーションを動かすことが非常に多いです。

KubernetesでJavaアプリを監視する場合には、Elasticsearch+KibanaやPrometheus+GrafanaなどでログやMBeanを監視する方法が一般的に行われています。 Java 11では有償JDKに含まれていた機能がOpenJDKへ寄贈され、JDK Flight Recorder (JFR)として生まれ変わりました。JFRはJVMの内部の情報やその上で動くJavaアプリケーションの様々な情報をほとんど負荷無く記録し、ファイルとして取得できます。このファイルをJDK Mission Controlなどのツールを使って確認し、これまでより詳細に分析できます。

これまででも、コンテナ環境においてもJFRを使用できました。しかし、記録の開始・停止などの管理やファイルとしての取得などを運用する環境を効率よく構築するのは非常に大変でした。

そこで誕生したのが Cryostat (旧名:ContainerJFR)です。Cryostatはコンテナ上で動いているJavaアプリケーションに接続して、JFRを管理します。また、JFRの情報分析結果の簡易レポートの表示やJFRファイルのダウンロードができるWeb UIが用意されている他、JFRで記録された情報をGrafanaへ取込できるため、問題発生時に非常に役立つツールになっています。これはKubernetes Operatorも用意されており、環境の構築も簡単にできます。

f:id:chiroito:20210622105326p:plain
JFRの情報をGrafanaで確認

今回は、Kubernetes上にCryostatをインストールする方法、サンプルのJavaアプリケーションを監視する方法、分析に役立つツールへのアクセス方法を紹介します。

また、今回は用意した環境の都合上、KubernetesとしてOpenShiftを使用します。

今回の環境

  • OpenShift 4.5のクラスタ(Azure Red Hat OpenShift、Red Hat OpenShift Service on AWSも可)
  • クライアントとなるRHEL 8.2

OpenShiftへCryostatをインストール

Kubernetes上でCryostatを使う場合にはCryostat Operatorを使用すると便利です。Cryostat OperatorをはOperatorHub.ioに無いため、自らビルドする必要があります。

Operator SDKをインストール

Cryostat OperatorはOperator SDKに依存関係を持っています。そのため、Cryostat Operatorをインストールするには、まずOperator SDKをインストールします。

export ARCH=$(case $(uname -m) in x86_64) echo -n amd64 ;; aarch64) echo -n arm64 ;; *) echo -n $(uname -m) ;; esac)
export OS=$(uname | awk '{print tolower($0)}')
export OPERATOR_SDK_DL_URL=https://github.com/operator-framework/operator-sdk/releases/download/v1.8.0
curl -LO ${OPERATOR_SDK_DL_URL}/operator-sdk_${OS}_${ARCH}
chmod +x operator-sdk_${OS}_${ARCH} && sudo mv operator-sdk_${OS}_${ARCH} /usr/local/bin/operator-sdk

参考:Installation | Operator SDK

Cryostat OperatorをOpenShiftへインストール

Operator SDKがインストールし終わったらCryostat Operatorをインストールしましょう。

まずは、OpenShiftへログインして今回使用したいプロジェクト(名前空間)を指定します。

oc login -u [ユーザ名] -p [パスワード]  [APIのURL]
oc project [プロジェクト名]

次に、Cryostat Operatorのビルドに必要なmakegoをインストールします。そして、Cryostat Operatorのソースコードを落としてからmakeコマンドでビルドしてインストールします。

sudo yum install -y make go
git clone https://github.com/cryostatio/cryostat-operator.git
cd cryostat-operator
make cert_manager
make deploy_bundle

参考:GitHub - cryostatio/cryostat-operator: An OpenShift Operator to facilitate setup and management of Cryostast and expose the Cryostat API through Kubernetes Custom Resources.

Cryostat Operatorがインストールされたことを確認します。

oc get deployments cryostat-operator-controller-manager

以下のように表示されていれば成功です。

NAME                                   READY   UP-TO-DATE   AVAILABLE   AGE
cryostat-operator-controller-manager   1/1     1            1           1h

OpenShiftの場合はWebコンソールのInstalled Operatorsで表示される一覧に追加されています。

f:id:chiroito:20210622104835p:plain
Webコンソールで確認

Cryostatをデプロイ

Cryostat Operatorのインストールが終わったので、Cryostatをデプロイしてみましょう。

cat <<EOF > cryostat.yaml
apiVersion: operator.cryostat.io/v1beta1
kind: Cryostat
metadata:
  name: cryostat-sample
spec:
  minimal: false
EOF
oc create -f cryostat.yaml

参考:cryostat-operator/config.md at main · cryostatio/cryostat-operator · GitHub

ここではmetadata.nameに名前を設定します。spec.minimalは最小構成でインストールするかどうかを表します。最小構成はCryostatのみをインストールします。最小構成ではカスタマイズされたGrafanaとその関連ツールはインストールされません。カスタマイズされたGrafanaがあると便利なのでfalseにする事をオススメします。

Cryostatがデプロイされたことを確認します。

oc get deployments cryostat-sample

以下のようになっていればデプロイ成功です。

NAME              READY   UP-TO-DATE   AVAILABLE   AGE
cryostat-sample   1/1     1            1           20h

こちらもOpenShiftの場合はWebコンソールのInstalled OperatorからGUIで作成できます。Cryostatタブを選択するとCryostatの一覧を表示します。そこでCreate Cryostatと言うボタンがあるので、ここから作成画面へ遷移します。

f:id:chiroito:20210622110434p:plain
Cryostatのページ

Cryostatを作成するページに移動するので、必要な情報を入力してCreateボタンを押せば作成されます。 ここではCLIで作成したとおり、名前をcryostat-sample、最小構成をfalseにして作成しています。

f:id:chiroito:20210622110627p:plain
Cryostatを作成

ここまでの作業が完了するとCryostatを使用する準備が完了しました。Webコンソール上のトポロジでは以下のようになっています。

f:id:chiroito:20210622113630p:plain
Cryostatインストール後の状況

環境構築はこれで終わりです。JFRを効率よく管理する環境が簡単に作れました。

Cryostat Web UIへのログイン

Cryostatは便利なWeb の UIを提供しており、デプロイが正常に完了していると、このUIへアクセスできるようになっています。このUIへアクセスするにはそのURLが必要になりますが、URLはocコマンドやWebコンソールから確認できます。

ocコマンドでURLを取得する場合は、先ほど作成したcryostat-sampleに対して以下のように実行します。

oc get route cryostat-sample

出力結果のHOST/PORTがCryostatのUIのURLになります。

NAME              HOST/PORT                                                                    PATH   SERVICES          PORT   TERMINATION   WILDCARD
cryostat-sample   cryostat-sample-jmx-sample3.apps.cluster-2ab2.2ab2.sandbox1193.opentlc.com          cryostat-sample   8181   reencrypt     None

また、OpenShiftのWebコンソールから確認するには、Installed OperatorsのCryostatからCryostatタブを選択しcryostat-sampleをクリックします。

f:id:chiroito:20210622155809p:plain
Cryostat一覧

右下のApplication URLにURLが記載され、リンクもされているのでクリックします。

f:id:chiroito:20210622155838p:plain
Cryostatリソースの詳細

このUIへアクセスすると以下のようにトークンを求められます。

f:id:chiroito:20210622155623p:plain
Cryostat UIへログイン

以下のコマンドを事項するとトークンが得られます。

oc whoami --show-token

出力されたトークンを入力してログインしましょう。

監視対象のアプリケーションをデプロイ

それでは、Cryostatで監視するJavaアプリケーションをデプロイしましょう。

CryostatではJava Management Extentions (JMX)を介してJFRを管理します。そのため、アプリケーションではJVM引数でJMXを有効化しなければなりません。JMXのポートはデフォルトで9091を監視します。他のポート番号にする場合はServiceのspec.ports.namejfr-jmxにして下さい。また、Java Discovery Protocol (JDP)でもJavaアプリケーションを検知できます。こちらについては難易度が高いのでServiceの名前にだけ注意するようにしましょう。

参考:GitHub - cryostatio/cryostat: A container-native JVM application which acts as a JMX bridge to other containerized JVMs and exposes a secure API for producing, analyzing, and retrieving JDK Flight Recorder data from your cloud workloads.

今回はサンプルイメージを用意しましたのでそのイメージを使用します。このサンプルでは以下のようにJMXを有効化しています。

-Dcom.sun.management.jmxremote.port=9091
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false

設定の簡素化のために認証は無効化していますが、CryostatはJMXの認証にも対応しているので必要な場合は設定して下さい。

認証の設定方法:https://github.com/cryostatio/cryostat#monitoring-applications

サンプルアプリをデプロイ

それではサンプルアプリケーションをデプロイします。

cat <<EOF > myapp.yaml
apiVersion: v1
kind: List
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    labels:
      app: jmx-container-sample
      app.kubernetes.io/component: jmx-container-sample
      app.kubernetes.io/instance: jmx-container-sample
    name: jmx-container-sample
  spec:
    replicas: 1
    selector:
      matchLabels:
        deployment: jmx-container-sample
    template:
      metadata:
        labels:
          deployment: jmx-container-sample
      spec:
        containers:
        - image: 'quay.io/cito/jmx-container-sample:1.0-SNAPSHOT'
          name: jmx-container-sample
          ports:
          - containerPort: 8080
            protocol: TCP
          - containerPort: 9091
            protocol: TCP
- apiVersion: v1
  kind: Service
  metadata:
    labels:
      app: jmx-container-sample
      app.kubernetes.io/component: jmx-container-sample
      app.kubernetes.io/instance: jmx-container-sample
    name: jmx-container-sample
  spec:
    ports:
    - name: 8080-tcp
      port: 8080
      protocol: TCP
      targetPort: 8080
    - name: jfr-jmx
      port: 9091
      protocol: TCP
      targetPort: 9091
    selector:
      deployment: jmx-container-sample
EOF
oc apply -f myapp.yaml

サンプルアプリケーションがデプロイされたことを確認します。

oc get deployments jmx-container-sample

以下のように出力されればデプロイされています。

NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
jmx-container-sample   1/1     1            1           20h

JFRの管理

サンプルアプリケーションを起動すると、Cryostatによって自動で検知されJFRが有効なJavaアプリケーションと言うことで管理の対象となります。

サンプルアプリケーションがCryostatで管理の対象となったことを確認します。

oc get flightrecorders

以下のようにサンプルアプリのpod名と同じものがあれば成功です。今回はjmx-container-sampleで始まるものがサンプルアプリケーションになります。

NAME                                    AGE
cryostat-sample-5b674ff96c-rshvn        4m10s
jmx-container-sample-8649b7699d-9v2fl   4m21s

CryostatではCryostat自身も管理されるため、先程デプロイしたcryostat-sampleのpodも含まれます。

また、この情報もWebコンソールから確認できます。Installed OperatorsCryostatからFlight Recorderタブを選択して下さい。

f:id:chiroito:20210622123407p:plain
FlightRecorderrを確認

JFRを起動

CryostatはRecordingというリソースとCryostat Web UIのどちらかでJFRを起動できます。 Recordingリソースでは記録したいJFRのイベントを詳細に指定しなければなりませんが、記録の情報はKubernetesのリソースとして管理されます。Cryostat Web UIではRecordingリソース同様にイベントを詳細に指定することもできますが、イベントのテンプレートも指定できます。しかし、こちらの方法ではRecordingリソースは生成されないためKubernetes上で記録を管理できません。

リソースとしての起動

全てのイベントを記録するのは非常に膨大になるので、試しに今回はサンプルアプリケーションに対し通信の入出力の情報を記録を試みます。対象となるJFRはspec.flightRecorder.nameに先程のoc get flightrecordersで出力されたものを指定します。記録するイベントはspec.eventOptionsで指定します。どの様なイベントがあるかはこちらを参照して下さい。

cat <<EOF > recording.yaml
apiVersion: operator.cryostat.io/v1beta1
kind: Recording
metadata:
  name: cont-recording
spec:
  name: cont-recording
  eventOptions:
  - "jdk.SocketRead:enabled=true"
  - "jdk.SocketWrite:enabled=true"
  duration: 0s
  archive: true
  flightRecorder:
    name: jmx-container-sample-8649b7699d-9v2fl
EOF
oc create -f recording.yaml

参照:cryostat-operator/api.md at main · cryostatio/cryostat-operator · GitHub

記録が開始できているかを確認します。

oc get recordings

出力結果は以下のようになります。

NAME             AGE
cont-recording   25h

Cryostat Web UIで起動

Cryostat Web UIでJFRを起動する場合には、Cryostat Web UIにログインしてから左側のメニューからRecordingsを選択します。右側にFlightRecorderとして管理されているリソースのリストがあるので、そこからJFRを起動したいJVMを指定します。サンプルJavaアプリをデプロイした場合はjmx-container-sampleから始まるJVMを指定して下さい。そうすると既に動いているJFRが表示されます。

f:id:chiroito:20210622155457p:plain
記録の一覧表示

この例では起動時に起動したのでそのJFRが記載されています。

Cryostat Web UIでJFRを起動

JFRを起動するにはCreateボタンを押して、作成画面へ行きます。

f:id:chiroito:20210622164434p:plain
JFRを起動

この画面では記録の名前や記録の期間、記録するイベントなどJFR自体の設定が行なえます。最低限はこの3つを設定すれば大丈夫です。Continuousのチェックボックスは継続して記録を取り、ファイルをダンプする時点から逆算して指定した期間分の情報が取れるようになっています。

オススメの設定は、名前は人間が分かりやすい名前が良いです。期間はContinuousを設定しつつ、障害を検知してからどれくらいの時間あればJFRファイルをダウンロードできるかを逆算しておき余裕を持って設定しておきましょう。イベントは通常はContinuousを選択し、具体的なトラブルがあったらProfilingやAlllを設定しましょう。

設定内容の詳細についてはJFRのドキュメントを参照下さい。

Grafanaへアクセスする

Cryostatの便利なポイントの1つは、JFRの内容をGrafanaで見られることです。JFRの内容をGrafanaで見るには、Cryostat Web UIのRecordingタブから見たいRecordingsの右側にあるメニューをクリックしてView in Grafana ...を選択します。そうすると、Grafanaのページへ遷移します。

f:id:chiroito:20210623110710p:plain
JFRをGrafanaで開く

Grafanaにアクセスすると認証情報の入力が求められます。この認証情報は、以下のコマンドを実行すると得られます。ユーザ名はadminですが、パスワードは環境によって異なります。

oc get secret cryostat-sample-grafana-basic -o json | jq -crM .data.GF_SECURITY_ADMIN_USER | base64 -d
oc get secret cryostat-sample-grafana-basic -o json | jq -crM .data.GF_SECURITY_ADMIN_PASSWORD | base64 -d

Grafanaへログインが成功すると、通常のGrafanaと同じように操作ができます。JFRのイベントはjfr-datasourceというデータソースに含まれています。好きなようにダッシュボードなどを作成してみて下さい。

f:id:chiroito:20210622105326p:plain
ダッシュボードの例

JFRをダウンロード

Grafanaは非常に便利ですが、JFRの情報を詳細に分析するには、現時点では専用のJDK Mission Controlの方が優れています。 Grafanaを使った分析で気になる点が見つけられたら、これまで通りJDK Mission Controlを使ってより詳細に分析しましょう。

Cryostatでは取得していたJFRのファイルをダウンロードできます。Cryostat Web UIのRecordingsでダウンロードしたいRecordingの右側をクリックするとメニューが出るので、Download Recordingをクリックしてダウンロードします。

f:id:chiroito:20210623110710p:plain
JFRファイルをダウンロード

ダウンロードしたファイルはJDK Mission Controlで開けますので、JMCを使用して詳細に分析ができるようになります。

f:id:chiroito:20210623111332p:plain
JDK Mission Control

最後に

これでコンテナ上において JDK Flight Recorder をより便利に使うためのツールである Cryostat の紹介は終わります。 CryostatとCryostat Operatorの開発はGithub上で行われているので、興味がある場合にはぜひスターを付けて下さい。

github.com

github.com

また、Cryostatの開発者が書いた記事の翻訳もありますので興味のある方はぜひご覧下さい。

rheb.hatenablog.com