ぽよメモ

レガシーシステム考古学専攻

Dockerで走らせたMinecraftのログをFluentdでSlackに飛ばす

何かと話題のDockerを最近よく聞くので使ってみたくなり,どうせなら面白いことがしたいなという理由だけでなんとなくMinecratのサーバーを立ててみることにしました.
ただそのまま普通に建てるだけでは何も面白くないので,ログをSlackに飛ばして監視しようかなと.

本番いきなりやるのは厳しいので仮想環境のUbuntuGnome 16.04で実験しています.

dockerのインストール

$ sudo apt-get update
$ sudo apt-get install apt-transport-https ca-certificates
$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
$ vim /etc/apt/sources.list.d/docker.list
deb https://apt.dockerproject.org/repo ubuntu-xenial main

xenial以外の場合はhttp://docs.docker.jp/engine/installation/linux/ubuntulinux.htmlを参照してください.

$ sudo apt-get update
$ sudo apt-get install linux-image-extra-$(uname -r)
$ sudo apt-get install docker-engine
$ sudo usermod -aG docker [ユーザ名]

これで準備完了です.一度再起動してhello-worldします.

$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
 https://hub.docker.com

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

成功です.一度よく分からないエラーが出たときには,最初から入れ直したら動きました.

構成

今回は

  1. Minecraftサーバのコンテナ
  2. ログを処理するFluentdのコンテナ
  3. Minecraftのデータコンテナ

の3つで構成したいと思います.
Dockerのlog driverとしてfluentdを指定できるので比較的簡単にできました.

また,全てdocker-composeでまとめてしまいます.これめっちゃ便利です.

$ sudo apt-get install docker-compose

それに伴い,今回は以下のようなディレクトリ構造としました.

~/minecraft_server
├── docker-compose.yml
├── fluentd
│   ├── Dockerfile
│   ├── fluent.conf
│   └── plugins
└── minecraft
    └── Dockerfile

Minecraftコンテナ

今回は既存のコンテナを流用します.バニラ環境だけでなく,Spigotやforgeも使える用なのでMod環境を多く構築している人にも良いのでは?

github.com

$ mkdir minecraft_server
$ cd minecraft_server
$ mkdir minecraft
$ mkdir fluentd
$ vim docker-compose.yml
minecraft:
  image: iztg/minecraft
  container_name: mc_server
  ports:
    - "25565:25565"
  volumes_from:
    - data-minecraft
  environment:
    EULA: "TRUE"
    VERSION: "1.10"

data-minecraft:
  image: busybox
  container_name: data-minecraft
  volumes:
    - /data

このminecraftイメージでは,与える環境変数(environment)に応じたバージョンのMinecraftをダウンロードし,自動で動かしてくれます.
設定可能な項目はgithubのREADMEに書いてあります.

このまま

$ docker-compose up

して正常に立ち上がったら成功です.
上で書いたディレクトリ構成にはminecraftディレクトリとDockerfileが記載されていますが別に要りません(単体でテストしたときの名残でおいてあるだけです

Fluentdコンテナ

Fluentdはログを良い感じにしてくれる(ざっくり)ツールで,今回はSlackに投げますが他にも普通に標準出力したり,他のFluentdコンテナにパイプしたり,ファイルに出力したりといろいろなことができるすごいやつです.Elasticsearchとかとよく組み合わされているのをネットで見ます.

qiita.com

具体的な飛ばし方は上記のページに書いています.
では作っていきます.Slackのwebhook urlは用意できたものとして進めます.

$ mkdir fluentd
$ cd fluentd
$ vim Dockerfile
FROM fluent/fluentd:latest-onbuild

USER fluent

WORKDIR /home/fluent
RUN gem install fluent-plugin-secure-forward 
RUN gem install fluent-plugin-slack
RUN gem install fluent-plugin-rewrite-tag-filter
RUN gem install fluent-plugin-rewrite

EXPOSE 24224

CMD fluentd -c /fluentd/etc/fluent.conf -p /fluentd/plugins -vv

gem installでいくつかプラグインをインストールしています.このうちfluent-plugin-slackによってSlackへ投稿します.
Minecraftコンテナからfluentdコンテナへログを流す設定をします.ルートディレクトリ直下のdocker-compose.ymlに追記します.

$ vim docker-compose.yml
minecraft:
  # ...省略...
  links:
    - fluentd
  log_driver: fluentd
  log_opt:
    fluentd-address: localhost:24224
    fluentd-tag: minecraft.raw
fluentd:
  container_name: fluentd
  build: fluentd/
  ports:
    - "24224:24224"
  volumes:
    - ./fluentd/:/fluentd/etc/
    - ./fluentd/plugins:/fluentd/plugins
  environment:
    FLUENTD_CONF: fluent.conf

公式のイメージではfluentdディレクトリの直下にpluginsディレクトリとfluent.confを配置する必要があります.
fluent.confという設定ファイルで出力するログの設定を色々できるのですが,正直あまりまだわかっていないので効率的でない設定を書いていると思います.もっと良い方法がありましたら教えていただきたいです.

$ cd fluentd
$ vim fluent.conf
<source>
  type forward
  port 24224
</source>

<match minecraft.raw>
  type stdout 
</match>

この状態でルートディレクトリ直下に移動し,docker-compose upするとfluentdコンテナの標準出力からログが出力されているのが分かると思います.

$ docker-compose up
# ...省略...
mc_server| [11:15:35] [Server thread/INFO]: Starting GS4 status listener
fluentd     | 2016-09-07 11:15:35 +0000 minecraft.raw: {
  "container_id":"4243fdf6b63b393d4e4a2de749f359743d6a5920b9f26e22e7b36cbe03103412",
  "container_name":"/mc_server",
  "source":"stdout",
  "log":"[11:15:35] [Server thread/INFO]: Starting GS4 status listener"
}

json形式のログストリームが流れてくるので,これを色々してSlackに投げます.

Slackに投げる

fluent-plugin-slack

Slackはあらかじめ用意しておいてください.まずはとりあえず適当に#consoleチャンネルにログを流してみます.

$ cd fluentd
$ vim fluent.conf
<source>
  type forward
  port 24224
</source>

<match minecraft.raw>
  type slack
  webhook_url https://hooks.slack.com/services/xxxx/xxxxxxx
  channel console
  username Minecraft Server
  message_keys log
  flush_interval 1s
</match>

message_keysがSlackに投稿されるテキストデータになります.また,flush_intervalは反映するまでのインターバルです.1s以内に出たログメッセージはまとめて投稿されます.
これでdocker-compose upすれば以下のようになると思います.

f:id:pudding_info:20160907202534p:plain

fluent-plugin-rewrite-tag-filter

このままではINFOログに他のものが飲み込まれてしまって,必要な情報が埋もれてしまうのでpluginを活用して良い感じにしていきます.
まず,WARNING以上のエラーはwarningチャンネルに流し込んでいきます.

<source>
  type forward
  port 24224
</source>

<match minecraft.raw>
  type rewrite_tag_filter
  rewriterule1 log WARN|ERROR minecraft.raw.error
  rewriterule2 log INFO minecraft.raw.info
</match>

<match minecraft.raw.info>
  type slack
  webhook_url https://hooks.slack.com/services/xxxx/xxxxxxx
  channel console
  username Minecraft Server
  message_keys log
  flush_interval 1s
</match>

<match minecraft.raw.error>
  type slack
  webhook_url https://hooks.slack.com/services/yyyy/yyyyyyy
  channel warning
  username Minecraft Server
  message_keys log
  color danger
  flush_interval 1s
</match>

warningチャンネルを新しく作り,またwebhook_urlを取得する必要があります.

fluent-plugin-rewrite-tag-filterは,ログのメッセージを正規表現で検索し,マッチした場合tagを書き換えることができるプラグインです.
意図的にエラーを起こす方法が特に思いつかないので,次はチャットの情報を抽出したいと思います.#chatチャンネルを作り,webhook_urlを取得しておきます.

<source>
  type forward
  port 24224
</source>

<match minecraft.raw>
  type rewrite_tag_filter
  rewriterule1 log \[INFO\]\:\s\<\S+\>\s minecraft.raw.chat
  rewriterule2 log WARN|ERROR minecraft.raw.error
  rewriterule3 log INFO minecraft.raw.info
</match>

<match minecraft.raw.chat>
  type slack
  webhook_url https://hooks.slack.com/services/zzzz/zzzzzzzz
  channel chat
  username Chat Log
  message_keys log
  flush_interval 1s
</match>

# ...省略...

rewriteruleの先頭にチャットを抽出するルールを追加します.チャットは[00:00:00] [Server thread/INFO] <ユーザ名> チャットテキストで表示されるため,以上のような(適当な)正規表現で抜き出しています.
ここでチャットログを抽出すると,以下のrewriterule2,3にはログデータがわたりません.つまり今回の場合,rewriterule3を先頭に(rewriterule1に)すると,チャットの抽出がされません.

ここでdocker-compose upし,適当にMinecraftサーバにログインしてチャットを入力すると以下のようになります.

f:id:pudding_info:20160907205220p:plain

fluent-plugin-rewrite

[00:00:00] [Server thread/INFO]って邪魔だなぁ…って思いません?ただのチャットだしいらない表示は消したいですよね.
ここでfluent-plugin-rewriteを使います.

# ...省略...
<match minecraft.raw>
  type rewrite_tag_filter
  rewriterule1 log \[INFO\]\:\s\<\S+\>\s minecraft.raw.chat
  rewriterule2 log WARN|ERROR minecraft.raw.error
  rewriterule3 log INFO minecraft.raw.info
</match>

<match minecraft.raw.chat>
  type rewrite
  add_prefix filtered
  <rule>
    key log
    pattern \[.+\]\:\s
    replace
  </rule>
</match>

<match filtered.minecraft.raw.chat>
  # ...省略...
</match>

# ...省略...

type rewrite以下に<rule>を追加し,

  • key : 正規表現を適用するログのキー
  • pattern : 検索する正規表現パターン
  • replace : 文字通り置換表現

<rule>はいくつでも追加することができ,ログに対して正規表現での整形が可能です.
上記のようなルールを設定すると以下のようにスッキリとしたチャットが表示されます.

f:id:pudding_info:20160907210031p:plain

また,ログイン時に色々とINFOログが出ますが欲しいのはjoinedというログだけなので,その他を消すためにruleを追加します.

# ...省略...

<match
<match minecraft.raw>
  type rewrite_tag_filter
  rewriterule1 log \[INFO\]\:\s\<\S+\>\s minecraft.raw.chat
  rewriterule2 log WARN|ERROR minecraft.raw.error
  rewriterule3 log INFO minecraft.raw.info
</match>

# ...省略...

<match minecraft.raw.info>
  type rewrite
  add_prefix filtered
  <rule>
    key log
    pattern \[.+\.?\]\slogged\sin|TextComponent|UUID\sof
    ignore true
  </rule>
</match>

<match filtered.minecraft.raw.chat>
  # ...省略...
</match>

<match filtered.minecraft.raw.info>
  # ...省略...
</match>

ものすごく適当な正規表現でごめんなさい…
これで以下のような感じになるはずです.

f:id:pudding_info:20160907210850p:plain

データのバックアップとリストア

せっかくデータコンテナを分離したので,手軽にバックアップが取れないかなぁと.
このminecraftイメージでは/data以下にworldデータが存在するので,それをtarで固めてしまいます.

$ docker run --rm --volumes-from data-minecraft -v $(pwd):/backup ubuntu tar cvf /backup/back.tar /data

これでカレントディレクトリにback.tarができるはずです.解凍すればMinecraftのデータが入っていることが分かります.
リストアしてみましょう.

$ docker run --rm --volumes-from data-minecraft -v $(pwd):/backup ubuntu bash -c "cd /data && tar xvf /backup/back.tar"

おそらくこれで問題なくリストアできます.
僕は既に稼働中の他のサーバのデータを引っ張ってこようとしたら色々エラーを吐かれて泣いています.誰か方法教えてください(泣)

総評

強い人が既に作製した強いイメージを簡単に使えるのがdockerの良いところな気もします.ものすごく簡単にここまで持ってくることができました.

あとはバックアップとリストアの仕組みがちゃんとできれば…
良いアイデア募集しています.