Mackerel で Nginx と PHP-FPM を監視するやつを Ansible でシュッとやる

Mackerel はそのままだと Zabbix より監視項目が少ないけど、プラグインを使えば、より個々の構成に固有なメトリクスを過不足なく取得できて嬉しいです。

自分はよく PHP のウェブサーバーを Ansible でプロビジョニングしているのですが、そういうお仕事でオススメな監視設定について、理解しながらチュートリアルを進める感じで紹介します。

お膳立てとして、なるべく CLI をシンプルにするために、カレントに `ansible.cfg` があるものとします。

```ini:ansible.cfg
[defaults]
roles_path = ./roles
private_key_file = ./path/to/private_key
```

OS は Ubuntu 16.04 を想定します。そのままだと Python2 が入ってないので、`ansible_python_interpreter=/usr/bin/python3` を... もし Python が 2.x の世代だった場合はいらないです。

```:hosts
[default]
xxx.xxx.xxx.xxx  server_identity=171202-001
xxx.xxx.xxx.xxx  server_identity=171202-002

[default:vars]
ansible_python_interpreter=/usr/bin/python3
```

`server_identity=` 変数をホストごとにユニークな値を持つように設定しておきます。これあとで使います。

# Ansible で Mackerel をインストールしてみる

はてなの中の人が作ってくださった Ansible ロールが Gallaxy に上がっています。

https://galaxy.ansible.com/mackerelio/mackerel-agent/

```shell-session
$ ansible-galaxy install mackerelio.mackerel-agent
- downloading role 'mackerel-agent', owned by mackerelio
- downloading role from https://github.com/mackerelio/ansible-mackerel-agent/archive/v0.7.0.tar.gz
- extracting mackerelio.mackerel-agent to /Users/tanakahisateru/Desktop/macka/roles/mackerelio.mackerel-agent
- mackerelio.mackerel-agent (v0.7.0) was installed successfully
```

使い方はざっくりこんな感じです。

```yaml:playbook.yml
---
- 
  hosts: default

  vars:
    mackerel_agent_apikey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    mackerel_agent_display_name: "web-server-{{ server_identity }}"
    mackerel_agent_roles: ["my_service:web_server"]
    mackerel_agent_start_on_setup: no

  roles:
    - mackerelio.mackerel-agent
```

`mackerel_agent_roles` を書いておけば、立ち上がった瞬間からもう、指定したサービスの指定したロールに登録されます。事前に Mackerel にサービスやロールを定義しておく必要はありません。そういう名前のが勝手に作られます。

これはぜひやってください。とくに初期導入時、いっぺんにサーバー追加したらどれがどれだか... IP アドレスから推測して GUI で役割を仕分けするの、まじ辛いです。

さらに `mackerel_agent_display_name` では、インベントリに付け足しで書いた `server_identity` 変数を使って表示名を工夫しています。IPアドレスかインスタンスIDかをもとに勝手に付けられた名前そのままではなく、ホストごとに名前を見たら意味がわかるようにしておくと、ちょっと便利です。(まあこれはオートスケールしない場合に限りますが)

リハーサルで正しくインストールできるか試すだけのうちは `mackerel_agent_start_on_setup` を `no` にして、いきなり起動しないようにしておくのがオススメです。課金対象のサーバーがいつの間にか増えてたら、いくら安いからって言っても、やっぱ上の人に怒られますよね。

ここで紹介しているのはいずれもオプションです。最低、`mackerel_agent_apikey` さえ正しく設定されていれば何とかなります。(逆に、これ設定されていないとエージェントが動きません)

実行はこうですね。

```shell-session
$ ansible-playbook -i hosts -u ubuntu -b playbook.yml

PLAY [default] ************************************************

TASK [Gathering Facts] ****************************************
ok: [xxx.xxx.xxx.xxx]

  :
  :
```

SSH で対象サーバーに接続して、`/usr/bin/mackerel-agent` がインストールされており、`/etc/mackerel-agent/mackerel-agent.conf` という設定ファイルが思ったように生成できているかを確認します。

```:/etc/mackerel-agent/mackerel-agent.conf
apikey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
display_name = "web-server-171202-001"

roles = [
  "my_service:web_server",
]
```

Mackerel の Ansible インストールって話なら、このまま本番用に `mackerel_agent_start_on_setup` の行を消しておしまいです。簡単ですね。これだけでも、ロードアベレージやメモリなどは監視できます。

ここからは PHPer のためのコーナーです。Nginx ベースの PHP 環境を監視するには、ちょっとひと工夫必要です。

# Nginx と PHP-FPM 自体の監視機能を有効にする

Ansible Gallaxy にある [geerlingguy](https://galaxy.ansible.com/geerlingguy/) さんのロールを使う仮定でいきます。

```yaml:requirements.yml
---
- geerlingguy.nginx
- geerlingguy.php
- mackerelio.mackerel-agent
```

```shell-session
$ ansible-gallaxy install -r requirements.yml
```

Nginx と PHP-FPM はともに、監視用の特殊な URL を提供する機能があり、そこに HTTP アクセスすることで、ステータスを調べるようになっています。

`playbook.yml` の `vars` の頭で、それを表す変数を導入します。(最初の `php_enable_php_fpm` は geerlingguy の php の設定変数です)

```yaml
  vars:
    php_enable_php_fpm: yes

    nginx_status_port: 8089
    nginx_status_path: "/nginx_status"
    php_fpm_status_path: "/php_fpm_status"
```

先に PHP のほう。FPM の設定ファイル `/etc/php/*/fpm/pool.d/www.conf` にあるコメントアウトされた `;pm.status_path=` を `pm.status_path = /php_fpm_status` のように書き換えれば OK。この書き換えは `lineinfile` でやるのが最短です。

```yaml
  tasks:
    - name: Configure php-fpm pool status path.
      lineinfile:
        dest: "{{ php_fpm_pool_conf_path }}"
        regexp: '^;?pm\.status_path.?=.+$'
        line: "pm.status_path = {{ php_fpm_status_path }}"
        state: present
      notify: restart php-fpm
```

もしうまく変更できてれば FPM をリスタートするように `notify` しています。

つぎ、Nginx で FPM の監視パスを公開するとともに、Nginx 自身の監視パスも設定しないといけません。Nginx は基本 `stub_status` ディレクティブを書くだけです。

で、この監視用のパスが外から見えるといけないので、中からしか叩けないようにしたほうがいいでしょう。また、ポートも 80 や 8080 でないものにしておくのがベターです。

geerlingguy のロール変数でカスタマイズしてやるのは逆にややこしいので、ここは、そのものズバリなファイル内容をテンプレートから作り、`/etc/nginx/conf.d` に放り込むことにします。

```jinja:templates/nginx_stat.conf.j2
server {
    listen       {{ nginx_status_port }};
    server_name  _;

    location = {{ nginx_status_path }} {
        access_log off;
        stub_status;
        allow 127.0.0.1;
        deny  all;
    }

    location = {{ php_fpm_status_path }} {
        access_log off;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass {{ php_fpm_listen }};
        allow 127.0.0.1;
        deny  all;
    }
}
```

`playbook.yml` の `tasks` に追加で...

```yaml
  tasks:
    # - name: Configure php-fpm pool status path.

    - name: Configure Nginx status listener.
      template: src=nginx_stat.conf.j2 dest=/etc/nginx/conf.d/stat.conf
      notify: restart nginx
```

ここまで、まとめて書くとこうなります。Mackerel 関連はいったん外してます。

```yaml:playbook.yml
---
- 
  hosts: default

  vars:
    php_webserver_daemon: nginx
    php_enable_php_fpm: true

    nginx_status_port: 8089
    nginx_status_path: "/nginx_status"
    php_fpm_status_path: "/php_fpm_status"

  roles:
    - geerlingguy.nginx
    - geerlingguy.php

  tasks:
    - name: Configure php-fpm pool status path.
      lineinfile:
        dest: "{{ php_fpm_pool_conf_path }}"
        regexp: '^;?pm\.status_path.?=.+$'
        line: "pm.status_path = {{ php_fpm_status_path }}"
        state: present
      notify: restart php-fpm

    - name: Configure Nginx status listener.
      template: src=nginx_stat.conf.j2 dest=/etc/nginx/conf.d/stat.conf
      notify: restart nginx
```

なるべくロール固有な事情に依存しないように買いてみました。ここで紹介したものとは違うロールを使っている場合でも、適宜そのルールに合わせて書き換えれば多分大丈夫です。

うまくできたのかどうか確認します。HTTP アクセスだけど外からは見えないので SSH でサーバーの中に入って...

```shell-session
$ curl http://127.0.0.1:8089/nginx_status
Active connections: 1 
server accepts handled requests
 21 21 21 
Reading: 0 Writing: 1 Waiting: 0

$ curl http://127.0.0.1:8089/php_fpm_status
pool:                 www
process manager:      dynamic
start time:           02/Dec/2017:00:00:00 +0000
start since:          6497
accepted conn:        5
listen queue:         0
max listen queue:     0
listen queue len:     128
idle processes:       4
active processes:     1
total processes:      5
max active processes: 1
max children reached: 0
slow requests:        0
```

Nginx と FPM を監視できるようになりました。これができればもう勝ちは見えています。

# Mackerel プラグインで監視項目を拡張

Mackerel には、ハードウェアの基本監視以外のメトリクスを取るために、多くのプラグインがあります。まあ、プラグインと言ってもその実体は、標準出力に文字列を吐くコマンドでしかありません。とても扱いやすいし、なんなら自分でも簡単に作れます。

公式プラグインのソースはここに:

https://github.com/mackerelio/mackerel-agent-plugins
https://github.com/mackerelio/go-check-plugins

エージェントプラグインというのは、ホストにいろいろなメトリックを追加するもの、チェックプラグインは特定の名前を持つプロセスがあるかみたいなyes/noのやつ(? まだよくわかっていません)、という感じみたいです。

公式プラグインを使って Nginx と FPM を監視できるようにしましょう。

```yaml
    mackerel_agent_apikey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    mackerel_agent_display_name: "web-server-{{ server_identity }}"
    mackerel_agent_roles: ["my_service:web_server"]

    mackerel_use_plugins: yes
    mackerel_agent_plugins:
      nginx: "/usr/bin/mackerel-plugin-nginx -port=8089 -path=/nginx_status"
      php-fpm: "/usr/bin/mackerel-plugin-php-fpm -url=http://127.0.0.1:8089/php_fpm_status?json"
    mackerel_check_plugins:
      nginx_proc: "/usr/bin/check-procs -p nginx"
      php-fpm_proc: "/usr/bin/check-procs -p php-fpm"

    mackerel_agent_start_on_setup: no
```

`mackerel_use_plugins` が `yes` だと、`/usr/bin/mackerel-plugin-*` といったコマンドがわんさか入るようになります。 (`/usr/local/bin` に同じようなセットが入りますが、そっちはレガシーの残骸だそうで、より新しいものは `/usr/bin` にしかありませんでした)

あとは、`mackerel_agent_plugins` に、個々のエージェントプラグインのコマンドライン引数を調べて追加、`mackerel_check_plugins` は簡単に名前だけ...

こうすると、プロビジョニング後 `/etc/mackerel-agent/mackerel-agent.conf` はこうなります。

```:/etc/mackerel-agent/mackerel-agent.conf
apikey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
display_name = "web-server-171202-001"

roles = [
  "my_service:web_server",
]


[plugin.metrics.nginx]
command = "/usr/bin/mackerel-plugin-nginx -port=8089 -path=/nginx_status"
[plugin.metrics.php-fpm]
command = "/usr/bin/mackerel-plugin-php-fpm -url=http://127.0.0.1:8089/php_fpm_status?json"

[plugin.checks.php-fpm_proc]
command = "/usr/bin/check-procs -p php-fpm"
[plugin.checks.nginx_proc]
command = "/usr/bin/check-procs -p nginx"
```

ほんと、ただコマンドラインに流す文字列が書かれてるだけですね。こんなので監視エージェント起動したらほんとに動くのか、って心配なので、試しに実行してみましょう。各コマンドはただの標準出力なので、実際に送信することなく、あらかじめ単独で動作を確認しておけます。

```shell-session
$ /usr/bin/mackerel-plugin-nginx -port=8089 -path=/nginx_status
nginx.connections.connections	1.000000	1511986657
nginx.requests.accepts	30.000000	1511986657
nginx.requests.handled	30.000000	1511986657
nginx.requests.requests	30.000000	1511986657
nginx.queue.reading	0.000000	1511986657
nginx.queue.writing	1.000000	1511986657
nginx.queue.waiting	0.000000	1511986657

$ /usr/bin/mackerel-plugin-php-fpm -url=http://127.0.0.1:8089/php_fpm_status?json
php-fpm.max_active_processes.max_active_processes	1	1511986670
php-fpm.max_children_reached.max_children_reached	0	1511986670
php-fpm.queue.listen_queue	0	1511986670
php-fpm.queue.listen_queue_len	128	1511986670
php-fpm.max_listen_queue.max_listen_queue	0	1511986670
php-fpm.slow_requests.slow_requests	0	1511986670
php-fpm.processes.total_processes	5	1511986670
php-fpm.processes.active_processes	1	1511986670
php-fpm.processes.idle_processes	4	1511986670

$ /usr/bin/check-procs -p php-fpm
Procs OK: Found 6 matching processes; cmd /php-fpm/

$ /usr/bin/check-procs -p nginx
Procs OK: Found 3 matching processes; cmd /nginx/
```

バッチリですね。あと `mackerel_agent_start_on_setup: no` を消しておけば、本番用 playbook の出来上がりです。


```yaml:playbook.yml
---
- 
  hosts: default

  vars:
    php_webserver_daemon: nginx
    php_enable_php_fpm: true

    nginx_status_port: 8089
    nginx_status_path: "/nginx_status"
    php_fpm_status_path: "/php_fpm_status"

    mackerel_agent_apikey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    mackerel_agent_display_name: "web-server-{{ server_identity }}"
    mackerel_agent_roles: ["my_service:web_server"]

    mackerel_use_plugins: yes
    mackerel_agent_plugins:
      nginx: "/usr/bin/mackerel-plugin-nginx -port=8089 -path=/nginx_status"
      php-fpm: "/usr/bin/mackerel-plugin-php-fpm -url=http://127.0.0.1:8089/php_fpm_status?json"
    mackerel_check_plugins:
      nginx_proc: "/usr/bin/check-procs -p nginx"
      php-fpm_proc: "/usr/bin/check-procs -p php-fpm"

    # mackerel_agent_start_on_setup: no

  roles:
    - geerlingguy.nginx
    - geerlingguy.php
    - mackerelio.mackerel-agent

  tasks:
    - name: Configure php-fpm pool status path.
      lineinfile:
        dest: "{{ php_fpm_pool_conf_path }}"
        regexp: '^;?pm\.status_path.?=.+$'
        line: "pm.status_path = {{ php_fpm_status_path }}"
        state: present
      notify: restart php-fpm

    - name: Configure Nginx status listener.
      template: src=nginx_stat.conf.j2 dest=/etc/nginx/conf.d/stat.conf
      notify: restart nginx
```

あ、肝心のアプリケーション用のドキュメントルート設定とかは省いてあるし、いいかげん大きいので別ファイルにするとかは、みなさん各自工夫してくださいね。

- - -

ハードウェアの負荷だけ見ているとわからないことも、実際にアクティブなFPMプロセス数なんかを監視すれば、すぐわかるかもしれません。

<img width="552" alt="fpm.png" src="https://qiita-image-store.s3.amazonaws.com/0/11525/d2c6b9fe-0128-2828-80fd-854b93aa0247.png">

PHP は小さな公開サイトによく使われるので、予想外のスパークが起きたときすぐわかるよう監視しておきたいところです。が、なんせプロジェクト規模も小さいのでなかなかエンジニアリソースが... なんてとき、導入が簡単で技術的なことは任せきりにできる Mackerel は、ちょうどいい選択かもしれませんね。