Kubernetes で VisualVM（JMX接続）

# はじめに
今回は k8s で動いている Java アプリに VisualVM で接続しようと思います。
Java アプリケーションの監視や解析の際に利用するかと思います。
コンテナなので Prometheus でのメトリクス収集が一般的だと思いますが、今回は Java 開発者に使い慣れたツールということで Java 側で用意されているツールを利用します。

※今回は Java アプリケーションを用意せず Jenkins のコンテナに対して実施しました
※JMX や Jenkins は Prometheus でメトリクス収集も可能です

# 環境情報
Kubernetes：1.18
helm：v3.2
※https://helm.sh/docs/intro/quickstart/
Java：1.8
Jenkins：2.235

# Jenkins コンテナ準備
helm で Jenkins コンテナをデプロイします。

まずはデプロイ用にパラメータファイルを作成します。
※Pod への接続は NodePort を利用します
※jmx で接続するポートを今回は 32000 で指定しています
※hostname で指定する IP アドレスは k8s の worker ノードの IP です。以下のコマンドで確認できます。

```console:workerノードのIPアドレス
$ kubectl get nodes --namespace jenkins -o jsonpath="{.items[0].status.addresses[0].address}"
```

```yaml:values.yaml
master:
  serviceType: NodePort
  javaOpts: >
    -Djava.rmi.server.hostname=0.0.0.0
    -Dcom.sun.management.jmxremote=true
    -Dcom.sun.management.jmxremote.port=32000
    -Dcom.Sun.management.jmxremote.local.only=false
    -Dcom.sun.management.jmxremote.rmi.port=32000
    -Dcom.sun.management.jmxremote.ssl=false
    -Dcom.sun.management.jmxremote.authenticate=false
    -Djava.rmi.server.hostname=192.168.10.51
  jmxPort: 32000
```

また、JMX アクセス用として Service を定義する yaml ファイルを作成します。

```yaml:svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: jenkins-jmx
  namespace: jenkins
spec:
  ports:
  - name: jmx
    nodePort: 32000
    port: 32000
    protocol: TCP
    targetPort: 32000
  selector:
    app.kubernetes.io/component: jenkins-master
    app.kubernetes.io/instance: jenkins
  type: NodePort
```

早速デプロイします。
※```helm pull stable/jenkins``` で helm で利用するファイル一式がダウンロードできます



```console:deploy_jenkins
# jenkins namespace 作成
$ kc create ns jenkins

# k8s 接続設定(context)確認
$ kc config current-context
sandbox

# k8s 接続設定(context)の namespace 設定
$ kc config set-context sandbox --namespace jenkins

# k8s 接続先 namespace 確認
$ kc config get-contexts
CURRENT   NAME                CLUSTER             AUTHINFO   NAMESPACE
*         sandbox             sandbox             sandbox    jenkins

# helm repository 追加・レポジトリ情報アップデート
$ helm repo add stable https://kubernetes-charts.storage.googleapis.com
"stable" has been added to your repositories

$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "stable" chart repository
Update Complete. ? Happy Helming!?

# Jenkins コンテナデプロイ
# helm install <リリース名> <Chart 名> -f <parameter file> --namespace <デプロイ先 namespace>
# リリース名：デプロイに対する helm での管理上の名前
# Chart 名：利用するテンプレート名的なもの
$ helm install jenkins stable/jenkins -f values.yaml --namespace jenkins
NAME: jenkins
LAST DEPLOYED: Sat Jul 25 hh:mm:ss 2020
NAMESPACE: jenkins
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:
  printf $(kubectl get secret --namespace jenkins jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
2. Get the Jenkins URL to visit by running these commands in the same shell:
  export NODE_PORT=$(kubectl get --namespace jenkins -o jsonpath="{.spec.ports[0].nodePort}" services jenkins)
  export NODE_IP=$(kubectl get nodes --namespace jenkins -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT/login

3. Login with the password from step 1 and the username: admin
・・・

# デプロイの確認
$ helm list
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART         APP VERSION
jenkins jenkins         1               2020-MM-DD hh:mm:ss.950509962 +0900 JST deployed        jenkins-2.4.1 lts
```

helm で作成したコンテナには JMX 接続する Service の定義がないため、Service を別途デプロイします。

```bash:deploy_service
$ kubectl get svc
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
jenkins         NodePort    10.43.173.89   <none>        8080:31036/TCP   12m
jenkins-agent   ClusterIP   10.43.130.60   <none>        50000/TCP        12m

$ kubectl apply -f svc.yaml
service/jenkins-jmx created

# kubectl get svc
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)           AGE
jenkins         NodePort    10.43.173.89   <none>        8080:31036/TCP    12m
jenkins-agent   ClusterIP   10.43.130.60   <none>        50000/TCP         12m
jenkins-jmx     NodePort    10.43.97.82    <none>        32000:32000/TCP   19s
```

# Java VisualVMで接続
さて、起動した Jenkins コンテナに JMX 接続します。
Java VisualVM を起動して、Jenkinsコンテナに接続しましょう。
JDK などにも付属されていますが、今回は pleiades(all in one eclipse) に付属している 1.8 の Java VisualVM で接続します。
https://mergedoc.osdn.jp/

※pleiades 解凍後 ```java\8\bin\jvisualvm.exe``` で起動できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317159/bac0993d-c99d-06b1-3143-cb74a1f12736.png)

1. 左メニューの「リモート」を右クリック⇒「リモートホストの追加」をクリック
2. 「ホスト名」にjavaOptsで指定したホスト名（IP アドレス）を入力して「了解」をクリック
3. 左メニューにリモートマシンが追加されていることを確認
4. 左メニューの登録したマシンを右クリック⇒「JMX 接続を追加」をクリック
5. 「接続」にホスト名が入力されているので、：（コロン）の後ろに続けてポート番号(今回の場合は 32000)を入力
6. 「SSL 接続を使用しない」にチェックを入れて、「了解」をクリック
7. 左メニューの登録したリモートマシン配下に接続が追加されていることを確認
8. 追加された接続をダブルクリックしてメイン画面に情報が表示されることを確認

**接続後の画面**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317159/c5b0c658-18f4-a8c7-7b7d-ac847af9481f.png)

**監視タブ**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317159/79de9b04-d3b4-f03b-9394-d743a568bd69.png)

**スレッドタブ**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317159/163d9046-20e4-1cc2-c669-558db1f50dae.png)

おまけに jconsole で接続してみるとこんな感じです。
※jconsole も VisualVM と同じフォルダにバイナリファイルがあります
※接続先は VisualVM で設定した際の接続先と一緒です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317159/c4ef240b-6efb-3d97-2780-e8a55608e141.png)

# まとめ
k8s 上で動作しているコンテナへの VisualVM や jconsole で接続する方法を今回は試してみました。
よくあるポートを開けて接続する方法ではできなかったので以下を追加して設定しています。

- java の起動オプション(javaOpts)に hostname を追加し、Worker ノードの IP アドレスを設定
- jmx で接続するポートと Nodeport で開放するポートを同じ値にする

Prometheus ではなく使い慣れたツールでの接続を行う際の参考にしてください。