Shin x Blog

PHPをメインにWebシステムを開発してます。Webシステム開発チームの技術サポートも行っています。

Docker Compose で php-fpm が発行するシステムコールを見る

php-fpm の挙動を確認するために発行されるシステムコールを簡単に確認できる Docker Compose 環境を作りました。

github.com

システムコール

システムコールは、php-fpm のようなユーザプログラムが、ファイル操作やネットワーク通信、プロセス制御のようなカーネルが提供する機能を利用する仕組みです。PHP コードは PHP(ここでは php-fpm)で実行する必要があるので、php-fpm が発行するシステムコールを確認することで php-fpm や PHP コードがどのように動作しているかを知る手掛かりになります。

システムコールを確認するには strace コマンドを利用します。strace は指定したコマンドやプロセスが発行したシステムコールをキャプチャすることができます。strace は対象のプログラムを変更するせずとも、外部から簡単に発行されたシステムコールを確認できます。

例えば、php -v コマンドが発行するシステムコールを確認するには下記のように strace の後に php -v を指定して実行します。

$ strace php -v
execve("/usr/local/bin/php", ["php", "-v"], 0xffffc3238da8 /* 17 vars */) = 0
brk(NULL)                               = 0xaaaaf9435000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffffbc154000
faccessat(AT_FDCWD, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
(snip)

システムコールについては、ここでは深掘りしないので下記リンクを参考にしてください。

strace-php-fpm

php-fpm が発行するシステムコールを簡単に確認できるように strace-php-fpm という Docker Compose 環境を作成しました。

github.com

make コマンドで起動すると、nginx と php-fpm が起動します。php-fpm は strace コマンド経由で実行され、php-fpm が発行するシステムコールは laravel/storage/logs/trace.txt に記録されるので、このファイルを見ると発行されたシステムコールが確認できます。

$ head -n 5 laravel/storage/logs/trace.txt
9     02:19:46.679857 execve("/usr/local/sbin/php-fpm", ["php-fpm"], 0xffffe0460808 /* 14 vars */) = 0 <0.001066>
9     02:19:46.681809 brk(NULL)         = 0xaaaac9abb000 <0.000021>
9     02:19:46.682124 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffffacab2000 <0.000029>
9     02:19:46.682511 faccessat(AT_FDCWD</var/www/html>, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) <0.000053>
9     02:19:46.683170 openat(AT_FDCWD</var/www/html>, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3</etc/ld.so.cache> <0.000034>

サンプルアプリケーションとして、Laravel を同梱しているので、下記 URL で簡単なレスポンスを返します。

$ curl http://localhost:8000/hello
Hello

この環境は php-fpm が発行するシステムコールをファイルに記録し続けるので目的の調査が完了したら、make clean コマンドで環境を破棄してください。なお、make clean では laravel/storage/logs/trace.txt を削除するので、必要ならこのファイルを別にコピーしておいてください。

strace オプション

strace には多数のオプションがあります。strace-php-fpm では下記のオプションを指定しています。

strace -f -s 1024 -y -ttT -o /var/www/html/storage/logs/trace.txt php-fpm
  • -f: 指定したプロセスだけでなく、子プロセスのシステムコールもキャプチャします。php-fpm ではワーカープロセスが対象になります。
  • -s SIZE: 出力する文字列サイズを指定(デフォルト: 32)
  • -y: ファイルディスクリプタ引数や AT_FDCWD に関連づけられたパスを表示。
  • -tt: 現在時刻をマイクロセカンド付きで出力。
  • -T: システムコールが消費した時間(マイクロセカンド)を出力。
  • -o PATH: 指定したファイルに出力。

www.man7.org

php-fpm が発行するシステムコール例

php-fpm が発行するシステムコールをいくつか見てみましょう。

ini ファイルの探索

php-fpm は php.ini のような設定ファイル(ini ファイル)を探索します。ini ファイルの設置場所は複数あり得るので、それぞれの場所にファイルがあるかを確認していく様子を見ることができます。ファイルが存在しない場合は No such file or directory が返されています。

php-SAPI_NAME.ini のような SAPI 名が含まれる ini ファイルや、php.ini、conf.d ディレクトリ以下の ini ファイルなどを確認しています。

8     05:31:55.242510 openat(AT_FDCWD</var/www/html>, "/usr/local/sbin/php-fpm-fcgi.ini", O_RDONLY) = -1 ENOENT (No such file or directory) <0.000034>
8     05:31:55.242813 openat(AT_FDCWD</var/www/html>, "/usr/local/etc/php/php-fpm-fcgi.ini", O_RDONLY) = -1 ENOENT (No such file or directory) <0.000038>
8     05:31:55.243070 openat(AT_FDCWD</var/www/html>, "/usr/local/sbin/php.ini", O_RDONLY) = -1 ENOENT (No such file or directory) <0.000030>
8     05:31:55.243309 openat(AT_FDCWD</var/www/html>, "/usr/local/etc/php/php.ini", O_RDONLY) = -1 ENOENT (No such file or directory) <0.000034>
8     05:31:55.243580 openat(AT_FDCWD</var/www/html>, "/usr/local/etc/php/conf.d", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3</usr/local/etc/php/conf.d> <0.000030>
8     05:31:55.243860 newfstatat(3</usr/local/etc/php/conf.d>, "", {st_mode=S_IFDIR|0755, st_size=4096, ...}, AT_EMPTY_PATH) = 0 <0.000027>
8     05:31:55.244089 getdents64(3</usr/local/etc/php/conf.d>, 0xaaaaf647f430 /* 14 entries */, 32768) = 600 <0.000054>
8     05:31:55.244400 getdents64(3</usr/local/etc/php/conf.d>, 0xaaaaf647f430 /* 0 entries */, 32768) = 0 <0.000032>
8     05:31:55.244632 close(3</usr/local/etc/php/conf.d>) = 0 <0.000027>
8     05:31:55.244852 newfstatat(AT_FDCWD</var/www/html>, "/usr/local/etc/php/conf.d/docker-fpm.ini", {st_mode=S_IFREG|0644, st_size=96, ...}, 0) = 0 <0.000039>
8     05:31:55.245087 openat(AT_FDCWD</var/www/html>, "/usr/local/etc/php/conf.d/docker-fpm.ini", O_RDONLY) = 3</usr/local/etc/php/conf.d/docker-fpm.ini> <0.000029>

nginx からの FastCGI リクエスト

nginx から送信される FastCGI リクエストの内容を確認できます。FastCGI はバイナリプロトコルですが、バイナリデータもエンコードされて文字列として出力されます。

46    05:40:15.567735 read(4<socket:[119519]>, "\1\1\0\1\0\10\0\0", 8) = 8 <0.000055>
46    05:40:15.568228 read(4<socket:[119519]>, "\0\1\0\0\0\0\0\0", 8) = 8 <0.000051>
46    05:40:15.568701 read(4<socket:[119519]>, "\1\4\0\1\7\232\6\0", 8) = 8 <0.000055>
46    05:40:15.569172 read(4<socket:[119519]>, "\17\36SCRIPT_FILENAME/var/www/html/public/index.php\f\0QUERY_STRING\16\3REQUEST_METHODGET\f\0CONTENT_TYPE\16\0CONTENT_LENGTH\v\nSCRIPT_NAME/index.php\v\6REQUEST_URI/hello\f\nDOCUMENT_URI/index.php\r\24DOCUMENT_ROOT/var/www/html/public\17\10SERVER_PROTOCOLHTTP/1.1\16\4REQUEST_SCHEMEhttp\21\7GATEWAY_INTERFACECGI/1.1\17\fSERVER_SOFTWAREnginx/1.24.0\v\fREMOTE_ADDR192.168.65.1\v\5REMOTE_PORT16108\v\nSERVER_ADDR172.24.0.4\v\2SERVER_PORT80\v\0SERVER_NAME\17\3REDIRECT_STATUS200\t\16HTTP_HOSTlocalhost:8000\17\nHTTP_CONNECTIONkeep-alive\16AHTTP_SEC_CH_UA\"Chromium\";v=\"124\", \"Google Chrome\";v=\"124\", \"Not-A.Brand\";v=\"99\"\25\2HTTP_SEC_CH_UA_MOBILE?0\27\7HTTP_SEC_CH_UA_PLATFORM\"macOS\"\36\1HTTP_UPGRADE_INSECURE_REQUESTS1\17uHTTP_USER_AGENTMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\v\200\0\0\207HTTP_ACCEPTtext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\23\4HTTP_SEC_FETCH_SITEnone\23\10HTTP_SEC_FETCH_MODEnavigate\23\2HTTP_SEC_FETCH_USER?1\23\10HTTP_SEC_FET"..., 1952) = 1952 <0.000046>
46    05:40:15.569704 read(4<socket:[119519]>, "\1\4\0\1\0\0\0\0", 8) = 8 <0.000053>

PHP ファイルの読み込み

PHP コードの include() や require() などによる PHP ファイルの読み込みも確認できます。下記では /var/www/html/vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php を読み込んでいます。

46    05:40:15.822747 openat(AT_FDCWD</var/www/html/public>, "/var/www/html/vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php", O_RDONLY) = 7</var/www/html/vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php> <0.000444>
46    05:40:15.823507 newfstatat(7</var/www/html/vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php>, "", {st_mode=S_IFREG|0644, st_size=5606, ...}, AT_EMPTY_PATH) = 0 <0.000071>
46    05:40:15.823974 read(7</var/www/html/vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php>, "<?php\n\nnamespace Illuminate\\Contracts\\Foundation;\n\nuse Illuminate\\Contracts\\Container\\Container;\n\ninterface Application extends Container\n{\n    /**\n     * Get the version number of the application.\n     *\n     * @return string\n     */\n    public function version();\n\n    /**\n     * Get the base path of the Laravel installation.\n     *\n     * @param  string  $path\n     * @return string\n     */\n    public function basePath($path = '');\n\n    /**\n     * Get the path to the bootstrap directory.\n     *\n     * @param  string  $path\n     * @return string\n     */\n    public function bootstrapPath($path = '');\n\n    /**\n     * Get the path to the application configuration files.\n     *\n     * @param  string  $path\n     * @return string\n     */\n    public function configPath($path = '');\n\n    /**\n     * Get the path to the database directory.\n     *\n     * @param  string  $path\n     * @return string\n     */\n    public function databasePath($path = '');\n\n    /**\n     * Get the path to the language files.\n     *\n     * @par"..., 5606) = 5606 <0.000160>
46    05:40:15.824734 close(7</var/www/html/vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php>) = 0 <0.000097>

さいごに

strace で発行されるシステムコールを見ることで、どのような挙動となっているかを外部から観察できます。これは PHP においても同様であり、ソースコードを読んで解析するのと合わせてシステムコールを確認することで、より理解を深めることができます。

Docker Compose で簡単に試すことができるので利用してみてください。

参考

[試して理解]Linuxのしくみ ―実験と図解で学ぶOS、仮想マシン、コンテナの基礎知識【増補改訂版】

learning.oreilly.com