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の管理画面にログインして画面を遷移させても、強制的にログイン画面へ戻されなくなりました。

所感

今回も初心者ミスでした。そもそもミドルウェアの理解不足が動機的な原因のため、調査に時間がかかります。

また、タイミングにより正常に処理ができたり、できなかったりする場合は、問題の切り分けが難しいです。ただその場合、大抵がアプリケーションではなく、環境が原因のことが多いので、色々とトライして切り分けしていくしかないと痛感しました。

About: ken


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください