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

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

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

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

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/

$ 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

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

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_setupno にして、いきなり起動しないようにしておくのがオススメです。課金対象のサーバーがいつの間にか増えてたら、いくら安いからって言っても、やっぱ上の人に怒られますよね。

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

実行はこうですね。

$ 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 さんのロールを使う仮定でいきます。

requirements.yml
---
- geerlingguy.nginx
- geerlingguy.php
- mackerelio.mackerel-agent
$ ansible-gallaxy install -r requirements.yml

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

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

  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 でやるのが最短です。

  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 に放り込むことにします。

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.ymltasks に追加で...

  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 関連はいったん外してます。

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 でサーバーの中に入って...

$ 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 を監視できるようにしましょう。

    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_pluginsyes だと、/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"

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

$ /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 の出来上がりです。

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プロセス数なんかを監視すれば、すぐわかるかもしれません。

fpm.png

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