eBPF+USDTでphpをトレースしてみる、php-fpmとmod-phpでもやる

前回から気づけばだいぶ経過してましたが

dasalog.hatenablog.jp

実際phpスクリプトが動いてる環境はmod-phpであったりphp-fpmであったりするので、そのあたりは結局動かせておらず

このあたりも試してみたのでいちおう記録として。 個人的な環境のアレによりphp-fpmの方はubuntu bionic、mod-phpの方はfocalで動かしたがまあ特にやることは変わらないはず。

結局動かすためにはプロセスに対しUSE_ZEND_DTRACE=1環境変数をセットすることが必要で、「どこから仕込むことができるか?」ということだけですね、ということで基本はsystemdの設定を上書きする方法でトレースは実行できました。

環境は上述の通りubuntu前提ですがsystemdレイヤーでやっているので、他の環境でも同様にできはするかなと(ubuntu以外で--enable-dtraceされてるバイナリが提供されてるのかどうかは定かではないですが・・

php-fpm

systemdの設定を上書きするunitファイルを仕込んでみます。

sudo cp /lib/systemd/system/php7.2-fpm.service /etc/systemd/system/php7.2-fpm.service

して /etc/systemd/system/php7.2-fpm.serviceEnvironment="USE_ZEND_DTRACE=1" を追加して設定反映、再起動すればOK

$ cat /etc/systemd/system/php7.2-fpm.service
[Unit]
Description=The PHP 7.2 FastCGI Process Manager customized
Documentation=man:php-fpm7.2(8)
After=network.target

[Service]
Type=notify
PIDFile=/run/php/php7.2-fpm.pid
ExecStart=/usr/sbin/php-fpm7.2 --nodaemonize --fpm-config /etc/php/7.2/fpm/php-fpm.conf
ExecReload=/bin/kill -USR2 $MAINPID
Environment="USE_ZEND_DTRACE=1"

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl restart php7.2-fpm.service

おもむろにbpftraceでトレースすると対象プロセスでUSDTがトレースできることができる。

$ sudo BPFTRACE_STRLEN=200  bpftrace -e 'usdt:/usr/sbin/php-fpm7.2:execute__entry { printf("%s, %d\n", str(arg0), arg1); }' -p `pgrep -u www-data php-fpm|head -n1`
Attaching 2 probes...
/var/www/html/info.php, 2
, 0
/var/www/html/info.php, 2
, 0
...

probeが二個あることになってるのはなんでやろ・・。とりあえず動きはしたのでヨシ。

mod-php

php-fpmと同様にsystemdの設定に書くか、apacheのenvvarsで設定する、どちらでもとりあえず動かせはする。

$ cat /etc/systemd/system/apache2.service
[Unit]
Description=The Apache HTTP Server Customized
After=network.target remote-fs.target nss-lookup.target
Documentation=https://httpd.apache.org/docs/2.4/

[Service]
Type=forking
Environment="APACHE_STARTED_BY_SYSTEMD=true" "USE_ZEND_DTRACE=1"
ExecStart=/usr/sbin/apachectl start
ExecStop=/usr/sbin/apachectl stop
ExecReload=/usr/sbin/apachectl graceful
PrivateTmp=true
Restart=on-abort

[Install]
WantedBy=multi-user.target

or

/etc/apache2/envvarsexport USE_ZEND_DTRACE=1 を追記しておく。

おもむろに子プロセスにアタッチしてみれば動く、こちらの環境はlibphp7.4が使われてるのでこんなかんじで。

$ sudo BPFTRACE_STRLEN=200  bpftrace -e 'usdt:/usr/lib/apache2/modules/libphp7.4.so:execute__entry { printf("%s, %d\n", str(arg0), arg1); }' -p `pgrep -u www-data apache2|head -n1`
Attaching 1 probe...


/var/www/html/test.php, 2
/var/www/html/test.php, 2
/var/www/html/test.php, 2
...

その他

bpftraceで --usdt-file-activation 使えるとプロセス特定してアタッチみたいなところが幾分楽できそうな気がする。

まとめ

ということで若干あやしいところもありつつトレース自体は動かせた。

いずれにせよ USE_ZEND_DTRACE=1 したプロセスしかトレースはできないし、php-fpmにせよmod-phpにせよ子プロセスは入れ替わっていくので、アドホックなトレーシングはともかく「追っかけ続ける」仕組みをつくるのはしんどそうだなーとか、例えばexecute__entryを逐一標準出力に出していくみたいなのはもちろんプロダクションでおもむろにやるにはオーバーヘッドが許容できないであろうということは容易に想像でき、じゃあBPF_MAPを使うのか、どの粒度で情報を保持するのか、そもそもそここまでしてUSDT使うのか、みたいなところでやはりなかなか使い所はむずかしいなーという印象です。動かせるのは楽しいんですけどね。

全体に雰囲気で動かしているのでもっとうまくやる方法があればすごく知りたいところ・・。

参考

ubuntuのsystemd設定上書きについてはこちらの記事を参照させていただきました

第598回 systemdユニットの設定を変える:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社