djangoをdockerコンテナで利用(1) – SSL利用

◆◆◆

きっかけ

そもそも、phpで自前のwebアプリを作って帳簿を管理している。
後学のためでもあるし、確定申告のためでもある。

webアプリはlarabelとかじゃなくて、ベタのphp。
動かし始めて7年、毎月帳簿をつけ年末になるとせっせと申告準備してる。

phpは賞味期限が長くないので、バージョンを上げていく頻度はけっこう短い。
jpeg/pdf保管にimagickを使っているので、この扱いがけっこう面倒。
グラフ表示もやってるし。

そこで、機能移行が全部できるか調べながらだけど、phpからdjangoに引っ越すことに。
既存DBの帳簿データや、グラフ表示を1円も誤差なく表示させられるのか?
イントラでssl接続も実現させた。

そこでやってみた。

https化したdjango環境を作る

あっちこっちで書かれているdjangoのロケット表示画面とadmin画面を自分でもdockerコンテナ使って作成してみた。

ロケット表示画面はSSLで提供できないかって考えてたら、やっておられる方がおられたので参考にさせてもらった。
(自分にとってはとてもいいヒントと問題提起になった)

ただし、admin画面はcsrf検証エラーになってしまうので、そこだけはdjangoのコンテナ直つなぎ。

もう少し勉強して解決できたらいいな。

※別の日にこの文章作るためにコンテナ作り直したら、admin画面のcsrf検証エラーがなくなった。その代わりdjangoコンテナに直接つなぐとcsrf検証エラーが発生するようになった。目的は達成できたけど、エラーになる原因は??? まだわからん。

【参考URL】

[Docker]Djangoを無料でHTTPS化して簡単にデプロイする方法
こんばんは、エンジニアの眠れない夜です。 Djangoを使っているエンジニアに朗報です! このdocker-c…
GitHub - SteveLTN/https-portal: A fully automated HTTPS server powered by Nginx, Let's Encrypt and Docker.
A fully automated HTTPS server powered by Nginx, Let's Encrypt and Docker. - GitHub - SteveLTN/https-portal: A fully aut...
uWSGI入門 | Python学習講座
Django + uWSGI + nginx (uWSGIチュートリアルの和訳) - Qiita
はじめにSetting up Django and your web server with uWSGI and nginx — uWSGI 2.0 documentationを適宜和訳しつつ、D…

dockerでビルドしてコンテナ動かすときは、自分の環境にフィットさせるために考えてdocker-compose.ymlとDockerfileを書き換える必要がある。
これがけっこう考えさせられるし、いい勉強になる。

あれこれ試しているときに、記事の内容で「docker-compose down」なんてそのまま動かすと、
自分の消したくないコンテナまで消えて「あー、手がスベった」ってなることもあった。

たまたまoracleのコンテナ動かしてたときは、docker-composeでdownを指定しちゃうと「あーsql動かしなおしかー」みたいなことになって時間がないときツラい。

以下、自分のdocker-compose.ymlの記述の一部。

djangoはデータベース利用に制限があるらしく、単一カラムに主キーがついたテーブルでないと扱えないらしい。

既存mariadbが自分のデータベースとして別で存在するけど、ちゃんと扱えるようになるまで別コンテナのmysqlを1つ用意しておく。

  sv_https-portal:
    image: steveltn/https-portal:1
    ports:
      - '30080:80'
      - '30443:443'
    environment:
      DOMAINS: 'nafslinux.intra.gavann-it.com -> http://svdjango:8080'
      STAGE: 'local' # or 'production'
    volumes:
      - ./nariDockerDat/sv_django-ssl_certs:/var/lib/https-portal

  sv_django:
    image: sv_django:3.1.5
    build: ./nariDockerDat/sv_django-uwsgi-nginx
    hostname: svdjango
    volumes:
      - ./nariDockerDat/sv_django-uwsgi-nginx/app:/code/app
    ports:
      - '38080:8080'

  sv_django-DBServer:
      image: mysql:5.7
      hostname: 'svdjangoDBServer'
      command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
      ports:
        - "23306:3306"
      environment:
        MYSQL_ROOT_PASSWORD: hogehoge
        MYSQL_DATABASE: djangodb
        MYSQL_USER: gvis
        MYSQL_PASSWORD: mogemoge
        TZ: 'Asia/Tokyo'
      volumes:
        - ./nariDockerDat/sv_django-db/lib:/var/lib/mysql
        - ./nariDockerDat/sv_django-db/etc:/etc/mysql
        - ./nariDockerDat/sv_django-dbconf:/docker-entrypoint-initdb.d
  :(中略)

フォルダの準備

/dockerをベースディレクトリにしている。
sambaで丸ごと共有し、windows/mac/linuxのvscodeから編集できるようにしてある。

中にnariDockerSysとnariDockerDatのフォルダがある。

/docker/
|--nariDockerSys
|--nariDockerDat

nariDockerSysはコンテナやイメージが格納されるフォルダで肥大化しがち。
dockerのプロセス起動オプション(data-root)で指定してる。

ExecStart=/usr/bin/dockerd --containerd=/run/containerd/containerd.sock --data-root /docker/nariDockerSys

こうしておくとルートパーティションが肥大化して苦しまない。
/dockerを別パーティションとして600GBほど確保しておき、そこで動かす。
いっぱいになりかけたらpruneするけど、それでもいっぱいになることもある。

謎のディスク消費があるときは、tar.gzで丸ごと逃がしておき、/dockerをvmのボリュームとして作り直して配置しなおす。コンテナ作り直すわけじゃない。

コンテナの永続化領域のためのフォルダはnariDockerDat。
mysqlはdjango専用の練習領域なので、まずは普通に作る。
コンテナ作り直すときは、sv_django-dbとsv_django-ssl_certsのフォルダ内容は空にしておく。

$ cd /docker/nariDockerDat/
$ mkdir sv_django-db
$ mkdir sv_django-dbconf
$ mkdir sv_django-ssl_certs
$ mkdir sv_django-uwsgi-nginx
$ cat ./sv_django-dbconf/init.sql
GRANT ALL PRIVILEGES ON FYblogdb.* TO 'gvis'@'%';
FLUSH PRIVILEGES;
$

結局こんな感じになる。
既存DBのmariadbをdjangoからも読ませる。
gvisは自分用のシステム名で使っている4文字。

☆印が既存のもので、今回作ったのが★印箇所。

★印箇所は、人様が作った材料をgitから拝借し、内容書き換えながら作った。

/docker/
|--nariDockerSys              ☆既存のコンテナとかイメージ用の領域
|--nariDockerDat
|  |--sv_django-db            ★django管理用DB
|  |--sv_django-db-dbconf
|  |  |--init.sql
|  |--sv_django-ssl_certs     ★ssl接続用
|  |--sv_django-uwsgi-nginx   ★django用
|  |  |--__init__.py
|  |  |--__pycache__
  :(中略)
|--sv_mariadb                 ☆自分用の既存DB(最初はmysql5.6だったが今はmariadb10.5.7)
|--sv_mariadbconf
|  |--gvis.cnf
|--sv_web                     ☆自分用の既存web(最初は5だったが今はphp7.4.7)
  :(中略)

djangoのadmin画面で使うデータベースを作り直す場合

sv_django-DBserverの中にある永続化領域はsudoして削除しておく。

# cd /docker/nariDockerDat/sv_django-db
# rm -fR etc lib

管理画面はデータベースのレコードを更新もできるらしい。
a5sqlとかあるから、そういうのはあまり必要としない。

管理画面からは直接操作できなさそうだけど、django用の管理テーブルがある。
migrationをするときに使われているみたい。
挙動がおかしくなったら作り直すかもしれない。

django用のフォルダ内容を用意

おおもとはgitから拝借。appのwebsiteフォルダは、以前にssl化せず普通にmigrateして自動作成したものをもってきた。アーカイブから戻して用意。

docker/nariDockerDat/sv_django-uwsgi-nginx
|--Dockerfile
|--app
|  |--manage.py
|  |--requirements.txt
|  |--website
|  |  |--__init__.py
|  |  |--__pycache__
  :(中略)
|  |  |--asgi.py
|  |  |--settings.py
|  |  |--static
|  |  |  |--admin
|  |  |  |  |--css
  :(中略)
|  |  |--urls.py
|  |  |--wsgi.py
|--nginx-app.conf
|--requirements.txt
|--supervisor-app.conf
|--uwsgi.ini
|--uwsgi_params

settings.pyの確認

djangoで自動作成されるフォルダにあるsettings.pyが今の自分の環境にマッチしていることを確認。
デフォルトがsqliteを使うらしく、別にそのままでもいいんだけど、mysql接続の練習したかったので書き換えた。

from pathlib import Path
# mysqlを使用
import pymysql

docker-compose.ymlに書いたサイトを許可設定に入れる。この記述を入れてないとブラウザで開いたときにエラーになる。

ALLOWED_HOSTS = ['nafslinux.intra.gavann-it.com']

csrf対策ってのがあるらしくて、admin画面を開くとどうしてもエラーになってしまう。
CsrfViewMiddlewareを記述から外そうかと何度か考えたけど、せっかく最初から書いてある推奨内容だからいったんそのまま。
ローカルLANの中で開発練習するけど、SSL使ったサイトにしてみたい。

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

この名前がプロジェクトのフォルダ名と一致していることを確認。

ROOT_URLCONF = 'website.urls'
WSGI_APPLICATION = 'website.wsgi.application'

新設のmysqlに接続するための記述を確認。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'djangodb',
        'USER': 'root',
        'PASSWORD': 'hogehogehoge',
        'HOST': 'nafslinux.intra.gavann-it.com',
        'PORT': '23306',
        'OPTIONS': {
                'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
                },
    }
}

djangoの言語とタイムゾーン設定を確認。

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = True

admin画面を表示させるときにcssを有効にさせる設定を確認。

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "website/static")

すぐには解決できなかったけど、admin画面をssl経由で表示させるための表示設定を最後に入れた。

ついでにmanage.pyでcheck処理動かしたときの対策推奨メッセージに対する設定も入れた。

# csrf検証失敗の対策
#
# 参考URL 
# https://ikura-lab.hatenablog.com/entry/2019/06/07/232236
# https://www.ytyng.com/blog/archive/2016/9/
#
# /usr/bin/python3 manage.py check --deploy
#

CSRF_TRUSTED_ORIGINS = ['nafslinux.intra.gavann-it.com:30443']
SESSION_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 60
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_PRELOAD = True

新設DBのコンテナを起動させておく

docker psでコンテナ名を確認してからbashでmysqlのコンテナに入りDBが動いていることを確認

$ cd /docker
$ docker-compose up -d sv_django-DBServer
$ docker ps | grep DB
f7530c88c5ab   mysql:5.7   "docker-entrypoint.s…"   6 days ago   Up 11 minutes   33060/tcp, 0.0.0.0:23306->3306/tcp, :::23306->3306/tcp   docker_sv_django-DBServer_1
$
$ docker exec -it docker_sv_django-DBServer_1 bash
root@svdjangoDBServer:/# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.35 MySQL Community Server (GPL)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> \q
Bye
root@svdjangoDBServer:/# exit
exit
$

a5aqlでも接続確認しておく。
「show variables」でmysqlのinnnodbパラメータが見れてる。
django-SSL

Djangoのコンテナ作成

sv_djangoのコンテナを作る。

sv_https-portalとsv_djangoのコンテナ&イメージを削除してからビルド実行。
mysqlのコンテナは先に起動しておき、django起動したときログにエラーがないことを確認したらctrl+cでいったん停止。

$ cd /docker/nariDockerDat/sv_django-uwsgi-nginx
$ docker build -f Dockerfile -t sv_django:3.1.5 .
Sending build context to Docker daemon  1.691MB
Step 1/24 : FROM ubuntu:20.04
---> 7e0aa2d69a15
Step 2/24 : MAINTAINER Dockerfiles
---> Running in d12da68bc256
Removing intermediate container d12da68bc256
---> 0309b8139a90
Step 3/24 : RUN DEBIAN_FRONTEND=noninteractive
---> Running in 86b1061e5289
Removing intermediate container 86b1061e5289
---> e528b8f5387f
Step 4/24 : RUN apt-get update && apt-get install -y tzdata
  :(中略)
Step 23/24 : EXPOSE 8080
---> Running in 1143815544d0
Removing intermediate container 1143815544d0
---> 081ac5864234
Step 24/24 : CMD ["supervisord", "-n"]
---> Running in b845ca3d3188
Removing intermediate container b845ca3d3188
---> 946b6bc0131a
Successfully built 946b6bc0131a
Successfully tagged sv_django:3.1.5
$
$ docker-compose up sv_django
Creating docker_sv_django_1 ... done
Attaching to docker_sv_django_1
sv_django_1           | 2021-08-22 05:33:01,652 CRIT Supervisor is running as root.  Privileges were not dropped because no user is specified in the config file.  If you intend to run as root, you can set user=root in the config file to avoid this message.
sv_django_1           | 2021-08-22 05:33:01,652 INFO Included extra file "/etc/supervisor/conf.d/supervisor-app.conf" during parsing
sv_django_1           | 2021-08-22 05:33:01,660 INFO RPC interface 'supervisor' initialized
sv_django_1           | 2021-08-22 05:33:01,660 CRIT Server 'unix_http_server' running without any HTTP authentication checking
sv_django_1           | 2021-08-22 05:33:01,660 INFO supervisord started with pid 1
sv_django_1           | 2021-08-22 05:33:02,663 INFO spawned: 'app-uwsgi' with pid 10
sv_django_1           | 2021-08-22 05:33:02,666 INFO spawned: 'nginx-app' with pid 11
sv_django_1           | 2021-08-22 05:33:03,973 INFO success: app-uwsgi entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
sv_django_1           | 2021-08-22 05:33:03,973 INFO success: nginx-app entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)

SSL利用のためのコンテナ作成

イントラ作業のメインイベント。
SSL化やってみる。

djangoコンテナの起動が確認できたら、sv_https-portalのコンテナを作るために起動しておく。

$ cd /docker
$ docker-compose up -d sv_django
Starting docker_sv_django_1 ... done
$

sv_https-portalのコンテナを作り、起動できることをログで確認したらいったんctrl+cで停止する。

$ cd /docker
$ docker-compose up sv_https-portal
Pulling sv_https-portal (steveltn/https-portal:1)...
1: Pulling from steveltn/https-portal
45b42c59be33: Pull complete

    :(中略)

4253af2c8ad9: Pull complete
Digest: sha256:102bf41b771756d591d0615ad47828c94e131d53eeea0a85c072db86498b6faf
Status: Downloaded newer image for steveltn/https-portal:1
Creating docker_sv_https-portal_1 ... done
Attaching to docker_sv_https-portal_1
sv_https-portal_1     | [s6-init] making user provided files available at /var/run/s6/etc...exited 0.
sv_https-portal_1     | [s6-init] ensuring user provided files have correct perms...exited 0.
sv_https-portal_1     | [fix-attrs.d] applying ownership & permissions fixes...
sv_https-portal_1     | [fix-attrs.d] done.
sv_https-portal_1     | [cont-init.d] executing container initialization scripts...
sv_https-portal_1     | [cont-init.d] 00-welcome: executing...
sv_https-portal_1     |
sv_https-portal_1     | ========================================
sv_https-portal_1     | HTTPS-PORTAL v##19.0
sv_https-portal_1     | ========================================
sv_https-portal_1     |
sv_https-portal_1     | [cont-init.d] 00-welcome: exited 0.
sv_https-portal_1     | [cont-init.d] 20-setup: executing...
sv_https-portal_1     | DH parameters appear to be ok.

    :(中略)

sv_https-portal_1     | RSA key ok
sv_https-portal_1     | Signing skipped for nafslinux.intra.gavann-it.com, it expires at 36500 days from now.
sv_https-portal_1     | [cont-init.d] 20-setup: exited 0.
sv_https-portal_1     | [cont-init.d] 30-set-docker-gen-status: executing...
sv_https-portal_1     | [cont-init.d] 30-set-docker-gen-status: exited 0.
sv_https-portal_1     | [cont-init.d] done.
sv_https-portal_1     | [services.d] starting services
sv_https-portal_1     | [services.d] done.

djangoのアプリを準備

djangoのコンテナにbashで入って、手動でアプリを準備。管理テーブルがmysql内に作成される。

$ docker exec -it docker_sv_django_1 bash
root@svdjango:/# cd /code/app
root@svdjango:/code/app# /usr/bin/python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
root@svdjango:/code/app#/usr/bin/python3 manage.py migrate

djangoからmysqlにadmin画面で使うユーザを作成。

root@svdjango:/code/app# /usr/bin/python3 manage.py createsuperuser
ユーザー名 (leave blank to use 'root'):
メールアドレス: hogehoge@gavann-it.com
Password:
Password (again):
Superuser created successfully.
root@svdjango:/code/app#

ソース編集のために

コンテナの中はrootでdjangoの処理が動く。
できれば何とかしてroot以外で動かせたらいいんだけど、今はいったんそのまま。

コンテナの外に出て永続化領域のフォルダに移動し、pythonのソースを編集できるように所有権を変更しておく。こうするとsambaで共有している永続化領域をvscodeで編集できるようになる。

# cd /docker/nariDockerDat/sv_django-uwsgi-nginx/app
# chown -R nari:nari ./website

コンテナを起動する

$ cd /docker
$ docker-compose up -d sv_django
$ docker-compose up sv_https-portal

サイトへ接続してみる

コンテンツはdjangoの初期状態だが、自己証明書でサーバが応答していることを確認する。

ローカルLAN内のサイトへSSL接続すると、オレオレ証明書なので「潜在的な・・・」って脅しが表示される。「詳細情報」から「危険を承知で続行」を選ぶ。

django-SSL

すると、djangoのロケット画面が表示され、SSL応答できている。
django-SSL

次にdjangoの管理画面を開いてみる。前はこの画面でログインしたときcsrf検証エラーが表示されてた。

django-SSL
django-SSL

今はSSLなしのdjangoコンテナにあるadmin画面へブラウザ接続すると、「CSRF検証に失敗したため、リクエストは中断されました」ってエラーが表示される。なぜだろう。csrf対策の理解が足りないか。

【追記】
結局django4へアップグレード後はこんな具合にsettigns.pyに書いている。
csrf検証失敗の際の対策を書いてくださってる方がおられた。
作者さんありがとう。

# csrf検証失敗の対策
#
# 参考URL 
# https://ikura-lab.hatenablog.com/entry/2019/06/07/232236
# https://www.ytyng.com/blog/archive/2016/9/
#
# /usr/bin/python3 manage.py check --deploy
#

# django4からホスト名だけの記述は許されず「http/https」をつけることになった
# 参考URL
# https://docs.djangoproject.com/ja/4.0/releases/4.0/
CSRF_TRUSTED_ORIGINS = [
    'https://nafslinux.intra.gavann-it.com:30443',
    'http://nafslinux.intra.gavann-it.com:38080'
]
SESSION_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 60
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_PRELOAD = True

この画面は最初はSSL接続の際に表示されてて、SSLなしの画面では表示されなかった。エラー画面にあるリンク先の英文を読みながら、csrfのことを調べてsettings.pyに記述を追加したらSSL画面では表示されなくなったけど、SSLなし画面で表示されるようになった。

django-SSL

ここまで来たらdjangoのアプリケーションがすっからかんの状態ができてるので、sv_django-uwsgi-nginxを自前のgitlabに登録してソース管理開始。

前に業務でrailsの処理をメンテナンスさせてもらったとき、脆弱性検査で同じようなエラーにひっかかったことあったなぁ。SSLでは表示されないからまぁ今はいいか。

次はアプリケーション作れるよう勉強開始。

タイトルとURLをコピーしました