Technology Topics by Brains

ブレインズテクノロジーの研究開発機関「未来工場」で働くエンジニアが、先端オープン技術、機械学習×データ分析(異常検知、予兆検知)に関する取組みをご紹介します。

Dockerコンテナで起動するElasticsearchの性能情報を取得した話

ブレインズテクノロジーの加藤です。

今回は、Dockerコンテナ上で起動するjavaアプリケーションの性能情報の取得方法についてまとめます。
例としてElasticsearchを使用しましたが、javaで起動しているアプリケーションであれば何にでも応用が効くはずです。

経緯

あるお客様の環境で、Dockerコンテナ上にElasticsearchを起動してサービスを提供しているのですが、GC系のERRORが頻発するようになりました。

原因の特定と解決策の検討のため、jstatによる性能情報を取得しようとしたのですが、ホストから見たElasticsearchのプロセスIDが異なるからか、ホスト側からは取得ができず。
仕方なくDockerコンテナに入って取得しようとしたのですが、今度はDockerコンテナにjdkが入っていないため、インストールするところから始めました。

以下はその手順です。

Dockerコンテナにログインする

Elasticsearchが起動していること、およびプロセス名を確認します。
今回のプロセス名は"brave_jepsen"のようです。

$ docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED                  STATUS              PORTS                NAMES
3596bfd346d4        elasticsearch:latest   "/docker-entrypoint.…"   Less than a second ago   Up 2 seconds        9200/tcp, 9300/tcp   brave_jepsen

docker exec コマンドにより、elasticsearchのコンテナにログインします。

$ docker exec -it brave_jepsen bash
root@3596bfd346d4:/usr/share/elasticsearch#

ログインできました。

Dockerコンテナにjdkをインストールする

試しにDockerコンテナの中で、jstatコマンドを実行してみます。

$ jstat
bash: jstat: command not found

入ってないですね。仕方ないのでインストールします。
インストールにはapt-getコマンドを使用します。

まずはapt-getを最新化しましょう。

$ apt-get update
Ign:1 http://deb.debian.org/debian stretch InRelease
Get:2 http://deb.debian.org/debian stretch-updates InRelease [91.0 kB]
・・・(略)
Fetched 10.3 MB in 3s (3173 kB/s)
Reading package lists... Done

ちなみに環境によっては、apt-getを実行しても失敗することがあります。
その際は、環境変数「http_proxy」にプロキシサーバを設定すれば通るかもしれません。

export http_proxy=${プロキシサーバのホスト}:${プロキシサーバのポート}

続いて、apt-fileをインストールします。

$ apt-get install -y apt-file
Reading package lists... Done
Building dependency tree
Reading state information... Done
・・・(略)
Setting up apt-file (3.1.4) ...
The system-wide cache is empty. You may want to run 'apt-file update'
as root to update the cache.

apt-fileをupdateしろと言っているので、仰せのままに実行します。

$ apt-file update
Ign:1 http://deb.debian.org/debian stretch InRelease
Get:2 http://security.debian.org stretch/updates InRelease [63.0 kB]
・・・(略)
Building dependency tree
Reading state information... Done
3 packages can be upgraded. Run 'apt list --upgradable' to see them.

apt-fileのsearchコマンドにより、jstatが含まれるパッケージを見つけます。

$ apt-file search jstat
libmpj-java: /usr/bin/mpjstatus
openjdk-8-jdk-headless: /usr/lib/jvm/java-8-openjdk-amd64/bin/jstat
openjdk-8-jdk-headless: /usr/lib/jvm/java-8-openjdk-amd64/bin/jstatd
openjdk-8-jdk-headless: /usr/lib/jvm/java-8-openjdk-amd64/man/ja_JP.UTF-8/man1/jstat.1.gz
openjdk-8-jdk-headless: /usr/lib/jvm/java-8-openjdk-amd64/man/ja_JP.UTF-8/man1/jstatd.1.gz
openjdk-8-jdk-headless: /usr/lib/jvm/java-8-openjdk-amd64/man/man1/jstat.1.gz
openjdk-8-jdk-headless: /usr/lib/jvm/java-8-openjdk-amd64/man/man1/jstatd.1.gz

「openjdk-8-jdk-headless」に含まれるようです。
なので対象のパッケージをインストールします。

$ apt-get install -y openjdk-8-jdk-head
Reading package lists... Done
Building dependency tree
・・・(略)
update-alternatives: using /usr/lib/jvm/java-8-openjdk-amd64/bin/wsgen to provide /usr/bin/wsgen (wsgen) in auto mode
update-alternatives: using /usr/lib/jvm/java-8-openjdk-amd64/bin/jcmd to provide /usr/bin/jcmd (jcmd) in auto mode

jpsコマンドにより、Elasticsearchのプロセスが表示されれば成功です。
elasticsearchの場合、プロセスIDは1になるようです。

$ jps -v
1 Elasticsearch -Xms2g -Xmx2g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+AlwaysPreTouch -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -Djdk.io.permissionsUseCanonicalPath=true -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Dlog4j.skipJansi=true -XX:+HeapDumpOnOutOfMemoryError -Des.path.home=/usr/share/elasticsearch
802 Jps -Dapplication.home=/usr/lib/jvm/java-8-openjdk-amd64 -Xms8m

Javaの性能情報取得

jstatコマンドにより性能情報を取得します。
使用可能なオプションの種類や、出力項目の詳細は、下記のオラクルのページに載っています。

docs.oracle.com

今回は gccauseオプションを使用したいと思います。
コマンドの意味としては、"-h1000"で1000行ごとにヘッダーを表示。
途中の1でelasticsearchのプロセスIDを指定。
1000は、1秒ごとに1000回繰り返すことでその間の性能情報を取得することができます。

$ jstat -gccause -h1000 1 1000 > /usr/share/elasticsearch/logs/gccause.txt
$ cat /usr/share/elasticsearch/logs/gccause.txt
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC
100.00   0.00  98.21   0.53  93.29  83.32      2    0.115     2    0.037    0.152 CMS Final Remark     No GC
100.00   0.00  98.21   0.53  93.29  83.32      2    0.115     2    0.037    0.152 CMS Final Remark     No GC
100.00   0.00  98.21   0.53  93.29  83.32      2    0.115     2    0.037    0.152 CMS Final Remark     No GC
100.00   0.00  98.21   0.53  93.29  83.32      2    0.115     2    0.037    0.152 CMS Final Remark     No GC
100.00   0.00  98.21   0.53  93.29  83.32      2    0.115     2    0.037    0.152 CMS Final Remark     No GC
100.00   0.00  98.21   0.53  93.29  83.32      2    0.115     2    0.037    0.152 CMS Final Remark     No GC
100.00   0.00  98.21   0.53  93.29  83.32      2    0.115     2    0.037    0.152 CMS Final Remark     No GC

GCログはDockerコンテナ内に出力されるので、volume mappingでホスト側と共有しておくと良いです。

ちなみにお客様の環境ではdocker-composeを使っています。
docker-compose.ymlの設定は以下のような感じです。

(抜粋)
  volumes:
        - /home/logs/elasticsearch:/usr/share/elasticsearch/logs

この設定をしておけば、ホスト側の「/home/logs/elasticsearch」ディレクトリにgccause.txtが出力されているはずです。
gccauseだと実行時刻が出力されないため、ホスト側で下記のようなコマンドを実行するといい感じです。

$ tail -f /home/logs/elasticsearch/gccause.txt | awk '{print strftime("%Y/%m/%d %H:%M:%S"), $0; fflush()}' > /home/logs/elasticsearch/gccause_new.txt

(出力結果)
2018/02/23 15:54:50   S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC                 
2018/02/23 15:54:50  99.35   0.00  81.41   5.55  98.06  94.74     40    2.183     2    0.108    2.292 Allocation Failure   No GC 
2018/02/23 15:54:50  99.35   0.00  81.43   5.55  98.06  94.74     40    2.183     2    0.108    2.292 Allocation Failure   No GC
2018/02/23 15:54:50  99.35   0.00  81.45   5.55  98.06  94.74     40    2.183     2    0.108    2.292 Allocation Failure   No GC
2018/02/23 15:54:50  99.35   0.00  81.46   5.55  98.06  94.74     40    2.183     2    0.108    2.292 Allocation Failure   No GC
2018/02/23 15:54:50  99.35   0.00  81.48   5.55  98.06  94.74     40    2.183     2    0.108    2.292 Allocation Failure   No GC

終わりに

というわけで、無事にDockerコンテナで起動するElasticsearchの性能情報を取得できました。
冒頭でも書きましたが、javaアプリケーションであれば、Elasticsearch以外でもこの方法で取得できるはずです。

この記事が誰かの役に立てればいいと思うー。そだねー。