Docker、Nginx、Let’sEncryptを使用してDjangoアプリケーションをスケーリングおよび保護する方法
序章
クラウドベースの環境では、Djangoアプリケーションをスケーリングして保護する方法は複数あります。 水平方向にスケーリングし、アプリのコピーを複数実行することで、フォールトトレラントで可用性の高いシステムを構築できると同時に、スループットを増やして、リクエストを同時に処理できるようにします。 。 Djangoアプリを水平方向にスケーリングする1つの方法は、DjangoアプリケーションとそのWSGI HTTPサーバー(GunicornやuWSGIなど)を実行する追加のアプリサーバーをプロビジョニングすることです。 この一連のアプリサーバー間で着信リクエストをルーティングおよび分散するには、ロードバランサーおよびNginxのようなリバースプロキシを使用できます。 Nginxは、静的コンテンツをキャッシュし、トランスポート層セキュリティ(TLS)接続を終了することもできます。これは、HTTPSとアプリへの安全な接続を提供するために使用されます。
Docker container 内でDjangoアプリケーションとNginxプロキシを実行すると、これらのコンポーネントがデプロイされている環境に関係なく同じように動作することが保証されます。 さらに、コンテナーは、アプリケーションのパッケージ化と構成を容易にする多くの機能を提供します。
このチュートリアルでは、コンテナ化されたDjangoおよびGunicorn Polls アプリケーションを、それぞれDjangoおよびGunicornアプリコンテナのコピーを実行する2つのアプリケーションサーバーをプロビジョニングすることにより、水平方向にスケーリングします。
また、Nginxリバースプロキシコンテナと Certbot クライアントコンテナを実行する3番目のプロキシサーバーをプロビジョニングして構成することで、HTTPSを有効にします。 Certbotは、 Let’sEncrypt認証局からNginxのTLS証明書をプロビジョニングします。 これにより、サイトが SSLLabsから高いセキュリティ評価を受けることが保証されます。 このプロキシサーバーは、アプリのすべての外部リクエストを受信し、2つのアップストリームDjangoアプリケーションサーバーの前に配置されます。 最後に、プロキシサーバーのみへの外部アクセスを制限することにより、この分散システムを強化します。
前提条件
このチュートリアルに従うには、次のものが必要です。
-
3つのUbuntu18.04サーバー:
- 2台のサーバーはアプリケーションサーバーになり、DjangoおよびGunicornアプリの実行に使用されます。
- 1つのサーバーはプロキシサーバーであり、NginxとCertbotの実行に使用されます。
- すべてのユーザーには、root以外のユーザーが必要です。
sudo
特権、およびアクティブなファイアウォール。 これらの設定方法のガイダンスについては、この初期サーバー設定ガイドを参照してください。
-
Dockerは3つのサーバーすべてにインストールされています。 Dockerのインストールに関するガイダンスについては、 Ubuntu18.04にDockerをインストールして使用する方法のステップ1と2に従ってください。
-
登録されたドメイン名。 このチュートリアルでは、
your_domain.com
全体を通して。 Freenom で無料で入手するか、選択したドメインレジストラを使用できます。 -
アン
A
DNSレコードyour_domain.com
プロキシサーバーのパブリックIPアドレスを指します。 DigitalOceanアカウントに追加する方法の詳細については、このDigitalOcean DNSの概要をフォローしてください(使用している場合)。 -
DigitalOcean Space などのS3オブジェクトストレージバケット。Djangoプロジェクトの静的ファイルと、このスペースのアクセスキーのセットを保存します。 スペースの作成方法については、スペースの作成方法の製品ドキュメントを参照してください。 スペースのアクセスキーを作成する方法については、アクセスキーを使用したスペースへのアクセスの共有を参照してください。 マイナーな変更により、django-storagesプラグインがサポートする任意のオブジェクトストレージサービスを使用できます。
-
DjangoアプリのPostgreSQLサーバーインスタンス、データベース、およびユーザー。 小さな変更を加えるだけで、Djangoがサポートするのデータベースを使用できます。
- PostgreSQLデータベースはpolls(または以下の構成ファイルに入力する別の覚えやすい名前)と呼ばれる必要があり、このチュートリアルではデータベースユーザーの名前はsammyになります。 これらを作成するためのガイダンスについては、Dockerを使用してDjangoおよびGunicornアプリケーションを構築する方法のステップ1に従ってください。 これらの手順は、3つのサーバーのいずれからでも実行できます。
- このチュートリアルでは、DigitalOceanマネージドPostgreSQLクラスターを使用します。 クラスタの作成方法については、DigitalOcean管理対象データベースの製品ドキュメントを参照してください。
- 独自のPostgreSQLインスタンスをインストールして実行することもできます。 UbuntuサーバーにPostgreSQLをインストールして管理するためのガイダンスについては、 Ubuntu18.04にPostgreSQLをインストールして使用する方法を参照してください。
ステップ1—最初のDjangoアプリケーションサーバーを構成する
まず、Djangoアプリケーションリポジトリを最初のアプリサーバーに複製します。 次に、アプリケーションのDockerイメージを構成してビルドし、Djangoコンテナーを実行してアプリケーションをテストします。
注: Dockerを使用してDjangoおよびGunicornアプリケーションを構築する方法から続行する場合は、ステップ1を完了しているので、ステップ2に進むことができます。 ] secondアプリサーバーを構成します。
2つのDjangoアプリケーションサーバーの最初のサーバーにログインし、 git
クローンを作成するには polls-docker
Django Tutorial Polls AppGitHubリポジトリのブランチ。 このリポジトリには、Djangoドキュメントのサンプルポーリングアプリケーションのコードが含まれています。 The polls-docker
ブランチには、Docker化されたバージョンのPollsアプリが含まれています。 コンテナ化された環境で効果的に機能するようにPollsアプリがどのように変更されたかについては、Dockerを使用してDjangoおよびGunicornアプリケーションを構築する方法を参照してください。
- git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git
に移動します django-polls
ディレクトリ:
cd django-polls
このディレクトリには、DjangoアプリケーションのPythonコードが含まれています。 Dockerfile
Dockerがコンテナイメージの構築に使用するものと、 env
コンテナの実行環境に渡される環境変数のリストを含むファイル。 検査する Dockerfile
を使用して cat
:
cat Dockerfile
OutputFROM python:3.7.4-alpine3.10
ADD django-polls/requirements.txt /app/requirements.txt
RUN set -ex \
&& apk add --no-cache --virtual .build-deps postgresql-dev build-base \
&& python -m venv /env \
&& /env/bin/pip install --upgrade pip \
&& /env/bin/pip install --no-cache-dir -r /app/requirements.txt \
&& runDeps="$(scanelf --needed --nobanner --recursive /env \
| awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
| sort -u \
| xargs -r apk info --installed \
| sort -u)" \
&& apk add --virtual rundeps $runDeps \
&& apk del .build-deps
ADD django-polls /app
WORKDIR /app
ENV VIRTUAL_ENV /env
ENV PATH /env/bin:$PATH
EXPOSE 8000
CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi"]
このDockerfileは、公式のPython 3.7.4 Docker image をベースとして使用し、DjangoとGunicornのPythonパッケージ要件をインストールします。 django-polls/requirements.txt
ファイル。 次に、不要なビルドファイルをいくつか削除し、アプリケーションコードをイメージにコピーして、実行を設定します。 PATH
. 最後に、そのポートを宣言します 8000
着信コンテナ接続を受け入れるために使用され、実行されます gunicorn
3人の労働者と一緒に、港で聞いています 8000
.
このDockerfileの各ステップの詳細については、Dockerを使用してDjangoおよびGunicornアプリケーションを構築する方法のステップ6を参照してください。
次に、を使用してイメージを作成します docker build
:
- docker build -t polls .
画像に名前を付けます polls
を使用して -t
フラグを立てて、現在のディレクトリをビルドコンテキストとして渡します。これは、イメージを構築するときに参照するファイルのセットです。
Dockerがイメージをビルドしてタグ付けした後、使用可能なイメージをリストします。 docker images
:
docker images
あなたは見るべきです polls
リストされている画像:
OutputREPOSITORY TAG IMAGE ID CREATED SIZE
polls latest 80ec4f33aae1 2 weeks ago 197MB
python 3.7.4-alpine3.10 f309434dea3a 8 months ago 98.7MB
Djangoコンテナーを実行する前に、を使用してその実行環境を構成する必要があります。 env
現在のディレクトリに存在するファイル。 このファイルはに渡されます docker run
コンテナーの実行に使用されるコマンド。Dockerは、構成された環境変数をコンテナーの実行環境に挿入します。
を開きます env
とファイル nano
またはお気に入りの編集者:
nano env
このようにファイルを構成します。以下に概説するように、いくつかの値を追加する必要があります。
DJANGO_SECRET_KEY=
DEBUG=True
DJANGO_ALLOWED_HOSTS=
DATABASE_ENGINE=postgresql_psycopg2
DATABASE_NAME=polls
DATABASE_USERNAME=
DATABASE_PASSWORD=
DATABASE_HOST=
DATABASE_PORT=
STATIC_ACCESS_KEY_ID=
STATIC_SECRET_KEY=
STATIC_BUCKET_NAME=
STATIC_ENDPOINT_URL=
DJANGO_LOGLEVEL=info
次のキーの不足している値を入力します。
DJANGO_SECRET_KEY
: Django docs で詳しく説明されているように、これを一意の予測できない値に設定します。 このキーを生成する1つの方法は、 Scalable DjangoAppチュートリアルのAppSettingsの調整にあります。DJANGO_ALLOWED_HOSTS
:この変数はアプリを保護し、HTTPホストヘッダー攻撃を防ぎます。 テスト目的で、これをに設定します*
、すべてのホストに一致するワイルドカード。 本番環境では、これを次のように設定する必要がありますyour_domain.com
. このDjango設定の詳細については、Djangoドキュメントのコア設定を参照してください。DATABASE_USERNAME
:前提条件の手順で作成したPostgreSQLデータベースユーザーに設定します。DATABASE_NAME
:これをに設定しますpolls
または、前提条件の手順で作成されたPostgreSQLデータベースの名前。DATABASE_PASSWORD
:前提条件の手順で作成したPostgreSQLユーザーパスワードに設定します。DATABASE_HOST
:これをデータベースのホスト名に設定します。DATABASE_PORT
:これをデータベースのポートに設定します。STATIC_ACCESS_KEY_ID
:これをS3バケットまたはSpaceのアクセスキーに設定します。STATIC_SECRET_KEY
:これをS3バケットまたはSpaceのアクセスキーシークレットに設定します。STATIC_BUCKET_NAME
:これをS3バケットまたはスペース名に設定します。STATIC_ENDPOINT_URL
:これを適切なS3バケットまたはスペースエンドポイントURLに設定します(例:https://space-name.nyc3.digitaloceanspaces.com
スペースがにある場合nyc3
領域。
編集が終了したら、ファイルを保存して閉じます。
これから使用します docker run
をオーバーライドするには CMD
Dockerfileに設定し、を使用してデータベーススキーマを作成します manage.py makemigrations
と manage.py migrate
コマンド:
docker run --env-file env polls sh -c "python manage.py makemigrations && python manage.py migrate"
実行します polls:latest
コンテナイメージ、変更したばかりの環境変数ファイルを渡し、Dockerfileコマンドを次のようにオーバーライドします sh -c "python manage.py makemigrations && python manage.py migrate"
、アプリコードで定義されたデータベーススキーマを作成します。 これを初めて実行する場合は、次のように表示されます。
OutputNo changes detected
Operations to perform:
Apply all migrations: admin, auth, contenttypes, polls, 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 polls.0001_initial... OK
Applying sessions.0001_initial... OK
これは、データベーススキーマが正常に作成されたことを示します。
実行している場合 migrate
その後、データベーススキーマが変更されない限り、Djangoはno-opを実行します。
次に、アプリコンテナーの別のインスタンスを実行し、その中のインタラクティブシェルを使用して、Djangoプロジェクトの管理ユーザーを作成します。
docker run -i -t --env-file env polls sh
これにより、実行中のコンテナ内にシェルプロンプトが表示され、Djangoユーザーの作成に使用できます。
python manage.py createsuperuser
ユーザーのユーザー名、メールアドレス、パスワードを入力し、ユーザーを作成したら、 CTRL+D
コンテナを終了して強制終了します。
最後に、アプリの静的ファイルを生成し、を使用してDigitalOceanSpaceにアップロードします。 collectstatic
. これが完了するまでに少し時間がかかる場合があることに注意してください。
docker run --env-file env polls sh -c "python manage.py collectstatic --noinput"
これらのファイルが生成およびアップロードされると、次の出力が表示されます。
Output121 static files copied.
これでアプリを実行できます。
docker run --env-file env -p 80:8000 polls
Output[2019-10-17 21:23:36 +0000] [1] [INFO] Starting gunicorn 19.9.0
[2019-10-17 21:23:36 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
[2019-10-17 21:23:36 +0000] [1] [INFO] Using worker: sync
[2019-10-17 21:23:36 +0000] [7] [INFO] Booting worker with pid: 7
[2019-10-17 21:23:36 +0000] [8] [INFO] Booting worker with pid: 8
[2019-10-17 21:23:36 +0000] [9] [INFO] Booting worker with pid: 9
ここでは、Dockerfileで定義されているデフォルトのコマンドを実行します。 gunicorn --bind :8000 --workers 3 mysite.wsgi:application
、およびコンテナポートを公開します 8000
そのポート 80
Ubuntuサーバーでポートにマップされます 8000
の polls
容器。
これで、に移動できるようになります。 polls
次のように入力して、Webブラウザを使用するアプリ http://APP_SERVER_1_IP
URLバーにあります。 ルートが定義されていないため、 /
パス、あなたはおそらく受け取るでしょう 404 Page Not Found
予想されるエラー。
警告: DockerでUFWファイアウォールを使用する場合、Dockerは、この GitHubの問題に記載されているように、構成されたUFWファイアウォールルールをバイパスします。 これはあなたがポートにアクセスできる理由を説明しています 80
前提条件のステップでUFWアクセスルールを明示的に作成していなくても、サーバーの ステップ5では、UFW構成にパッチを適用することにより、このセキュリティホールに対処します。 UFWを使用しておらず、DigitalOceanのクラウドファイアウォールを使用している場合は、この警告を無視しても問題ありません。
案内する http://APP_SERVER_1_IP/polls
投票アプリのインターフェースを表示するには:
管理インターフェースを表示するには、次のWebサイトにアクセスしてください。 http://APP_SERVER_1_IP/admin
. Pollsアプリの管理者認証ウィンドウが表示されます。
で作成した管理ユーザー名とパスワードを入力します createsuperuser
指図。
認証後、Pollsアプリの管理インターフェースにアクセスできます。
の静的アセットに注意してください admin
と polls
アプリはオブジェクトストレージから直接配信されています。 これを確認するには、テストスペースの静的ファイル配信を参照してください。
探索が終了したら、 CTRL+C
Dockerコンテナを実行しているターミナルウィンドウで、コンテナを強制終了します。
アプリコンテナが期待どおりに実行されることを確認したので、 detached モードで実行できます。これにより、バックグラウンドで実行され、SSHセッションからログアウトできるようになります。
docker run -d --rm --name polls --env-file env -p 80:8000 polls
The -d
フラグは、コンテナをデタッチモードで実行するようにDockerに指示します。 -rm
フラグは、コンテナが終了した後にコンテナのファイルシステムをクリーンアップし、コンテナに名前を付けます polls
.
最初のDjangoアプリサーバーからログアウトし、に移動します http://APP_SERVER_1_IP/polls
コンテナが期待どおりに実行されていることを確認します。
これで、最初のDjangoアプリサーバーが稼働しているので、2番目のDjangoアプリサーバーをセットアップできます。
ステップ2—2番目のDjangoアプリケーションサーバーを構成する
このサーバーをセットアップするためのコマンドの多くは前のステップのコマンドと同じであるため、ここでは省略形で示します。 このステップの特定のコマンドの詳細については、ステップ1を確認してください。
secondDjangoアプリケーションサーバーにログインすることから始めます。
クローンを作成する polls-docker
のブランチ django-polls
GitHubリポジトリ:
- git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git
に移動します django-polls
ディレクトリ:
cd django-polls
を使用してイメージを構築します docker build
:
- docker build -t polls .
を開きます env
とファイル nano
またはお気に入りの編集者:
nano env
DJANGO_SECRET_KEY=
DEBUG=True
DJANGO_ALLOWED_HOSTS=
DATABASE_ENGINE=postgresql_psycopg2
DATABASE_NAME=polls
DATABASE_USERNAME=
DATABASE_PASSWORD=
DATABASE_HOST=
DATABASE_PORT=
STATIC_ACCESS_KEY_ID=
STATIC_SECRET_KEY=
STATIC_BUCKET_NAME=
STATIC_ENDPOINT_URL=
DJANGO_LOGLEVEL=info
ステップ1のように、不足している値を入力します。 編集が終了したら、ファイルを保存して閉じます。
最後に、アプリコンテナをデタッチモードで実行します。
docker run -d --rm --name polls --env-file env -p 80:8000 polls
案内する http://APP_SERVER_2_IP/polls
コンテナが期待どおりに実行されていることを確認します。 実行中のコンテナを終了せずに、2番目のアプリサーバーから安全にログアウトできます。
両方のDjangoアプリコンテナーが稼働している状態で、Nginxリバースプロキシコンテナーの構成に進むことができます。
ステップ3—NginxDockerコンテナを構成する
Nginx は、リバースプロキシ、負荷分散、キャッシュなどの多くの機能を提供する多用途のWebサーバーです。 このチュートリアルでは、Djangoの静的アセットをオブジェクトストレージにオフロードしたため、Nginxのキャッシュ機能は使用しません。 ただし、2つのバックエンドDjangoアプリサーバーへのリバースプロキシとしてNginxを使用し、それらの間で着信リクエストを分散します。 さらに、NginxはTLSターミネーションとCertbotによってプロビジョニングされたTLS証明書を使用したリダイレクトを実行します。 これは、クライアントにHTTPSの使用を強制し、着信HTTP要求をポート443にリダイレクトすることを意味します。 次に、HTTPSリクエストを復号化し、アップストリームのDjangoサーバーにプロキシします。
このチュートリアルでは、Nginxコンテナーをバックエンドサーバーから切り離すように設計を決定しました。 ユースケースに応じて、Djangoアプリサーバーの1つでNginxコンテナを実行し、リクエストをローカルでプロキシするか、他のDjangoサーバーにプロキシするかを選択できます。 別の可能なアーキテクチャは、クラウドロードバランサーを前面に配置して、各バックエンドサーバーに1つずつ、2つのNginxコンテナーを実行することです。 アーキテクチャごとに異なるセキュリティとパフォーマンスの利点があり、システムを負荷テストしてボトルネックを発見する必要があります。 このチュートリアルで説明する柔軟なアーキテクチャにより、バックエンドのDjangoアプリレイヤーとNginxプロキシレイヤーの両方をスケーリングできます。 単一のNginxコンテナがボトルネックになったら、複数のNginxプロキシにスケールアウトして、クラウドロードバランサーまたはHAProxyなどの高速L4ロードバランサーを追加できます。
両方のDjangoアプリサーバーが稼働している状態で、Nginxプロキシサーバーのセットアップを開始できます。 プロキシサーバーにログインし、というディレクトリを作成します conf
:
mkdir conf
と呼ばれる構成ファイルを作成します nginx.conf
を使用して nano
またはお気に入りの編集者:
nano conf/nginx.conf
次のNginx構成で貼り付けます。
upstream django {
server APP_SERVER_1_IP;
server APP_SERVER_2_IP;
}
server {
listen 80 default_server;
return 444;
}
server {
listen 80;
listen [::]:80;
server_name your_domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your_domain.com;
# SSL
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
client_max_body_size 4G;
keepalive_timeout 5;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://django;
}
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
}
}
これらは upstream
, server
、 と location
ブロックは、HTTPリクエストをHTTPSにリダイレクトし、手順1と2で構成された2つのDjangoアプリサーバー間で負荷分散するようにNginxを構成します。 Nginx構成ファイルの構造の詳細については、Nginx構成ファイルの構造と構成コンテキストの理解に関するこの記事を参照してください。 さらに、Nginxサーバーとロケーションブロック選択アルゴリズムの理解に関するこの記事が役立つ場合があります。
この構成は、 Gunicorn 、 Cerbot 、および Nginx によって提供されるサンプル構成ファイルから組み立てられ、このアーキテクチャを稼働させるための最小限のNginx構成として意図されています。 このNginx構成の調整はこの記事の範囲を超えていますが、 NGINXConfig などのツールを使用して、アーキテクチャー用のパフォーマンスの高い安全なNginx構成ファイルを生成できます。
The upstream
ブロックは、リクエストをプロキシするために使用されるサーバーのグループを定義します。 proxy_pass
指令:
upstream django {
server APP_SERVER_1_IP;
server APP_SERVER_2_IP;
}
. . .
このブロックでは、アップストリームに名前を付けます django
両方のDjangoアプリサーバーのIPアドレスを含めます。 アプリサーバーがDigitalOceanで実行されており、VPCネットワーキングが有効になっている場合は、ここでプライベートIPアドレスを使用する必要があります。 DigitalOceanでVPCネットワーキングを有効にする方法については、既存のドロップレットでVPCネットワーキングを有効にする方法を参照してください。
最初 server
ブロックは、ドメインと一致しないリクエストをキャプチャし、接続を終了します。 たとえば、サーバーのIPアドレスへの直接HTTPリクエストは、次のブロックによって処理されます。
. . .
server {
listen 80 default_server;
return 444;
}
. . .
次 server
ブロックは、 HTTP 301リダイレクトを使用して、ドメインへのHTTPリクエストをHTTPSにリダイレクトします。 これらのリクエストは、ファイナルによって処理されます server
ブロック:
. . .
server {
listen 80;
listen [::]:80;
server_name your_domain.com;
return 301 https://$server_name$request_uri;
}
. . .
これらの2つのディレクティブは、TLS証明書と秘密鍵へのパスを定義します。 これらはCertbotを使用してプロビジョニングされ、次のステップでNginxコンテナにマウントされます。
. . .
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
. . .
これらのパラメータは、Certbotが推奨するSSLセキュリティのデフォルトです。 それらの詳細については、Nginxドキュメントの Modulengx_http_ssl_moduleを参照してください。 Mozillaのセキュリティ/サーバーサイドTLSは、SSL構成を調整するために使用できるもう1つの役立つガイドです。
. . .
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
. . .
GunicornのサンプルNginx構成からのこれら2つのディレクティブは、クライアントリクエスト本文の最大許容サイズを設定し、クライアントとのキープアライブ接続のタイムアウトを割り当てます。 Nginxはその後クライアントとの接続を閉じます keepalive_timeout
秒。
. . .
client_max_body_size 4G;
keepalive_timeout 5;
. . .
最初 location
ブロックはNginxにリクエストをプロキシするように指示します upstream django
HTTP経由のサーバー。 さらに、発信元IPアドレス、接続に使用されるプロトコル、およびターゲットホストをキャプチャするクライアントHTTPヘッダーを保持します。
. . .
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://django;
}
. . .
これらのディレクティブの詳細については、Nginxドキュメントの DeployingGunicornおよびModulengx_http_proxy_moduleを参照してください。
最終 location
ブロックは、へのリクエストをキャプチャします /well-known/acme-challenge/
パス。CertbotがHTTP-01チャレンジで使用し、Let’s Encryptを使用してドメインを検証し、TLS証明書をプロビジョニングまたは更新します。 Certbotが使用するHTTP-01チャレンジの詳細については、Let’sEncryptのドキュメントのチャレンジタイプを参照してください。
. . .
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
}
編集が終了したら、ファイルを保存して閉じます。
これで、この構成ファイルを使用してNginxDockerコンテナーを実行できます。 このチュートリアルでは、 nginx:1.19.0
画像、バージョン 1.19.0
Nginxによって維持されている公式Dockerイメージの。
コンテナを初めて実行すると、構成ファイルで定義された証明書をまだプロビジョニングしていないため、Nginxはエラーをスローして失敗します。 ただし、コマンドを実行してNginxイメージをローカルにダウンロードし、他のすべてが正しく機能していることをテストします。
docker run --rm --name nginx -p 80:80 -p 443:443 \
-v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
-v /var/www/html:/var/www/html \
nginx:1.19.0
ここでは、コンテナに名前を付けます nginx
ホストポートをマッピングします 80
と 443
それぞれのコンテナポートに。 The -v
flagは、構成ファイルをNginxコンテナにマウントします。 /etc/nginx/conf.d/nginx.conf
、Nginxイメージがロードするように事前構成されています。 に取り付けられています ro
または「読み取り専用」モードであるため、コンテナはファイルを変更できません。 Webルートディレクトリ /var/www/html
コンテナにも取り付けられています。 ついに nginx:1.19.0
Dockerにプルして実行するように指示します nginx:1.19.0
Dockerhubからの画像。
Dockerはイメージをプルして実行し、構成されたTLS証明書と秘密鍵が見つからない場合はNginxがエラーをスローします。 次のステップでは、DockerizedCertbotクライアントとLet’sEncrypt認証局を使用してこれらをプロビジョニングします。
ステップ4—Certbotの構成と証明書の更新を暗号化しましょう
Certbot は、 Electronic FrontierFoundationによって開発されたLet’sEncryptクライアントです。 Let’s Encrypt 認証局から無料のTLS証明書をプロビジョニングし、ブラウザがWebサーバーのIDを確認できるようにします。 NginxプロキシサーバーにDockerがインストールされている場合、CertbotDockerイメージを使用してTLS証明書をプロビジョニングおよび更新します。
DNSがあることを確認することから始めます A
プロキシサーバーのパブリックIPアドレスにマップされたレコード。 次に、プロキシサーバーで、を使用して証明書のステージングバージョンをプロビジョニングします。 certbot
Dockerイメージ:
docker run -it --rm -p 80:80 --name certbot \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
certbot/certbot certonly --standalone --staging -d your_domain.com
このコマンドは、 certbot
インタラクティブモードのDockerイメージ、およびポートの転送 80
ホストからコンテナへのポート 80
. 2つのホストディレクトリを作成してコンテナにマウントします。 /etc/letsencrypt/
と /var/lib/letsencrypt/
. certbot
で実行されます standalone
モード、Nginxなし、Let’sEncryptを使用します staging
ドメイン検証を実行するサーバー。
プロンプトが表示されたら、メールアドレスを入力し、利用規約に同意します。 ドメインの検証が成功すると、次の出力が表示されます。
OutputObtaining a new certificate
Performing the following challenges:
http-01 challenge for stubb.dev
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/your_domain.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/your_domain.com/privkey.pem
Your cert will expire on 2020-09-15. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
を使用して証明書を検査できます cat
:
sudo cat /etc/letsencrypt/live/your_domain.com/fullchain.pem
TLS証明書をプロビジョニングすると、前の手順でアセンブルされたNginx構成をテストできます。
docker run --rm --name nginx -p 80:80 -p 443:443 \
-v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/lib/letsencrypt:/var/lib/letsencrypt \
-v /var/www/html:/var/www/html \
nginx:1.19.0
これは、ステップ3 で実行されたのと同じコマンドですが、最近作成された両方のLet’sEncryptディレクトリが追加されています。
Nginxが起動して実行されたら、次の場所に移動します http://your_domain.com
. 認証局が無効であるという警告がブラウザに表示される場合があります。 これは、本番のLet’s Encrypt証明書ではなく、ステージング証明書をプロビジョニングしたためです。 ブラウザのURLバーをチェックして、HTTPリクエストがHTTPSにリダイレクトされたことを確認します。
打つ CTRL+C
ターミナルでNginxを終了し、 certbot
再びクライアント、今回は省略 --staging
国旗:
docker run -it --rm -p 80:80 --name certbot \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
certbot/certbot certonly --standalone -d your_domain.com
既存の証明書を保持するか、更新して置き換えるように求められたら、 2
それを更新してから ENTER
選択を確認します。
本番TLS証明書をプロビジョニングしたら、Nginxサーバーをもう一度実行します。
docker run --rm --name nginx -p 80:80 -p 443:443 \
-v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/lib/letsencrypt:/var/lib/letsencrypt \
-v /var/www/html:/var/www/html \
nginx:1.19.0
ブラウザで、に移動します http://your_domain.com
. URLバーで、HTTPリクエストがHTTPSにリダイレクトされていることを確認します。 Pollsアプリにデフォルトルートが設定されていない場合、Djangoページが見つかりませんエラーが表示されます。 案内する https://your_domain.com/polls
標準のPollsアプリインターフェースが表示されます。
この時点で、Certbot Dockerクライアントを使用して本番TLS証明書をプロビジョニングし、2つのDjangoアプリサーバーへの外部リクエストのリバースプロキシと負荷分散を行っています。
Let’sEncryptの証明書は90日ごとに期限切れになります。 証明書の有効性を維持するには、スケジュールされた有効期限が切れる前に定期的に証明書を更新する必要があります。 Nginxを実行している場合は、Certbotクライアントを使用する必要があります webroot
代わりにモード standalone
モード。 これは、Certbotがファイルを作成することによって検証を実行することを意味します /var/www/html/.well-known/acme-challenge/
ディレクトリ、およびこのパスへのLet’sEncrypt検証要求はによってキャプチャされます location
ステップ3のNginx構成で定義されたルール。 その後、Certbotは証明書をローテーションし、この新しくプロビジョニングされた証明書を使用するようにNginxをリロードできます。
この手順を自動化する方法は複数あり、TLS証明書の自動更新はこのチュートリアルの範囲を超えています。 を使用した同様のプロセスの場合 cron
スケジューリングユーティリティについては、 Nginx、Let’s Encrypt、およびDockerComposeを使用してコンテナ化されたNode.jsアプリケーションを保護する方法のステップ6を参照してください。
ターミナルで、 CTRL+C
Nginxコンテナを強制終了します。 を追加して、デタッチモードで再度実行します -d
国旗:
docker run --rm --name nginx -d -p 80:80 -p 443:443 \
-v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/lib/letsencrypt:/var/lib/letsencrypt \
-v /var/www/html:/var/www/html \
nginx:1.19.0
Nginxをバックグラウンドで実行している状態で、次のコマンドを使用して、証明書の更新手順のドライランを実行します。
docker run -it --rm --name certbot \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
-v "/var/www/html:/var/www/html" \
certbot/certbot renew --webroot -w /var/www/html --dry-run
を使用します --webroot
プラグイン、Webルートパスを指定し、 --dry-run
フラグを立てて、証明書の更新を実際に実行せずにすべてが正しく機能していることを確認します。
更新シミュレーションが成功すると、次の出力が表示されます。
OutputCert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator webroot, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for your_domain.com
Using the webroot path /var/www/html for all unmatched domains.
Waiting for verification...
Cleaning up challenges
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/your_domain.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
** (The test certificates below have not been saved.)
Congratulations, all renewals succeeded. The following certs have been renewed:
/etc/letsencrypt/live/your_domain.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
** (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
本番環境では、証明書を更新した後、変更を有効にするためにNginxをリロードする必要があります。 Nginxをリロードするには、次のコマンドを実行します。
docker kill -s HUP nginx
このコマンドは、 HUPUnixシグナルを内部で実行されているNginxプロセスに送信します。 nginx
Dockerコンテナ。 このシグナルを受信すると、Nginxは構成と更新された証明書をリロードします。
HTTPSが有効になっていて、このアーキテクチャのすべてのコンポーネントが稼働している場合、最後のステップは、2つのバックエンドアプリサーバーへの外部アクセスを防止することにより、セットアップをロックダウンすることです。 すべてのHTTPリクエストはNginxプロキシを経由する必要があります。
ステップ5—DjangoAppサーバーへの外部アクセスを防止する
このチュートリアルで説明するアーキテクチャでは、SSLターミネーションはNginxプロキシで発生します。 これは、NginxがSSL接続を復号化し、パケットが暗号化されていない状態でDjangoアプリサーバーにプロキシされることを意味します。 多くのユースケースでは、このレベルのセキュリティで十分です。 財務データまたは健康データを含むアプリケーションの場合、エンドツーエンドの暗号化を実装することをお勧めします。 これを行うには、暗号化されたパケットをロードバランサーを介して転送し、アプリサーバーで復号化するか、プロキシで再暗号化して、Djangoアプリサーバーで再度復号化します。 これらの手法はこの記事の範囲を超えていますが、詳細については、エンドツーエンド暗号化を参照してください。
Nginxプロキシは、外部トラフィックと内部ネットワークの間のゲートウェイとして機能します。 理論的には、外部クライアントが内部アプリサーバーに直接アクセスすることはできず、すべてのリクエストはNginxサーバーを経由する必要があります。 ステップ1のメモでは、DockerがバイパスするDockerの未解決の問題について簡単に説明しています。 ufw
デフォルトではファイアウォール設定であり、ポートを外部で開きます。これは安全でない可能性があります。 このセキュリティ上の懸念に対処するために、Docker対応サーバーで作業する場合はクラウドファイアウォールを使用することをお勧めします。 DigitalOceanを使用したクラウドファイアウォールの作成の詳細については、ファイアウォールの作成方法を参照してください。 操作することもできます iptables
使用する代わりに直接 ufw
. 使用の詳細については iptables
Dockerの場合は、Dockerとiptablesを参照してください。
このステップでは、Dockerによって開かれたホストポートへの外部アクセスをブロックするようにUFWの構成を変更します。 アプリサーバーでDjangoを実行すると、 -p 80:8000
フラグを立てる docker
、ポートを転送します 80
ホストからコンテナへのポート 8000
. これもポートを開きました 80
外部クライアントに送信します。これは、次のWebサイトにアクセスして確認できます。 http://your_app_server_1_IP
. 直接アクセスを防ぐために、ufw-dockerGitHubリポジトリで説明されている方法を使用してUFWの構成を変更します。
最初のDjangoアプリサーバーにログインすることから始めます。 次に、 /etc/ufw/after.rules
スーパーユーザー権限を持つファイルを使用して nano
またはお気に入りの編集者:
sudo nano /etc/ufw/after.rules
プロンプトが表示されたらパスワードを入力し、 ENTER
確認するために。
次のように表示されます ufw
ルール:
#
# rules.input-after
#
# Rules that should be run after the ufw command line added rules. Custom
# rules should be added to one of these chains:
# ufw-after-input
# ufw-after-output
# ufw-after-forward
#
# Don't delete these required lines, otherwise there will be errors
*filter
:ufw-after-input - [0:0]
:ufw-after-output - [0:0]
:ufw-after-forward - [0:0]
# End required lines
# don't log noisy services by default
-A ufw-after-input -p udp --dport 137 -j ufw-skip-to-policy-input
-A ufw-after-input -p udp --dport 138 -j ufw-skip-to-policy-input
-A ufw-after-input -p tcp --dport 139 -j ufw-skip-to-policy-input
-A ufw-after-input -p tcp --dport 445 -j ufw-skip-to-policy-input
-A ufw-after-input -p udp --dport 67 -j ufw-skip-to-policy-input
-A ufw-after-input -p udp --dport 68 -j ufw-skip-to-policy-input
# don't log noisy broadcast
-A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input
# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT
一番下までスクロールして、UFW設定ルールの次のブロックに貼り付けます。
. . .
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
これらのルールは、Dockerによって開かれたポートへのパブリックアクセスを制限し、 10.0.0.0/8
, 172.16.0.0/12
、 と 192.168.0.0/16
プライベートIP範囲。 DigitalOceanでVPCを使用している場合、VPCネットワーク内のドロップレットはプライベートネットワークインターフェイスを介して開いているポートにアクセスできますが、外部クライアントはアクセスできません。 VPCの詳細については、VPCの公式ドキュメントを参照してください。 このスニペットに実装されているルールの詳細については、 ufw-dockerREADMEの仕組みを参照してください。
DigitalOceanでVPCを使用しておらず、アプリサーバーのパブリックIPアドレスを upstream
Nginx構成のブロックでは、UFWファイアウォールを明示的に変更して、Nginxサーバーからポート経由のトラフィックを許可する必要があります 80
Djangoアプリサーバー上。 作成のガイダンスについて allow
UFWファイアウォールのルールについては、 UFW Essentials:Common Firewall RulesandCommandsを参照してください。
編集が終了したら、ファイルを保存して閉じます。
再起動 ufw
新しい構成を取得するように:
sudo systemctl restart ufw
案内する http://APP_SERVER_1_IP
Webブラウザーで、ポート経由でアプリサーバーにアクセスできなくなったことを確認します 80
.
2番目のDjangoアプリサーバーでこのプロセスを繰り返します。
最初のアプリサーバーからログアウトするか、別のターミナルウィンドウを開いて、2番目のDjangoアプリサーバーにログインします。 次に、 /etc/ufw/after.rules
スーパーユーザー権限を持つファイルを使用して nano
またはお気に入りの編集者:
sudo nano /etc/ufw/after.rules
プロンプトが表示されたらパスワードを入力し、 ENTER
確認するために。
一番下までスクロールして、UFW設定ルールの次のブロックに貼り付けます。
. . .
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
編集が終了したら、ファイルを保存して閉じます。
再起動 ufw
新しい構成を取得するように:
sudo systemctl restart ufw
案内する http://APP_SERVER_2_IP
Webブラウザーで、ポート経由でアプリサーバーにアクセスできなくなったことを確認します 80
.
最後に、に移動します https://your_domain_here/polls
NginxプロキシがまだアップストリームのDjangoサーバーにアクセスできることを確認します。 デフォルトのPollsアプリインターフェースが表示されます。
結論
このチュートリアルでは、Dockerコンテナーを使用してスケーラブルなDjangoPollsアプリケーションをセットアップしました。 トラフィックが増加し、システムの負荷が増加すると、Nginxプロキシレイヤー、Djangoバックエンドアプリレイヤー、PostgreSQLデータベースレイヤーの各レイヤーを個別にスケーリングできます。
分散システムを構築する場合、多くの場合、直面しなければならない設計上の決定が複数あり、いくつかのアーキテクチャがユースケースを満たす場合があります。 このチュートリアルで説明するアーキテクチャは、DjangoとDockerを使用してスケーラブルなアプリを設計するための柔軟な青写真として意図されています。
エラーが発生したときのコンテナーの動作を制御したり、システムの起動時にコンテナーを自動的に実行したりすることができます。 これを行うには、 Systemd などのプロセスマネージャーを使用するか、再起動ポリシーを実装します。 これらの詳細については、Dockerドキュメントのコンテナを自動的に起動するを参照してください。
同じDockerイメージを実行している複数のホストで大規模に作業する場合、AnsibleやChefなどの構成管理ツールを使用して手順を自動化する方が効率的です。 構成管理の詳細については、構成管理の概要および Ansibleを使用したサーバーセットアップの自動化:DigitalOceanワークショップキットを参照してください。
すべてのホストで同じイメージを構築する代わりに、 Docker Hub のようなイメージレジストリを使用して展開を合理化することもできます。このレジストリは、Dockerイメージを一元的に構築、保存し、複数のサーバーに配布します。 イメージレジストリに加えて、継続的インテグレーションとデプロイパイプラインは、イメージをビルド、テスト、およびアプリサーバーにデプロイするのに役立ちます。 CI / CDの詳細については、 CI/CDのベストプラクティスの概要を参照してください。