DockerコンテナでWebサイトを構築し、SSL(HTTPS)化する手順をまとめました。
Dockerで構築したWebサイトの構成は以下となります。
CentOS8環境(ConoHaVPS)
- Nginx(https-portal)
- Django
- PostgreSQL
https-portalは、dockerで構築したWebサイトを自動でSSL化(https)にするコンテナで、Nginxと無料のSSL(Let’s Encrypt)が内包されています。
Docker上でNginx+Django+PostgreSQLの構築については以下ページを参考にしてください。
設計手順は以下となります。
「Docker-compose」でNginx+Django+PostgreSQLのWebサイトを無料でSSL化する手順
今回構築するプロジェクトの構造は以下となります。
$ tree . |-- Dockerfile |-- db-data |-- docker-compose.yml |-- manage.py |-- mysite | |-- __init__.py | |-- __pycache__ | | |-- __init__.cpython-38.pyc | | |-- settings.cpython-38.pyc | | |-- urls.cpython-38.pyc | | `-- wsgi.cpython-38.pyc | |-- asgi.py | |-- settings.py | |-- urls.py | `-- wsgi.py |-- requirements.txt `-- ssl_certs |-- account.key |-- dhparam.pem `-- engineers-life.com `-- production |-- chained.crt -> /var/lib/https-portal/example.com/production/signed.crt |-- domain.csr |-- domain.key `-- signed.crt
事前に必要となるファイルやディレクトリは以下となります。
- Dockerfile
- requirements.txt
- docker-compose.yml
各ファイルで内のデータは以下で説明します。
Dockerfile
FROM python:3 ENV PYTHONUNBUFFERED 1 RUN mkdir ./code WORKDIR /code COPY requirements.txt /code/ RUN pip install -r requirements.txt RUN pip install django-sslserver RUN pip install --upgrade pip RUN pip install --upgrade setuptools COPY . /code/
Dockerfileはpythonの最新版Dockerイメージをインストールし、requirements.txt内のイメージをコンテナ上でインストールします。
requirements.txt
Django psycopg2 gunicorn
requirements.txtでコンテナ上でインストールするイメージとなります。
- Django:Webフレームワーク
- psycopg2:PythonからPostgreSQLサーバにアクセスするために必要となるイメージ
- gunicorn:NginxでPython製WebフレームワークでできたWebアプリケーションを起動
docker-compose.yml
docker-compose.ymlで設定する内容は以下となります。
version: '3' services: db: image: postgres ports: - 5432:5432 environment: - POSTGRES_PASSWORD=postgres volumes: - ./db-data:/var/lib/postgresql/data container_name: postgres web: restart: always build: . command: bash -c "python manage.py runserver 0.0.0.0:8000 && python manage.py migrate" volumes: - .:/code ports: - 8000:8000 depends_on: - db container_name: django https-portal: image: steveltn/https-portal:1 ports: - 80:80 - 443:443 links: - web restart: always environment: DOMAINS: 'example.com -> http://web:8000' STAGE: 'production' # Don't use production until staging works #FORCE_RENEW: 'true' container_name: https-portal volumes: - ./ssl_certs:/var/lib/https-portal volumes: db-data: ssl_certs:
上記設定はあくまで検証環境となります。
グローバルに公開する際はポート「8000番」閉じて公開するようお願いします。
service「db」
PostgreSQLのコンテナを構築します。
ポート番号は5432にアクセスします。データベースは永続化するために「db-data」へ共有します。
コンテナ名は「postgres」になります。
service「web」
DjangoWebアプリを起動します。
ポートは8000番でアクセスします。
コンテナはdbコンテナが立ち上がったことを確認した後、コンテナを起動するようにします。
コンテナ名は「django」となります。
service「https-portal」
Nginxを起動し、無料SSL(Let’s Encrypt)を自動で設定します。
80番ポートでアクセスするとリダイレクトで443へアクセスします。
80もしくは443でアクセスした場合、django(web)8000番へリダイレクトします。
「STAGE」では以下の設定をします。
インターネット上では以下の動作をします。
- local:オレオレ証明書 アクセスできない
- staging:オレオレ証明書 アクセスできる
- production:証明書発行 アクセスできる
「#FORCE_RENEW: ‘true’」ここで無効化をしています。
docker-compose up/downを繰り返しているとrenewが実行され、リミットエラーが発生してしまいます。
月に5回ほどこれをしてしまうと、1週間証明書が発行できなくなり、SSL化できなくなってしまいます。
Webサイト構築後に「#」を外すようにしましょう。
これにより、定期的に証明書の更新を自動で実施してくれます。
「ssl_certs」ではコンテナボリュームの証明書と共有化してくれます。
ボリューム名は「ssl_certs」とします。
「docker-compose」でDjangoプロジェクト作成
ファイルの作成が完了したら、次に対象のフォルダ直下で以下コマンドでDjangoプロジェクトを作成します。
$ docker-compose run web django-admin startproject mysite .
※docker-composeコマンドが使えない場合はパッケージがインストールされていないので事前にインストールしておきましょう。
正常に実行されると以下のようにフォルダとファイルが作成されます。
-rw-r--r-- 1 root root 247 May 7 13:45 Dockerfile drwx------ 19 systemd-coredump root 4096 May 8 17:18 db-data -rw-r--r-- 1 root root 951 May 8 17:12 docker-compose.yml -rwxr-xr-x 1 root root 626 May 5 00:48 manage.py drwxr-xr-x 3 root root 4096 May 5 01:05 mysite -rw-r--r-- 1 root root 25 May 5 00:39 requirements.txt drwxr-xr-x 3 root root 4096 May 8 17:16 ssl_certs
DjangoをPostgreSQLデータベースに接続
Djangoのデータベース接続をPostgreSQLに設定します。
プロジェクトディレクトリで、「mysite/settings.py」ファイルを編集します。
vi mysite/settings.py
#ALLOWED_HOSTS = [] ALLOWED_HOSTS = ["*"] #DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } #} DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'postgres', 'USER': 'postgres', 'PASSWORD' : 'postgres', 'HOST' : 'db', 'PORT' : 5432, } }
ALLOWED_HOSTSはWebサイトへのアクセスを全て許可「*」とします。
DATABASESはPostgreSQLへのアクセスへ書き換えます。
データベース設定後、Djangoコンテナで以下コマンドで更新を実施します。
docker-compose run web python manage.py migrate
「docker-compose」でWebサイト起動とSSL自動取得・設定
docker-compose up ------中略------ django | Django version 3.0.6, using settings 'mysite.settings' django | Starting development server at http://0.0.0.0:8000/ django | Quit the server with CONTROL-C.
「docker-compose up」コマンド実行後、上記のように表示された後、少し経つとSSL証明書の発行と設定が自動で実施されます。
正常に動作すると以下のようにサービスが立ち上がり、SSL(https)でのアクセスが可能となります。
https-portal | [services.d] starting services https-portal | [services.d] done.
ブラウザを立ち上げ、該当のWebサーバへアクセスすると以下のように証明書が有効となり、SSL化されていることが確認できます。
証明書も正常に設定されていることが確認できます。
「docker-compose up」後の状態
Dockerコンテナ起動後の状態は以下のようになります。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE django_web latest cbfffd069c7b 47 hours ago 1.02GB python 3 4f7cd4269fa9 10 days ago 934MB postgres latest 0f10374e5170 2 weeks ago 314MB steveltn/https-portal 1 fcf3e18b11ed 8 weeks ago 301MB $
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 26a98361c225 steveltn/https-portal:1 "/init" 17 minutes ago Up 2 seconds 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp https-portal 42d4fc615990 django_web "bash -c 'python man…" 17 minutes ago Up 3 seconds 0.0.0.0:8000->8000/tcp django 8bb99b4ac06c postgres "docker-entrypoint.s…" 17 minutes ago Up 4 seconds 0.0.0.0:5432->5432/tcp postgres $
$ docker volume ls DRIVER VOLUME NAME local django_db-data local django_ssl_certs $
証明書実行時にエラーが発生した場合
上記でも簡単に説明はしましたが、今回dockerコンテナで起動した「https-portal」ですが、NginxとSSLを自動で立ち上げてくれますが、「FORCE_RENEW」を設定すると、何度か、「docker-compose」のUP/DOWNを繰り返すと、リミッターに抵触し、1週間ほど証明書が上手く設定できなくなってしまいます。
https-portal_1 | Response Code: 429 https-portal_1 | Response: {u'status': 429, u'type': u'urn:ietf:params:acme:error:rateLimited', u'detail': u'Error creating new order :: too many certificates already issued for exact set of domains: test.engineers-life.com: see https://letsencrypt.org/docs/rate-limits/'} https-portal_1 | ================================================================================ https-portal_1 | Failed to sign test.engineers-life.com, is DNS set up properly? https-portal_1 | ================================================================================ https-portal_1 | Failed to obtain certs for test.engineers-life.com
調べてみるとLet’s Encrypyでは証明書の更新は週5回までとなっており、それ以上更新することがNGとなっているので、ある程度構築が完了した後、「FORCE_RENEW」を設定し反映するようにしましょう。
もしくは、今回の無料SSLは3ヶ月更新のため、3ヶ月後の更新時に「FORCE_RENEW」を反映し、更新を実施します。
これ、私もハマってしまい、SSL化できなくなってしまいました。
リミットは1週間ほどで外れ、再度更新ができるようなので、ここは気をつけて運用するようにしましょう。
自動SSL化コンテナ「https-portal」の公式サイトは以下となります。
https://github.com/SteveLTN/https-portal#quick-starthttps://github.com/SteveLTN/https-portal#quick-start
以上がNginx+Django+PostgreSQL構成のWebサイトをSSL化する手順となります。
Dockerを構築するならVPSがおすすめです。
エンジニアのオンライン学習
ITエンジニアにおすすめの教材、オンラインスクールです。
無料からエンジニアの学習ができる教材などまとめているので参考にしてください。
おすすめオンライン教材 | |
自宅で学習ができるオンラインスクール | |
ITエンジニアの開発・検証・学習としてインターネット上で専用のサーバ(VPS)を利用しましょう!
実務経験はVPSで学べます。
webのコンテナに対して8000番のポートフォワードを設定する必要はないのではないでしょうか?
せっかくリバースプロキシでHTTPS化してるのにセキュリティホールを作ってしまっているようなものだと思いますが。
(ついでに言えば、dbも直接外部公開するのはあまり良くないと思いますが…)
surface0さん
コメント有難うございます。
おっしゃる通りですね。
こちらのページの設定はあくまで、当時のDjango検証用で構築したサイトのデモとなるので、参考としてお願いします。
DBに関してもWebアプリを構築するうえで、簡易的な設定となります。
実際商用で使用する際は、セキュリティも考慮したうえでの設計をお願いします。
返信ありがとうございます。
ローカル環境で試しに、というのであれば許容するところのなのですが、
VPSという一般公開される環境において、しかも入門記事といえど特にHTTPS導入というセキュリティ向上がテーマでの記事ですから、個人・商用問わず最低限のセキュリティには気を配った説明をお願いしたいと存じます。
発展記事ならば尚のことで「8000番を開けておくのはもう不要です。危険なので閉じましょう」というのが入っていれば説得力があって良いかと思いました。
このような丁寧な説明の記事ほど鵜呑みにしてしまう初学者は多いので、書いてない事についての意図が伝わりづらく難しいところだとは思いますが、なるべくご配慮いただけると幸いです。
surface0さん
ご返信ありがとうございます。
こちら記事に追記いたしました。
ご指摘ありがとうございます。
今後ともよろしくお願いします。
django-sslserverは本番環境には非推奨みたいですね・・・
kさん
コメントありがとうございます。
私は一時期商用として公開していましたよー
ただし、FWなどのセキュリティ対策はしていました。
公式が非推奨と言っているのかは確認できませんでしたが、個人で運用するのがめんどくさいので結果的にWordpressで運用してます。