Dockerで構築したマルチドメインサイトのDjango2で何度もログインへ戻される
Django管理画面にログインしても、何度もログイン画面へ戻される。
web arenaのVPS上にインストール済みのDockerを使って、新たにDjangoのREST APIを追加したのですが、どうも挙動がおかしい。
Djangoの管理画面にログインして画面を遷移させると、強制的にログイン画面へ戻ってしまう。(ただし、5~10回に1回くらいは画面遷移に成功する。)
また、本番環境では発生するが、開発環境では発生しておらず、原因の特定に困っていました。
調査のため、ブラウザの開発者ツールで、クッキーの情報をみると、開発環境ではsession情報が画面遷移しても消えていないが、本番環境では画面遷移で削除されていた。ただ、画面遷移に成功する場合は、sessionもクッキーに残っている状態でした。
結局、原因はpostgresqlのホスト名が重複しており、既存のサイトのDBへ接続していたことで、上記の事象が発生していました。
変更前のDocker上のNginx/Django/PostgreSQL構成
既存の環境です。
フロント側にNginxでリバースプロキを立て、バックエンドのアプリケーションは、「Nginx/uwsgi・django/postgresql」を1つのdocker-compose.ymlに記載し、管理していました。
Dockerを使ったマルチサイト構築の詳細は、以下のまとめ記事をご覧下さい。
最安VPSでマルチドメインサイト構築(WebArena + Docker + Let’s Encrypt)
既存の環境のdocker-compose.ymlです。最前面のリバースプロキシは、「https://mt.sample.com」がリクエストされたら、「mt」へ連携するようにconfigファイルに記載しています。
version: '3.4' volumes: django.db.volume: name: django.db.volume services: nginx: build: ./nginx tty: true image: mt container_name: mt environment: VIRTUAL_HOST: mt.localhost volumes: - '/etc/letsencrypt:/etc/letsencrypt' - '/var/run/uwsgi:/var/run/uwsgi' - '/static:/static' depends_on: - web db: image: postgres environment: TZ: "Asia/Tokyo" web: # Dockerfile が存在するディレクトリの相対パスを指定する build: . # コンテナ実行時に実行するコマンド command: uwsgi --ini ./app/app.ini # コンテナの /code を、ホストのカレントディレクトリにマウントする volumes: - .:/code - ./static:/static - '/var/run/uwsgi:/var/run/uwsgi' expose: - "8001" depends_on: - db networks: default: external: name: dc_proxy_nw
また、uwsgiの設定ファイルであるapp.iniは以下の通り、mt.sockを介してnginxと通信しています。
[uwsgi] #---------- socket = /var/run/uwsgi/mt.sock #---------- socket = 127.0.0.1:8001 #---------- chmod-socket = 666 module = app.wsgi wsgi-file = app/wsgi.py logto = uwsgi.log py-autoreload = 1 master = true processes = 4 threads = 2
変更後のDocker上のNginx/Django/PostgreSQL構成
今回は、上記のdocker-compose.ymlとapp.iniをコピーして、新規環境を作成しました。
その際、WEBサーバのNginxのコンテナ名をmt→fxに変更しています。またNginxとuwsgiのソケット通信で利用するファイルも、mt.sock→fx.sockに修正しました。
この修正で疎通はとれたのですが、 Djangoの管理画面にログインして画面を遷移させると、強制的にログイン画面へ戻ってしまう。(ただし、5~10回に1回くらいは画面遷移に成功する。)
DjangoにSSL HTTPSの設定を確認
もともと、マルチドメインで利用を考えていたため、前面にリバースプロキをたて、リクエストされたURL(サブドメイン)毎に接続するアプリケーションを切り替える方式にしてます。
また、リバースプロキのNginxはlet’s encryptでSSL化していました。
環境の差異としては、本番環境がSSL化しているので、そこが原因かと考え、対応を調べました。Django側でhttpsの設定はsetting.pyになります。
以下、公式サイトから前文を抜粋
セキュリティの観点から、サイトを HTTPS の下でデプロイするのは常に良い選択です。HTTPS がなければ、悪意のあるネットワークユーザーが、クライアント・サーバー間で通信される認証情報やその他あらゆる情報を盗み見たり、場合によっては、積極的な ネットワーク攻撃者がどちらの方向でもデータ書き換えが可能になってしまいます。 HTTPS による保護をサーバー上で有効にするためには、多少の追加作業が必要になります:
setting.pyの設定個所は、以下となります。ただし、今回はどれを変更しても症状は変わりませんでした。
- SECURE_PROXY_SSL_HEADERをセット
- SECURE_SSL_REDIRECT を True にセット
- SESSION_COOKIE_SECURE をTrue にセット
- CSRF_COOKIE_SECURE を True にセット
- HTTP Strict Transport Security (HSTS) を使用
PostgreSQLのdjango_sessionテーブルを確認
そもそも、なぜ画面遷移がうまくいくときと、失敗するときがあるのかを明確にするために、成功時と失敗時に、sessionテーブルの状態がどうなっているかを確認することにしました。
まず画面でログインに成功した状態にして、sessionテーブルの情報を確認したのですが、なぜか0件。
# DBコンテナへ入る docker exec -it fx-xxxxxx_db_1 /bin/bash # postgresqlへ接続 psql -U postgres # セッションテーブルの状態を確認 postgres=# select * from django_session; session_key | session_data | expire_date -------------+--------------+------------- (0 rows)
そこで、ハッとし、既存のアプリケーション側を見に行くと、そちらのsessionテーブルにセッション情報が格納されているではありませんか。
# DBコンテナへ入る docker exec -it mt-xxxxxx_db_1 /bin/bash # postgresqlへ接続 psql -U postgres # セッションテーブルの状態を確認 postgres=# select * from django_session; session_key |session_data| expire_date ----------------------------------+------------+------------------------------- 53k279ef4x0leuyfxiur70295rkxfam5 | ZWE....... | 2019-07-21 00:57:39.009463+09 (1 row)
原因は、DBホスト名が重複したことで、Djangoが誤ったテーブルを参照してしまっていました。
原因がわかりました。
新規に追加したアプリケーションが利用するpostgreSQLも、ホスト名を「db」とし、 docker-compose.ymlに記載したため、名前が重複し、Djangoが誤ったDBを見に行っていたのです。
docker-compose.yml とsetting.pyを修正
修正箇所は2つです。
docker-compose.ymlのDBホスト名をユニークにします。このとき、depends_onで「db」としている場合は、忘れずに変更します。
fxdb: #dbからfxdbへ変更 image: postgres environment: TZ: "Asia/Tokyo"
depends_on: - fxdb #dbからfxdbへ変更
また、setting.pyのDB接続情報を変更します。
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'postgres', 'USER': 'postgres', 'HOST': 'fxdb', 'PORT': 5432, } }
上記の対応で、 Djangoの管理画面にログインして画面を遷移させても、強制的にログイン画面へ戻されなくなりました。
所感
今回も初心者ミスでした。そもそもミドルウェアの理解不足が動機的な原因のため、調査に時間がかかります。
また、タイミングにより正常に処理ができたり、できなかったりする場合は、問題の切り分けが難しいです。ただその場合、大抵がアプリケーションではなく、環境が原因のことが多いので、色々とトライして切り分けしていくしかないと痛感しました。