こんにちは開発チームの崔珉秀と申します。
今回はnginxというウェブサーバーについて話をさせて頂きます。
nginxは最近数年の間けっこう人気が高くなっています。特によく使われているApacheやLighttpdなどのウェブサーバーと性能の面で比較することがよくありまして、優れた性能で単純なstaticファイルを転送するウェブサーバーからCGIサーバー、reverse proxyサーバーなどの様々なウェブリクエスト処理に関わる分野で導入されています。
今日はnginxの性能の比較よりもサーバーの開発者(nginx module)もしくはサーバーの運営者としてのnginxにある仕組の中で一つを紹介したいと存じます。
サーバーの開発や運営をする場合ロジックや設定などの変更により配布の後、サーバーを再起動することがあります。
その再起動の時にウェブサービスとしてリクエストの処理を続けながら、変更の内容を反映するための手段がいくつかあります。
他のウェブサーバーも普通設定の変更の場合は“優雅な再起動(graceful restart)”でウェブリクエストの処理を中断せずに変更の内容を反映しています。
しかし、サーバーのバイナリ自体が変わった場合、ウェブリクエストの処理を継続しながらサーバーを再起動する時より安定的にその再起動を行うためのnginxの仕様が有ります。
nginxはバイナリの交換の後再起動を以下の様にコントロール出来ます。
- サーバーのbinary配布
- 新規バイナリでnginxを起動(既存プロセスと同時に起動する)
- リクエストの処理は既存のbinaryと同時に処理
- リクエストの処理を新規バイナリに交代(kill -WINCH [old master Pid])
- 既存のnginx processを優雅に終了(kill -QUIT [old master Pid])
もっと詳しくrestart時のprocessの状態を各段階別に確認しましょう。
1.再起動前
root 29624 S 16:28 0:00 nginx: master process /usr/local/nginx/sbin/nginx
www 29625 S 16:28 0:10 nginx: worker process
www 29626 S 16:28 0:09 nginx: worker process
www 29627 S 16:28 0:21 nginx: worker process
www 29628 S 16:28 0:11 nginx: worker process
www 29629 S 16:28 0:13 nginx: worker process
www 29630 S 16:28 0:10 nginx: worker process

2. SIGUSR2でmasterとworker processを複製
→ 新規バイナリで新しいmasterを実行
→ この時点では二つのプロセスがリクエストを処理している
root 29624 S 16:28 0:00 nginx: master process /usr/local/nginx/sbin/nginx
www 29625 S 16:28 0:10 nginx: worker process
www 29626 S 16:28 0:10 nginx: worker process
www 29627 S 16:28 0:21 nginx: worker process
www 29628 S 16:28 0:11 nginx: worker process
www 29629 S 16:28 0:13 nginx: worker process
www 29630 S 16:28 0:10 nginx: worker process
root 30164 S 18:24 0:00 nginx: master process /usr/local/nginx/sbin/nginx
www 30165 S 18:24 0:00 nginx: worker process
www 30166 S 18:24 0:00 nginx: worker process
www 30167 S 18:24 0:00 nginx: worker process
www 30168 S 18:24 0:00 nginx: worker process
www 30169 S 18:24 0:00 nginx: worker process
www 30170 S 18:24 0:00 nginx: worker process

3. SIGWINCHでリクエストを処理するworkerを切り替える
→ 以前workerは新しい接続を受けない(acceptしない)
→ acceptの役割を新規workerが担当
→ この時点で既存masterにSIGQUITをすると新規binaryに切り替え
→ 既存のバイナリに巻き戻す場合は新規masterの方にSIGNALを投げる。
root 29624 S 16:28 0:00 nginx: master process /usr/local/nginx/sbin/nginx
www 29625 S 16:28 0:10 nginx: worker process
www 29626 S 16:28 0:10 nginx: worker process
www 29627 S 16:28 0:21 nginx: worker process
www 29628 S 16:28 0:11 nginx: worker process
www 29629 S 16:28 0:13 nginx: worker process
www 29630 S 16:28 0:10 nginx: worker process
root 30164 S 18:24 0:00 nginx: master process /usr/local/nginx/sbin/nginx
www 30165 S 18:24 0:00 nginx: worker process
www 30166 S 18:24 0:00 nginx: worker process
www 30167 S 18:24 0:00 nginx: worker process
www 30168 S 18:24 0:00 nginx: worker process
www 30169 S 18:24 0:00 nginx: worker process
www 30170 S 18:24 0:00 nginx: worker process

4. SIGQUITで既存プロセスを終了
→ 既存worker processは処理中のリクエストのレスポンスを全部返したら終了
→ 既存worker processがすべて終了した時点で既存master processも終了root 29624 S 16:28 0:00 nginx: master process /usr/local/nginx/sbin/nginx
www 29625 S 16:28 0:10 nginx: worker process is shutting down
www 29626 S 16:28 0:10 nginx: worker process is shutting down
www 29627 S 16:28 0:21 nginx: worker process is shutting down
www 29628 S 16:28 0:11 nginx: worker process is shutting down
www 29629 S 16:28 0:13 nginx: worker process is shutting down
www 29630 S 16:28 0:10 nginx: worker process is shutting down
root 30164 S 18:24 0:00 nginx: master process /usr/local/nginx/sbin/nginx
www 30165 S 18:24 0:00 nginx: worker process
www 30166 S 18:24 0:00 nginx: worker process
www 30167 S 18:24 0:00 nginx: worker process
www 30168 S 18:24 0:00 nginx: worker process
www 30169 S 18:24 0:00 nginx: worker process
www 30170 S 18:24 0:00 nginx: worker process

5. 切り替えが終った状態
root 30164 S 18:24 0:00 nginx: master process /usr/local/nginx/sbin/nginx
www 30165 S 18:24 0:00 nginx: worker process
www 30166 S 18:24 0:00 nginx: worker process
www 30167 S 18:24 0:01 nginx: worker process
www 30168 S 18:24 0:00 nginx: worker process
www 30169 S 18:24 0:01 nginx: worker process
www 30170 S 18:24 0:00 nginx: worker process

参考としてSIGWINCHを行う前にRollbackを考えていない状況であればこの再起動のスクリプトは以下になります。
#!/bin/bash↵
echo"restart nginx service for change binary
"↵kill -USR2 `cat /usr/local/nginx/logs/nginx.pid`↵
kill -WINCH `cat /usr/local/nginx/logs/nginx.pid.oldbin`↵
kill -QUIT `cat /usr/local/nginx/logs/nginx.pid.oldbin`↵
上の様な同時に二つ以上のプロセスが同じPortで同時に接続を受けることはfork()の仕組みとaccept mutexという仕組みで構成されているものです。
あるプロセスがfork()で子プロセスを生成しますとその時点で親プロセスが持っているリソースが子プロセスに引き継ぎます。
例えば親プロセスがOpenしてbindingしているlisten socketも子プロセスに引き継いで子プロセスも同じくlisten socketからAcceptが出来ます。
しかし複数のプロセスが同時にAcceptをしない様にAcceptを直列化(serialize)する為にlisten socketからacceptすることをmutex lockなどで管理して、順次的に子プロセスのAcceptを行います。

この仕組みで複数のプロセスが同時にlisten socketを共有しながら仕事(リクエストの処理)を分配して行うことが出来ます。
このaccept mutexの方式はnginxだけで使われていることではないですが、これをよく活用してnginx binaryの交換をより安定的に行われるようにしたことをサーバー開発者として学びました。
特にnginxの場合はmoduleを追加してある機能を追加して行くようになっていますが、nginxのmoduleはstaticにいれるもので適用変更時にnginxのバイナリを切り替える必要がありますのでこういう仕組みは助かります。
nginxは他にもconfigurationをscriptの形式で書くことができ、読み込み実行することやmoduleで各リクエストの各段階別にhookingの様に機能を追加することが出来たり、HTTPのウェブサーバーだけではなく一般的なEvent machineとしての活用もできるので今後もいろんなところで活躍すると期待します。