search
LoginSignup
1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Heroku Advent Calendar 2020 Day 6

posted at

updated at

もう少しSIGTERMと仲良くする

本稿はHeroku Advent Calendar 2020の6日目の記事です。5日目はsho7650さんによる「[Heroku] index.html をダイレクトに開く静的コンテンツサイトを、すぐ公開したい」でした。buildpackは奥が深いよ!! 7日目はkimihomさんによる「Heroku Addons で SendGrid を使ってる方が必要な年内対応」です。SendGridをご利用の方はお早めに!

Herokuではdynoの停止時にすべてのプロセスにSIGTERMを送り、メインプロセスの停止を待ってからdynoを停止させます(graceful shutdown)。ここでメインプロセスがシェルの場合には、SIGTERMの送信直後にシェルが停止するためdynoが停止されてしまいます。本稿ではシェルがメインプロセスの場合にもSIGTERMを無視するようにして、graceful shutdownするようにしてみます。

Dynoでのプロセスの起動

Dynoの起動後にps -xfコマンドを実行してプロセスツリーを確認してみると、Herokuではbashをログインシェルとして起動しProcfileの内容を実行させることで、スタートアップスクリプトの読み込みもおこなっているようです。

bash --login -c "puma -C config/puma.rb"

Bashによるプロセスの実行

多くのHerokuアプリでは、プラットフォームが起動したbashはProcfileで指定されたコマンドをexecして自プロセスと置き換えます。Dynoの停止時にプラットフォームから全てのプロセスに送られたSIGTERMはProcfileで指定されたプロセスが直接受け取ることになります。Pumaなど多くのサーバはSIGTERMを受け取るとgraceful shutdownするようになっています。

プロセスツリーにはProcfileで指定されたコマンドの親プロセスは見られません。

puma -C config/puma.rb

Bashがコマンドをexecできない場合には、指定されたコマンドをforkして子プロセスとして実行します。プラットフォームから送られたSIGTERMはbashにも届き、bashは停止し、アプリのプロセスが稼動を続けていてもdynoは停止されてしまいます。

Bashのソースコードのコメントによれば、スタートアップスクリプトにtrapがあった場合にはbashはコマンドをexecするのではなくforkするようです。この場合プロセスツリーにはProcfileで指定されたコマンドの親プロセスとしてbashが見られます。

bash --login -c "puma -C config/puma.rb"
 \_ puma -C config/puma.rb

Bashは起動するコマンドが複文だった場合にもコマンドをforkするようです。

bash --login -c "puma -C config/puma.rb & sidekiq &"
 \_ puma -C config/puma.rb
 \_ sidekiq

Forkするアプリでもgraceful shutdownしてもらう

メインプロセスがforkされるアプリでもgraceful shutdownさせるには、SIGTERMを受け取ってもbashが停止しないように、SIGTERMを無視させると良さそうです。Procfileの最初に下記を実行します。

trap '' SIGTERM

ついでに、複数稼動しているプロセスのいずれかが停止してしまった場合に、他のプロセスにもSIGTERMを送り、graceful shutdownしてもらいます。いずれかのバックグラウンドプロセスが停止するまでwait -nで待ち、killコマンドですべてのバックグラウンドプロセスにSIGTERMを送り、そして、すべてのプロセスが停止するまでwaitで待ちます:

wait -n; kill -SIGTERM -$$: wait

これらを併せて、pumaとsidekiqをバックグラウンドで起動しているアプリケーションでsidekiqがちゃんとshutdownするようになりました。

web: trap '' SIGTERM; puma -C config/puma.rb & sidekiq & wait -n; kill -SIGTERM -$$; wait

アプリケーションログを確認すると、trapの追加前にはbashがシグナルを受けて停止していた(exit statusが143だった)のに対して:

2020-10-27T18:28:53.674073+00:00 heroku web.1 - - Restarting
2020-10-27T18:28:53.676489+00:00 heroku web.1 - - State changed from up to starting
2020-10-27T18:28:54.965022+00:00 heroku web.1 - - Stopping all processes with SIGTERM
2020-10-27T18:28:55.263424+00:00 heroku web.1 - - Process exited with status 143

trapの追加後にはそれぞれのバックグラウンドプロセスが停止してからbashが停止する様子が確認できます:

2020-11-04T19:25:17.574465+00:00 heroku[web.1]: Restarting
2020-11-04T19:25:17.587549+00:00 heroku[web.1]: State changed from up to starting
2020-11-04T19:25:18.621855+00:00 heroku[web.1]: Stopping all processes with SIGTERM
2020-11-04T19:25:18.660761+00:00 app[web.1]: [7] - Gracefully shutting down workers...
2020-11-04T19:25:18.660678+00:00 app[web.1]: pid=8 tid=1hsg DEBUG: Got TERM signal
2020-11-04T19:25:18.660736+00:00 app[web.1]: pid=8 tid=1hsg INFO: Shutting down
2020-11-04T19:25:18.660788+00:00 app[web.1]: pid=8 tid=1hsg INFO: Terminating quiet workers
2020-11-04T19:25:18.661283+00:00 app[web.1]: pid=8 tid=3fw8 INFO: Scheduler exiting...
2020-11-04T19:25:19.164122+00:00 app[web.1]: pid=8 tid=1hsg INFO: Bye!
2020-11-04T19:25:19.272989+00:00 heroku[web.1]: Process exited with status 0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1
Help us understand the problem. What are the problem?