docker
Mackerel
ECS

mackerel-agentのDockerイメージでECS上で動作するjavaアプリケーションのjvmを監視する

More than 1 year has passed since last update.

概要

これまでリモート経由でのメトリクスに対応していなかったmackerel-plugin-jvmがリモート経由でも対応されたと教えていただいたのでやってみました。
これが可能になれば、軽量さも重視されるコンテナにおいてmackerel-agentを入れることを回避できるようになりますね。

ECS上に構築したjavaアプリケーションと同じサービス内で起動した docker mackerel-agent から mackerel-plugin-jvmを使用してjavaアプリケーションのjvm関連のメトリクスの作成までを行います。

前提

以下のものは本記事では割愛します。

  • ECSの使い方
  • dockerの使い方
  • docker-composeの使い方
  • ecs-cliの使い方
  • mackerelエージェントのインストール
  • javaアプリケーションの詳細な中身

docker-composeの定義ファイルを作成する

jvm関連の値を取得するために必要な設定

javaアプリケーションを実行しているサーバー上にmackerel-agentをインストールする場合には意識することはなかったのですが、
他コンテナから取得させるには以下のものが必要となります。

  • appコンテナ
    • 1099ポート(デフォルト)のEXPOSE
    • rmiregistry の起動
    • jstatd の起動
  • mackerel-agentコンテナ
    • jdkのインストール(jps、jstat、jinfo)
    • plugin-jvmの設定
  • コンテナ間のlink

appコンテナ

1099ポートのEXPOSE

これはjava-appで定義しているDockerfileのEXPOSEに1099を追加するだけです。

Dockerfile
FROM java:8-alpine
 :
 :
EXPOSE 9000 1099

※ アプリケーションの使用ポートを9000としています。

rmiregistry の起動

こちらはECS起動時に必要な内容となります。
以下のような内容をdocker-composeファイルのcommandブロックで定義してあげれば起動可能です。

eval 'rmiregistry -J-Djava.rmi.server.codebase=file://$${JAVA_HOME}/lib/tools.jar &'

jstatd の起動

まずはポリシーファイルを作成します。
このあたりは各社のポリシーに従って調整してください。

tools.policy
grant {
    permission java.security.AllPermission;
};

※ こちらのpolicyファイルはDockerfile内でCOPYするか、docker-compose.ymlの command ブロック内で出力させてあげてください。

jstatd の起動も dokcer-composeファイルの command ブロックに定義してやります。

eval 'jstatd -J-Djava.security.policy=/path/to/tools.policy -J-Djava.rmi.server.hostname=$${HOSTNAME} &'

1コンテナ1プロセスの考え方はそのうち…

mackerel-agentコンテナ

jdkのインストール

Dockerfileを作って管理するのが嫌だったので、こちらも docker-composeファイルの command ブロックに記述してやります。

yum install -y java-1.8.0-openjdk-devel

plugin-jvmの設定

今回はDockerfileは作成しないので、 command ブロックに以下のように追記します。

mkdir /etc/mackerel-agent/conf.d \
&& echo \"[plugin.metrics.jvm]\" > /etc/mackerel-agent/conf.d/jvm.conf \
&& echo 'command = \"mackerel-plugin-jvm -javaname=NettyServer -host=java-app\"' >>  /etc/mackerel-agent/conf.d/jvm.conf \
&& echo 'display_name = \"java-app\"' >> /etc/mackerel-agent/mackerel-agent.conf

最終行でMackerelの管理画面上で見分けの付けやすいように名称を付与しています。

コンテナ間link

mackerel-agentのコンテナから名前解決でjava-appを探すために必要になります。
docker-composeファイルの links ブロックに追記してやるだけです。

links:
  - java-app

docker-composeの定義を書く

これらを踏まえてdocker-composeファイルを書いていきます。

docker-compose.yml
version: '2'
services:
  java-app:
    mem_limit: 268435456
    image: java-app:latest
    user: quartette
    environment:
      - APP_ENV=dev
      - JAVA_OPTS=-Xms128m -Xmx256m -Xmn64m -XX:MetaspaceSize=32m -XX:MaxMetaspaceSize=32m -Dfile.encoding=UTF-8 -Duser.country=JP -Duser.language=ja -Duser.timezone=Asia/Tokyo -Djava.awt.headless=true -XX:-OmitStackTraceInFastThrow
    command:
      /bin/sh -c
        "eval 'rmiregistry -J-Djava.rmi.server.codebase=file://$${JAVA_HOME}/lib/tools.jar &' \
        && eval 'jstatd -J-Djava.security.policy=/path/to/tools.policy -J-Djava.rmi.server.hostname=$${HOSTNAME} &' \
        && /path/to/app.jar"
    labels:
      - Env=dev
    logging:
      driver: awslogs
      options:
        awslogs-group: "java-app"
        awslogs-region: "ap-northeast-1"
        awslogs-stream-prefix: "quartette"
  mackerel-agent:
    image: mackerel/mackerel-agent:latest
    mem_limit: 134217728
    command:
      /bin/sh -c
        "mkdir /etc/mackerel-agent/conf.d \
        && yum install -y java-1.8.0-openjdk-devel \
        && echo \"[plugin.metrics.jvm]\" > /etc/mackerel-agent/conf.d/jvm.conf \
        && echo 'command = \"mackerel-plugin-jvm -javaname=NettyServer -host=java-app\"' >>  /etc/mackerel-agent/conf.d/jvm.conf \
        && echo 'display_name = \"java-app\"' >> /etc/mackerel-agent/mackerel-agent.conf \
        && /startup.sh
        "
    environment:
      - apikey=[mackerel api key]
      - auto_retirement=true
      - enable_docker_plugin=true
      - opts=-v
      - include=/etc/mackerel-agent/conf.d/*.conf
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/mackerel-agent/:/var/lib/mackerel-agent/
    links:
      - java-app
    logging:
      driver: awslogs
      options:
        awslogs-group: "mackerel-container-agent"
        awslogs-region: "ap-northeast-1"
        awslogs-stream-prefix: "quartette"

デプロイをして確認

ECSのサービスを更新する

私の環境では、 ecs-cli を使ってデプロイをしているので以下のようにします。

ecs-cli compose -p java-app -f docker-compose.yml service up --container-name java-app --container-port 80 --roke ecsServiceRole --target-group-arn [targetGroupArn] --deployment-min-healthy-percent 50

Mackerel上で確認

上手く値が取得できていればこんな感じのグラフができているはずです。

スクリーンショット 2017-12-01 15.24.47.png

グラフが表示されない場合

多くの場合、mackerel-agentからアプリケーションとの通信に失敗しているだけなのでコンテナにログインして確認をしましょう

mackerel-agent側からの確認

  • plugin-jvm内で実行されているコマンドを直接実行する mackerel-jvm-pluginではjps、jstat、jinfoあたりのコマンドが実行されています。 jpsとjstatの値が取れていればひとまずは問題ないと思います。
# jps java-app
5 RegistryImpl
6 Jstatd
7 NettyServer

# jstat -gc 7@java-app
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
30720.0 29184.0 1880.2  0.0   71168.0  44096.2   393216.0   283934.1  60032.0 56869.0 8320.0 7870.9    150    2.792   3      3.371    6.163

正常に設定できていればこんなような値が取得できるはずです。

  • mackerel-jvm-pluginを直接実行する
    jpsとjstatが取得できれば問題なくメトリクスが作成されるはずですが、plugin内でエラーになっている可能性もあるのでjvm-plugin も実行してみます。
    正常に取得できるようであれば以下のような値が取得できます。
# mackerel-plugin-jvm -javaname=NettyServer -host=java-app
jvm.nettyserver.gc_events.YGC   0.000000    1512110013
jvm.nettyserver.gc_events.FGC   0.000000    1512110013
jvm.nettyserver.gc_time.YGCT    0.000000    1512110013
jvm.nettyserver.gc_time.FGCT    0.000000    1512110013
jvm.nettyserver.memorySpace.oldSpaceRate    72.208176   1512110013
jvm.nettyserver.memorySpace.newSpaceRate    11.839294   1512110013
jvm.nettyserver.gc_time_percentage.YGCT 0.000000    1512110013
jvm.nettyserver.gc_time_percentage.FGCT 0.000000    1512110013
jvm.nettyserver.new_space.NGCMX 134217728.000000    1512110013
jvm.nettyserver.new_space.NGC   134217728.000000    1512110013
jvm.nettyserver.new_space.EU    13965107.200000 1512110013
jvm.nettyserver.new_space.S0U   1925324.800000  1512110013
jvm.nettyserver.new_space.S1U   0.000000    1512110013
jvm.nettyserver.old_space.OGCMX 939524096.000000    1512110013
jvm.nettyserver.old_space.OGC   402653184.000000    1512110013
jvm.nettyserver.old_space.OU    290748518.400000    1512110013
jvm.nettyserver.metaspace.MCMX  1128267776.000000   1512110013
jvm.nettyserver.metaspace.MCMN  0.000000    1512110013
jvm.nettyserver.metaspace.MC    61472768.000000 1512110013
jvm.nettyserver.metaspace.MU    58233856.000000 1512110013
jvm.nettyserver.metaspace.CCSC  8519680.000000  1512110013
jvm.nettyserver.metaspace.CCSU  8059801.600000  1512110013

java-app側からの確認

  • プロセスの確認 rmiregistryやjstadが起動しているかを確認します。
# ps
PID   USER     TIME   COMMAND
    1 quartette   0:00 /bin/sh -c eval 'rmiregistry -J-Djava.rmi.server.codebase=file://${JAVA_HOME}/lib/tools.jar &'  && eval 'jstatd -J-Djava.security.policy
    5 quartette   0:45 rmiregistry -J-Djava.rmi.server.codebase=file:///usr/lib/jvm/java-1.8-openjdk/lib/tools.jar
    6 quartette   2:00 jstatd -J-Djava.security.policy=/home/tlab-app/tools.policy -J-Djava.rmi.server.hostname=cefd4fb34c63
    7 quartette   7:06 /usr/lib/jvm/java-1.8-openjdk/bin/java -Xms128m -Xmx256m -Xmn32m -XX:MetaspaceSize=32m -XX:MaxMetaspaceSize=32m -Dfile.encoding=UTF-8
 2461 quartette   0:00 /bin/bash
 2466 quartette   0:00 ps

いくつか表示されていますが、PID 5、6の rmiregistryjstatd が起動していれば問題ありません。
java-app側が問題ないはずなのにmackerel-agent側から値が取れない場合、EXPOSEでポートの指定が上手くいっていないか、コンテナ間リンクが上手くいっていない場合が多いです。

最後に

最近は運用をコンテナで行うことも増えてきて、コンテナ関連監視に悩んでいました。
コンテナ内にエージェントをインストールすることなく監視をしたい場合などにmackerelのコンテナベースのagentは便利だと思います。
DataDogやNewRelicなどでも同様のことはできるとは思いますが、Mackerelであれば日本語でのサポートも行ってくれるのでいいですね。