Dockerでmonitを動かす(NginxとPHP(hhvm)とMySQL)

Dockerでは、基本的に内部で動かせるサービスは一つだけです。そして、そのサービスの実行がとまった瞬間に、コンテナは動作を停止します。

そのため、複数のサービスを実行する場合は、SupervisorやMonitなどのツールを使い、それをコンテナとすることで、複数のサービスを動かします。

イメージとしては、以下のサイトに書かれている図が詳しい。

 * [Dockerfile の書き方「私的」なベストプラクティス（3）〜サービスの起動について〜](http://inokara.hateblo.jp/entry/2013/12/29/215322)

本来Monitは、その名の通りさまざまなサービスの生死状態やファイルの存在、プロセスの状態などを監視して状況をメールやWeb UIで通知するツールです。定義ファイルにメールの宛先やメール送信条件、Web UIの表示ポート番号などを指定すると、監視ツールとして機能するようになっています。
今回はその機能は使わず、それぞれのサービスを動かすためのコンテナとして動作をしてもらいます。

# Monit定義ファイルディレクトリ
とはいえ、こちらでやることはMonitのサービス定義ファイルを設定ディレクトリに配置するだけです。今回は[前回](http://qiita.com/TakamiChie/items/936b3c8676baf06b11b3)紹介したNginxとPHP(hhvm)、MySQLを利用します。それぞれのインストール方法については追々。

Monitのサービス定義ファイルは、`/etc/monit/`ディレクトリ配下に配置します。たぶん直下のmonitrcに直接書いても良いのでしょうが、`conf.d`ディレクトリの配下(`/etc/monit/conf.d/`)に配置するのが一般的っぽいです。

## Nginx
Nginxについては、以下のような感じ。必須なのは最初の三行です。他、Monitにサーバの生死確認などの機能を持たせたいときは、四行目以降にいろいろ書いていく感じ。

```nginx.conf
check process nginx with pidfile /var/run/nginx.pid
  start program = "/etc/init.d/nginx start" with timeout 60 seconds
  stop program  = "/etc/init.d/nginx stop"
  if failed port 80 protocol http
    then restart
  group www-data
```

## hhvm
HHVMについては次のような感じです。ただ、実際この設定でMonitのログファイルを見ると、プロセスの監視に失敗している模様です。動作はしているのでとりあえず無視していますがちゃんと使うなら直した方がよさげ。

```hhvm.conf
check process hhvm with pidfile /var/run/hhvm/pid
  group hhvm
  start program = "/usr/sbin/service hhvm start" with timeout 60 seconds
  stop program  = "/usr/sbin/service hhvm stop"
  if failed unixsocket /var/run/hhvm/hhvm.sock then restart
  if mem > 400.0 MB for 1 cycles then restart
  if 5 restarts with 5 cycles then timeout
```

## MySQL
引き続きMySQLも。

```mysql.conf
check process mysql with pidfile /var/run/mysqld/mysqld.pid
   start program = "/etc/init.d/mysql start"
   stop program = "/etc/init.d/mysql stop"
   if failed unixsocket /var/lib/mysql/mysql.sock then restart
   if 5 restarts within 5 cycles then timeout
```

## Dockerfile

そして最後にDockerfile。関連してるところだけの抜粋です。

```Dockerfile:Dockerfile

# monit
RUN apt-get -y install monit
・・・
# monit
ADD monit/*.conf /etc/monit/conf.d/
・・・
CMD ["/usr/bin/monit", "-I", "-c", "/etc/monit/monitrc"]
```

とりあえずこれで動作します。最後monitの-Iオプションは、monitをフォアグラウンドで起動させるためのオプションです。このオプションを使わないと、monitがバックグラウンドで起動する→起動直後、フォアグラウンドなプロセスが全部終了したとみなされて、コンテナが終了してしまいます。


# Monitの監視状態をモニタする

今回はMonitのWeb UI機能を使いませんので、それぞれのサーバの状況をMonitを使って閲覧する場合、CUIからの操作となります。
DockerでMonitを動かしている場合、以下記事で紹介されている方法でコンテナのシェルにログインして、コマンドを実行します。

 * [コンテナに入りたい？それ docker exec でできるよ](http://qiita.com/yosisa/items/a5670e4da3ff22e9411a)

で、そのコマンドは、そのまま`monit`です。`-v`オプションを使えばひとまず監視中のサービスの状態が表示されます。参考までに自分の表示結果を。

```出力結果

> monit -v

Runtime constants:
 Control file       = /etc/monit/monitrc
 Log file           = /var/log/outlog/monit.log
 Pid file           = /run/monit.pid
 Id file            = /var/lib/monit/id
 State file         = /var/lib/monit/state
 Debug              = True
 Log                = True
 Use syslog         = False
 Is Daemon          = True
 Use process engine = True
 Poll time          = 120 seconds with start delay 0 seconds
 Expect buffer      = 256 bytes
 Event queue        = base directory /var/lib/monit/events with 100 slots
 Mail from          = (not defined)
 Mail subject       = (not defined)
 Mail message       = (not defined)
 Start monit httpd  = False

The service list contains the following entries:

Process Name          = nginx
 Group                = www-data
 Pid file             = /var/run/nginx.pid
 Monitoring mode      = active
 Start program        = '/etc/init.d/nginx start' timeout 60 second(s)
 Stop program         = '/etc/init.d/nginx stop' timeout 30 second(s)
 Existence            = if does not exist 1 times within 1 cycle(s) then restart else if succeeded 1 times within 1 cycle(s) then alert
 Pid                  = if changed 1 times within 1 cycle(s) then alert
 Ppid                 = if changed 1 times within 1 cycle(s) then alert
 Port                 = if failed [localhost:80 [HTTP via TCP] with timeout 5 seconds and retry 0 time(s) 1 times within 1 cycle(s) then restart else if succeeded 1 times within 1 cycle(s) then alert

Process Name          = mysql
 Pid file             = /var/run/mysqld/mysqld.pid
 Monitoring mode      = active
 Start program        = '/etc/init.d/mysql start' timeout 30 second(s)
 Stop program         = '/etc/init.d/mysql stop' timeout 30 second(s)
 Existence            = if does not exist 1 times within 1 cycle(s) then restart else if succeeded 1 times within 1 cycle(s) then alert
 Pid                  = if changed 1 times within 1 cycle(s) then alert
 Ppid                 = if changed 1 times within 1 cycle(s) then alert
 Unix Socket          = if failed [/var/lib/mysql/mysql.sock [protocol DEFAULT] with timeout 5 seconds and retry 0 time(s) 1 times within 1 cycle(s) then restart else if succeeded 1 times within 1 cycle(s) then alert
 Timeout              = If restarted 5 times within 5 cycle(s) then unmonitor

Process Name          = hhvm
 Group                = hhvm
 Pid file             = /var/run/hhvm/pid
 Monitoring mode      = active
 Start program        = '/usr/sbin/service hhvm start' timeout 60 second(s)
 Stop program         = '/usr/sbin/service hhvm stop' timeout 30 second(s)
 Existence            = if does not exist 1 times within 1 cycle(s) then restart else if succeeded 1 times within 1 cycle(s) then alert
 Pid                  = if changed 1 times within 1 cycle(s) then alert
 Ppid                 = if changed 1 times within 1 cycle(s) then alert
 Unix Socket          = if failed [/var/run/hhvm/hhvm.sock [protocol DEFAULT] with timeout 5 seconds and retry 0 time(s) 1 times within 1 cycle(s) then restart else if succeeded 1 times within 1 cycle(s) then alert
 Memory amount limit  = if greater than 409601kB 1 times within 1 cycle(s) then restart else if succeeded 1 times within 1 cycle(s) then alert
 Timeout              = If restarted 5 times within 5 cycle(s) then unmonitor

System Name           = bbe50316866a
 Monitoring mode      = active

```

# Dockerとのつきあい方
あちこち見ると、Dockerの使い方には二種類あるように感じます

 * 一つのコンテナに全てのサービスを突っ込んで運用する
 * 複数のコンテナに一つ一つサービスを突っ込んで、それを連携させて一つのシステムをなす

個人的には後者の方がコンテナ一つあたりの構成はシンプルになるし扱いやすくなるとは思います(複数のコンテナ間連携には、dockerの--linkオプションなどが利用できます)が、複数のコンテナを協働させる仕組みが必要になるため、総合的な運用コストが高まります。
また、あくまで主役はコンテナではなくその中で動くアプリですので、環境を維持しつつアプリをテストしていくというDocker本来の用途から離れてしまうように感じます。

そのため、今回は前者の方法でコンテナを作成・運用する方法として、Monitを使う方法をためしてみました。