読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

GunicornのUNIXドメインソケットが喪失するバグへの対応

エンジニアの原です。

弊社内のいくつかのPythonを使用したWebサービスでは、アプリケーションサーバーとしてGunicorn を使用しています。

先日サービス全体には大きな影響がなかったものの、そのGunicornのバグを踏み、一部のサービスが不安定になったことがあったのでその事例紹介をします。

環境

  • Python 3.4.2
  • Gunicorn 19.6.0
  • Nginx 1.12.0

現象

特に負荷のかかる時間帯にもかかわらず、任意のWebサーバーのnginxからクライアントに502レスポンスが返されていました。 nginxのログ的に、Gunicornがリクエストハンドリング用に生成しているUNIXドメインソケットが喪失したようです。

nginxのupstreamとして稼働しているGunicornのログには、以下のようなスタックトレースが出力されていました。*1

level:ERROR      time:2017-02-14 00:00:00 (JST)        process:100001     name:gunicorn.error   message:Exception in worker process
Traceback (most recent call last):
  File "/var/www/example/venv/default/lib/python3.4/site-packages/gunicorn/arbiter.py", line 557, in spawn_worker
    worker.init_process()
  File "/var/www/example/venv/default/lib/python3.4/site-packages/gunicorn/workers/gthread.py", line 109, in init_process
    super(ThreadWorker, self).init_process()
  File "/var/www/example/venv/default/lib/python3.4/site-packages/gunicorn/workers/base.py", line 132, in init_process
    self.run()
  File "/var/www/example/venv/default/lib/python3.4/site-packages/gunicorn/workers/gthread.py", line 240, in run
    s.close()
  File "/var/www/example/venv/default/lib/python3.4/site-packages/gunicorn/sock.py", line 123, in close
    os.unlink(self.cfg_addr)
FileNotFoundError: [Errno 2] No such file or directory: '/var/run/sockets/gunicorn.sock'

CPU稼働率、メモリ使用率等のシステムリソースには特異点はありませんでした。

原因

同様の問題に関するissueがありました。

github.com

issue内のコメントでも言及されていますが、原因は19.6.0で入ったこの変更だと思われます。

github.com

該当箇所は以下です。

-    def close(self, locked=False):
-        if self.parent == os.getpid() and not locked:
-            os.unlink(self.cfg_addr)
+    def close(self):
+        os.unlink(self.cfg_addr)
+        super(UnixSocket, self).close()

本来ならば、UNIXドメインソケットはarbiterプロセスのみによってファイルunlinkされるはずですが、workerプロセスによってunlinkされるようになっていました。
このcloseメソッド内ではプロセスIDからarbiterプロセスであるかの判定が必要なのですが、その判定が当該コミットで削除されています。
どうやらcloseメソッドを呼ぶプロセス自体がarbiterプロセスのみになるため削除したらしいのですが、19.6.0ではまだworkerプロセスでcloseメソッドが呼ばれてしまっているようです。

対策

この現象は、GunicornをUNIXドメインソケットでバインドさせた際に発生する問題なので、 UNIXドメインソケットではなくループバックアドレスに未使用ポートでGunicornをlistenさせることで解決しました。

- bind = 'unix:/var/run/sockets/gunicorn.sock'
+ bind = '127.0.0.1:8080'

なお、このバグは2017/3/4にリリースされたGunicorn v19.7.0で修正されています。

github.com

closeメソッド自体が削除され、別の場所でunlinkされるようになったようです。
バージョンアップしたいところでしたが、内部の実装が大きく変わっていたため、緊急性と安定性を鑑みてバージョンアップは見送りました。

まとめ

UNIXドメインソケットを使用する場合は、v19.6.0以外のバージョンを使用するようにしましょう。
非常にニッチなテーマで恐縮ですが、誰かの助けになれば幸いです。

*1:ログ日時、ファイルパスなどは実際のものとは異なります