Edited at

ホットデプロイを実現する2つの方法


ホットデプロイとはなにか

Webアプリケーションをデプロイするとき、単純にサーバ上のソースコード1を更新して再起動すればデプロイ自体はできます。しかし、この方法だと、処理中のリクエストが途中で終了してしまったり、再起動をする間はリクエストに応答することができなくなったりしてしまいます。24時間常にユーザーからのリクエストがやってくるようなサービスでは、このようなデプロイ方法はユーザーにとって不便です。これを回避するために、デプロイ中もサービスを止めずに運用したいという気持ちになります。

このような、「デプロイ中もサービスを停止しないデプロイ」のことをホットデプロイといいます。


ホットデプロイを実現する2つの方法

ホットデプロイを実現する方法は大きく分けて2つあります。


ロードバランサを用いる方法

システムがロードバランサを使っている場合、以下の手順でホットデプロイを実現できます。


  1. デプロイ対象のWebサーバを適当に選ぶ(Aとする)

  2. ロードバランサの設定を変えて、Aにはリクエストが行かないようにする

  3. Aを停止して、ソースコードを差し替えてアプリケーションを再起動する

  4. ロードバランサの設定を変えて、Aにもリクエストが行くようにする

  5. すべてのWebサーバがデプロイ済みになるまで1~4を繰り返す

3でサーバを停止していますが、リクエストはすべて他のサーバに流れているため、ユーザーには影響が出ません。

これによってホットデプロイが実現されます。

ロードバランサを用いたホットデプロイのやり方は他にもあります2が、デプロイ対象のサーバをクラスタから外してデプロイし、デプロイが終わったらクラスタに入れるという点では共通しています。


プロセスのforkを利用する方法

RackのWebアプリケーションサーバであるUnicornを例に説明します。とはいえ大抵のWebアプリケーションサーバは似たような機能を持っていると思います(多分)345

Unicornでは、ホットデプロイは以下の手順で実現できます。


  1. アプリケーションのソースコードを差し替える

  2. UnicornのmasterプロセスにUSR2シグナルを送る6

  3. UnicornのmasterプロセスにQUITシグナルを送る7

この操作をしたときのUnicornの挙動について詳しく説明します。

まず、稼働中のUnicornのプロセスツリーは以下のようになっています。つまり、一つのmasterに複数のworkerが子としてぶら下がっている状態です。

$ pstree

\- 22000 unicorn master
\- 22001 unicorn worker
\- 22002 unicorn worker

これらのプロセスは1つのソケットを共有しており8、workerはソケットをlistenしています(masterはlistenしない)。

ここで、masterプロセスにUSR2シグナルを送るとmasterがforkし、新しいmasterとworkerが子プロセスとして生成されます。

$ kill -s USR2 22000 # masterにUSR2シグナルを送信

$ pstree
\- 22000 unicorn master (old)
\- 22001 unicorn worker (old)
\- 22002 unicorn worker (old)
\- 23000 unicorn master
\- 23001 unicorn worker
\- 23002 unicorn worker

これら6つのプロセスは1つのソケットを共有しており、4つのworkerがソケットをlistenしています9

新しいmaster/workerは、更新されたあとのソースコードを基に起動されます。したがってこの状態では、更新前と更新後の両方のworkerがソケットをlistenしていることになります10

そして、古い方のmasterにQUITシグナルを送ると、古いmasterとworkerが停止します。

$ kill -s QUIT 22000 # 古いmasterのPID

$ pstree
\- 23000 unicorn master # => 新しいmasterとworkerだけが残る
\- 23001 unicorn worker
\- 23002 unicorn worker

古いworkerが停止するときは、自分が処理中のすべてのリクエストの処理が終わってから停止します。また、古いworkerが全部停止しても新しいworkerが生きているので、リクエストは途切れることなく処理されます。これによって、ホットデプロイが実現されます。

参考:


まとめ

ホットデプロイの手法について、ロードバランサを使う方法とプロセスのforkを利用する方法を解説しました。

細かく見るともっと多くの手法があるようですが、いずれも本記事で解説したいずれかの方法を応用したものです(知っている範囲では)11

これら2つの手法を理解しておけば、他のホットデプロイ手法についても容易に理解できると思います。





  1. あるいは、コンパイル言語でWeb開発をしている場合はそのバイナリ。 



  2. この記事で解説した方法は「ローリングデプロイ」です。その他の手法についてはこの記事に記載があります。  



  3. pumaにも似たような機能がありそう。 https://github.com/puma/puma/blob/master/docs/restart.md 



  4. Webアプリケーションサーバにこの機能を持たせるのではなく、独立したスーパーバイザを使うケースもある模様(というかその方が一般的?)。やってることは多分同じ。 https://github.com/lestrrat-go/server-starter 



  5. 説明のためにWINCHについては触れていません。詳しく知りたい方は https://bogomips.org/unicorn/SIGNALS.html を読んでください。 



  6. kill -s USR2 $(cat tmp/unicorn.pid) https://bogomips.org/unicorn/SIGNALS.html 



  7. kill -s QUIT $(cat tmp/unicorn.pid.oldbin) https://bogomips.org/unicorn/SIGNALS.html 



  8. 起動時にまずmasterがソケットをオープンし、その後forkしてworkerを起動しているのでソケットが共有される模様。 



  9. forkなので親子間でファイルディスクリプタが共有される。 



  10. つまり、ユーザーがリクエストを送ったときに、古いレスポンスと新しいレスポンスのどちらが返ってくるかはわからない。 



  11. もしどちらとも異なる手法があったら教えて下さい。