日記

日々のことと、Python/Django/PHP/Laravel/nodejs などソフトウェア開発のことを書き綴ります

uWSGi で Django Channels を動かす

はじめに

ちょっとしたお仕事で Django Channels を uWSGI 上で動かそうとしたら、色々と uWSGI 以外のプロセスも起動していく必要がありました。

ぐぐっても 開発環境や使い方の説明はあるものの、デプロイ時の How to があまり見つからなかったので備忘的に残しておきます。

Django Channels とは?

Django Channels の使い方を説明する記事では無いので、使い方を知りたいひとは、次のページをどうぞ。

Python の WebSocket ライブラリを選定してて、このページに来ちゃった人は次のページも参考にどうぞ。(スケールしないよ、と言う話)

何が問題か?

前述のページを見ると runserver で普通に動いてるじゃん?と思われますが、本番環境用に uWSGI を使うと uWSGI 上で起動した Django アプリだけでは WebSocket 通信が行えません。

Channel Layer と Interface Server は Django の組み込み Web サーバ上でのみロードされ、機能するように作られるためです。 uWSGI を使うときは当然、 manage.py runserver なんて叩かないし、エントリーポイントは wsgi ファイルですよね。

どうやって解決するか

uWSGI と一緒に別のプロセスで Channel Layer と Interface Server が起動してあげればよいです。

Django Channels を利用したアプリが既にあったとしてもいくつか注意が必要です。

Channel Layer の変更

Channel Layer のバックエンドに In-memory を使っていると別のプロセスで動作する uWSGI と連動できなくなります。Redis バックエンドに変更しましょう。

$ pip install asgi-redis \
                    msgpack-python \
                    redis

そして、Django Settings の設定を変更します。

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "asgi_redis.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("redis://127.0.0.1:6379/0")],
        },
        "ROUTING": "websocket.routing.channel_routing"
    },
}

Django Session に Redis を使っている場合は DB 番号に気をつけてください。

asgi.py ファイルの準備

Channel Layer のサーバプロセスに読み込ませる asgi.py ファイルを用意します。

import os
from channels.asgi import get_channel_layer

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")

channel_layer = get_channel_layer()

Channel Layer を起動する時に Django Settings ファイルも環境に合わせたものを読むので注意してください。

Channel Layer (daphne) と Interface Server (manage.py runworker)

さあ、いよいよ起動です。Channel Layer として daphne と、Interface Server として manage.py runworker の2つのプロセスを起動します。

自分の管理サーバでは Ubuntu 16.04 になって、uWSGI も systemd にぶら下げましたが、へたれなので supervisor を導入します。( supervisor 導入手順は省略して、設定のみ晒します。)

[supervisord]
minfds = 4096

[program:workerserver]
command=/usr/local/bin/python /home/user/git/your-app/manage.py runworker -v1 --pythonpath /home/user/git/your-app --settings app.procution
user=user
autorestart=true
stdout_logfile=/var/log/supervisor/workerserver.log
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=10
redirect_stderr=true

[program:daphne]
command={{ virtualenv_path }}/bin/daphne --root-path /home/user/git/your-app app.asgi:channel_layer
directory=/home/user/git/your-app
environment=DJANGO_SETTINGS_MODULE="app.production"
user=user
autorestart=true
stdout_logfile=/var/log/supervisor/daphne.log
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=10
redirect_stderr=true

workerserver と daphne として supervisor にぶら下げてみました。

まとめ

振り返りになりますが、何が必要か上げていくと次のようになります。

  • Channel Layer を in-memory から Redis に変更する
  • Channel Layer (daphne) のサーバプロセスが使う asgi.py ファイルを用意する
  • 本番環境で Redis を起動しておく
  • supervisor を導入する(オプション、systemd でがんばっても良い)
  • supervisor に Channel Layer の daphne と Interface Server の manage.py runworker をぶら下げる

以上のことで、uWSGI で Django Channels 導入まで行けるはず。

Django Channels は SwampDragon や Tornado は開発時に不要で楽という触れ込みだけど、本番環境では uWSGI とは別のプロセスを起動する羽目になり、何かモヤモヤする結果となりました。でも、ちゃんと動くからご安心を!