JavaアプリケーションのJMX監視を有効にしたらデプロイが稀にコケるようになった


事象

Datadog にJVM のメトリクスをレポートするためにアプリケーション起動時に JMX 監視を有効にするようにしたら3回に1回くらいの確率でデプロイが失敗するようになった。

デプロイガチャ😂

状況整理

エラーメッセージ

Error: JMX connector server communication error: service:jmx:rmi://ip-xxx-xxx-xxx-xxx:7199

7199 はJMX用に割り当てたポート。

JMXの設定

デプロイはシェルスクリプトで Java プロセスを kill してから再起動している。
JMXの設定は再起動時に Java オプションから。

deploy.sh
#!/bin/bash

set -ue

service_restart() {
  ssh "$DEPLOY_HOST" 'pkill java; test $? -le 1'
  echo "java process stopped"
  START_JAVA_COMMAND=$(cat << EOS
    nohup /path/to/app_start_script \
      -Dconfig.file=/path/to/stage/conf/application.conf \
      -Dcom.sun.management.jmxremote \
      -Dcom.sun.management.jmxremote.port=7199 \
      -Dcom.sun.management.jmxremote.rmi.port=7199 \
      -Dcom.sun.management.jmxremote.authenticate=false \
      -Dcom.sun.management.jmxremote.ssl=false \
      > /var/log/nohup.out \
      2>&1 &
EOS
)
  ssh "$DEPLOY_HOST" "$START_JAVA_COMMAND"
  echo "java process started"
}

service_restart

app_start_script は単に Java オプションを受け取ってアプリケーションを起動するスクリプト。

調査

調べてみたところ jmxremote が有効になっているとアプリケーションの再起動に失敗するという事例は結構あるみたいだった。

原因

どうやら Java プロセスを kill した後もしばらくの間、JMXに指定ポートを掴まれてしまっていることが原因みたいだった。

具体的には、再起動の時点でJMXに割り当てたいポートが LISTEN もしくは TIME_WAIT の状態なってしまっていた。

対策

ということなので netstat で JMXのプロセスを監視して解放されたことを確認してから Java アプリケーションを再起動させるようにシェルを改修した。

#!/bin/bash

set -ue

# JMXのポートが空くまで待機する関数
wait_until_port_opened() {
  while :
  do
    if ! ssh "$DEPLOY_HOST" "netstat -an | grep 7199" > /dev/null
    then
      break
    fi
    sleep 1
  done
}

service_restart() {
  ssh "$DEPLOY_HOST" 'pkill java; test $? -le 1'
  # JMXのポートが空くまで待機
  wait_until_port_opened
  echo "java process stopped"
  START_JAVA_COMMAND=$(cat << EOS
    nohup /path/to/app_start_script \
      -Dconfig.file=/path/to/stage/conf/application.conf \
      -Dcom.sun.management.jmxremote \
      -Dcom.sun.management.jmxremote.port=7199 \
      -Dcom.sun.management.jmxremote.rmi.port=7199 \
      -Dcom.sun.management.jmxremote.authenticate=false \
      -Dcom.sun.management.jmxremote.ssl=false \
      > /var/log/nohup.out \
      2>&1 &
EOS
)
  ssh "$DEPLOY_HOST" "$START_JAVA_COMMAND"
  echo "java process started"
}

service_restart

他の解決策

いろいろ調べてる時に教えてもらったことだけれども、ライブラリを利用してJMXの設定にポートを利用しないというアプローチもあるようだった。

こちらはまたの機会に。

おしまい