はじめに
Railsアプリケーションの開発用Webサーバとして、puma-devが便利ということで何気なく使っていました。しかしその仕組みについてはいまいちブラックボックス感が拭えませんでした。
そこで勉強も兼ねて、実際にpuma-devのコードを読んで仕組みを調べてみました。
調べたテーマ
コードを全て読み切るというのは大変だったので、puma-devについて自分が疑問に思っていたことを2つテーマとして設けて調べました。
- puma-devの初期セットアップで何をしているか?
- Webブラウザからリクエストを投げたときpuma-devは何をしているか?
前提
環境
puma-devはLinuxでも利用できるかと思いますが、本記事ではMac OS Xの環境で調べています。
- Mac OS X 10.13.3
- puma-dev v0.11
コードリーディングの対象リポジトリ
2018/4/19時点で最新のreleaseであるv0.11のpuma-devをコードリーディングの対象としました。
puma-devというくらいなのでrubyかと思いましたが、golangで書かれていました。しかし割とシンプルなCLIツールの構成になっており、読みやすかった印象です。
調べたテーマ1: puma-devの初期セットアップで何をしているか?
まずはpuma-devを利用する前の初期設定で何をしているのかを調べました。
多分puma-devを使うときは、おおよそ下記のようなコマンドを実行するかと思います。
それぞれのコマンドで何をしているかざっくり確認します。
$ puma-dev -install -d test
$ sudo puma-dev -setup
$ cd /path/to/app
$ puma-dev link -n app_name
$ puma-dev -uninstall
コマンド:puma-dev -install -d test
コード該当箇所
- LaunchAgentsの設定ファイル
~/Library/LaunchAgents/io.puma.dev.plist
を書き出している。-d
で指定したドメイン名test
もここで.plistファイルに反映される -
~/Library/Logs/puma-dev.log
をログファイルのパスに指定している -
launchctl load
で配置した.plistファイルをサービス起動している
puma-devはMacのLaunchAgentsを使って、プロセスは自動起動する挙動をとるようです。
インストール後、下記のようなコマンドでpuma-devのプロセスが存在することを確認できると思います。
$ ps x | grep puma-dev
1086 ?? S 1:14.41 /usr/local/Cellar/puma-dev/0.11/bin/puma-dev -launchd -dir ~/.puma-dev -d test -timeout 15m0s
コマンド:puma-dev -setup
コード該当箇所
-
etcDir
で指定されているディレクトリ/etc/resolver
の所有者をpuma-devコマンドを実行しているユーザに変更する -
/etc/resolver
以下にDNS名前解決用のドメイン名(test)のファイルを作成する
resolverを使い、ローカルのMacにpuma-dev用のDNSサーバ設定を追加しているようです。下記のようなコマンドで確認できます。
$ cat /etc/resolver/test
# Generated by puma-dev
nameserver 127.0.0.1
port 9253
コマンド:puma-dev -stop
コード該当箇所
-
pkill -USR1 puma-dev
を実行し、puma-devプロセスを再起動する
puma-dev -install
でLaunchAgentに常時起動プロセスとして登録されているので、プロセスは停止せず再起動する挙動をとるみたいです。
コマンド:puma-dev link -n app_name
コード該当箇所
-
-n
オプションで指定したファイル名でシンボリックリンクを作成する - シンボリックリンクは
~/.puma-dev/app_name
のように作成され、コマンド実行したディレクトリまたは-n app_name
以降に設定したディレクトリパスを紐付ける
コードを読む限り、単純なシンボリックリンク作成のみしているようです。
コマンド:puma-dev -uninstall
コード該当箇所
-
launchctl unload
でpuma-devプロセスを停止する -
~/Library/LaunchAgents/io.puma.dev.plist
を削除する -
/etc/resolver
以下のファイルを削除する
アンインストールということもあり、puma-dev用の設定ファイルの削除まで実施するみたいです。
余談ですが、puma-devプロセスだけを停止したい場合は、下記コマンドを実行すれば良いということになりそうです。
(※puma-devのissueにも書いてありました。)
$ launchctl unload ~/Library/LaunchAgents/io.puma.dev.plist
調べたテーマ2: Webブラウザからリクエストを投げたときpuma-devは何をしているか?
おおまかにpuma-devの設定で何をしているかは確認できたので、次は実際にpuma-devを利用するときの挙動を調べてみました。
概要図
まず、自分がpuma-devのコードやプロセスの挙動を調べて理解した内容を図にしてみました。
- Webブラウザから
https://XXX.test
というURLでリクエスト -
/etc/resolver/test
で定義されている設定に基づき、puma-devプロセス上のDNSサーバ経由でURLの名前解決をリクエスト - puma-devプロセス上のDNSサーバで、HTTPSなら
localhost:443
に、HTTPならlocalhost:80
に名前解決 - puma-devプロセス上のHTTP/HTTPSサーバから、UNIXドメインソケット通信を使ったpumaプロセスを起動・リクエスト
- pumaがリクエストに応じてRailsアプリケーションにリクエストする
こちらを踏まえ、Webブラウザからリクエストが投げられた後の挙動をざっくり見ていこうと思います。
1 ~ 2までの挙動
Webブラウザからhttps://XXX.test
というURLでリクエストすると、まずDNSの名前解決を試みます。
puma-dev -install -d test
で作成されたファイルをおさらいすると、IPアドレス127.0.0.1:9253
が指定されており、こちらに対して名前解決を試みます。
# Generated by puma-dev
nameserver 127.0.0.1
port 9253
ポート9253番はpuma-devのデフォルトのDNSサーバのポート番号として設定されています。
2 ~ 3までの挙動
127.0.0.1:9253
に対するDNSの名前解決のリクエストは、LaunchAgentsによって常時起動しているpuma-devのプロセスに対して実行されます。
改めて、puma-devのプロセスが起動していることを確認します。
$ ps x | grep puma-dev
1086 ?? S 1:14.41 /usr/local/Cellar/puma-dev/0.11/bin/puma-dev -launchd -dir ~/.puma-dev -d test -timeout 15m0s
またpuma-devがLISTENしているポート番号を確認してみます。9253番とhttp/httpsのポートをLISTENしているのがわかります。(http/httpsについては後ほどまとめます)
つまり、ポート9253番への通信があるとpuma-devのプロセスに通信されることになります。
ここから、puma-devプロセスがDNSサーバとしてリクエストを受けることが予想できます。
$ lsof -i | grep puma-dev
puma-dev 1086 gotchane 5u IPv4 0x7ab086f0e36ca1fd 0t0 TCP localhost:9253 (LISTEN)
puma-dev 1086 gotchane 7u IPv4 0x7ab086f0cff974d5 0t0 UDP localhost:9253
puma-dev 1086 gotchane 8u IPv4 0x7ab086f0e36991fd 0t0 TCP *:http (LISTEN)
puma-dev 1086 gotchane 11u IPv4 0x7ab086f0e36925dd 0t0 TCP *:https (LISTEN)
実際のコードも読んでみると、抜粋した箇所でリクエストをLISTENする処理が書かれているようです。
puma-devはhttp/httpsのポートもLISTENしているので、リクエストはそのままpuma-devのhttp/httpsサーバに移る感じになると思います。
3 ~ 5の挙動
ここではhttp/httpsサーバからRailsアプリケーションがどう起動されるかに絞って調べてみました。
(※puma-devで複数アプリケーション登録した時の切り替え処理などもありましたが、その辺はちょっと複雑だったので割愛します。近々追記予定)
Railsアプリケーションが起動する処理は、下記抜粋箇所に実装されています。
読んでいただくとわかるのですが、こちらのコード内にpumaをUNIXドメインソケット通信で起動するコマンドが定義されています。
下記にコマンドの部分だけ抜粋します。-b unix:%s
と書いてあるので、UNIXドメインソケット通信のファイルパスにbindして起動していることがわかります。
const executionShell = `exec bash -c '
cd %s
if test -e ~/.powconfig; then
source ~/.powconfig
fi
if test -e .env; then
source .env
fi
if test -e .powrc; then
source .powrc
fi
if test -e .powenv; then
source .powenv
fi
if test -e Gemfile && bundle exec puma -V &>/dev/null; then
exec bundle exec puma -C $CONFIG --tag puma-dev:%s -w $WORKERS -t 0:$THREADS -b unix:%s
fi
exec puma -C $CONFIG --tag puma-dev:%s -w $WORKERS -t 0:$THREADS -b unix:%s'
`
こちらを踏まえると、特にrails s
やbundle exec puma
などのコマンドを別途実行する必要はなく、puma-devで実行してくれるということがわかります。
pumaプロセスを確認してみると、UNIXドメインソケット通信のファイルを作ってpumaが起動していることがわかると思います。
$ ps x | grep puma
1086 ?? S 1:15.38 /usr/local/Cellar/puma-dev/0.11/bin/puma-dev -launchd -dir ~/.puma-dev -d test -timeout 15m0s
80819 ?? S 5:28.50 puma 3.6.2 (unix:/Users/gotchane/.puma-dev/sample_app/tmp/puma-dev-1086.sock) [puma-dev:sample_app]
ここでようやくRailsアプリへのリクエストまで疎通できるところまで辿ってこれました。
終わりに
だいぶ大まかに調べてきましたが、コードを読みつつ挙動を調べることでだいぶ理解が進んだと思いました。golangの勉強にもなってよかったです。