開発者ドキュメント

Docker、Nginx、Let’sEncryptを使用してDjangoアプリケーションをスケーリングおよび保護する方法

序章

クラウドベースの環境では、Djangoアプリケーションをスケーリングして保護する方法は複数あります。 水平方向にスケーリングし、アプリのコピーを複数実行することで、フォールトトレラントで可用性の高いシステムを構築できると同時に、スループットを増やして、リクエストを同時に処理できるようにします。 。 Djangoアプリを水平方向にスケーリングする1つの方法は、DjangoアプリケーションとそのWSGI HTTPサーバー(GunicornuWSGIなど)を実行する追加のアプリサーバーをプロビジョニングすることです。 この一連のアプリサーバー間で着信リクエストをルーティングおよび分散するには、ロードバランサーおよび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アプリケーションサーバーの前に配置されます。 最後に、プロキシサーバーのみへの外部アクセスを制限することにより、この分散システムを強化します。

前提条件

このチュートリアルに従うには、次のものが必要です。

ステップ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アプリケーションを構築する方法を参照してください。

  1. 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
Output
FROM 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:

  1. docker build -t polls .

画像に名前を付けます polls を使用して -t フラグを立てて、現在のディレクトリをビルドコンテキストとして渡します。これは、イメージを構築するときに参照するファイルのセットです。

Dockerがイメージをビルドしてタグ付けした後、使用可能なイメージをリストします。 docker images:

docker images

あなたは見るべきです polls リストされている画像:

Output
REPOSITORY 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-polls / 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

次のキーの不足している値を入力します。

編集が終了したら、ファイルを保存して閉じます。

これから使用します docker run をオーバーライドするには CMD Dockerfileに設定し、を使用してデータベーススキーマを作成します manage.py makemigrationsmanage.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"、アプリコードで定義されたデータベーススキーマを作成します。 これを初めて実行する場合は、次のように表示されます。

Output
No 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"

これらのファイルが生成およびアップロードされると、次の出力が表示されます。

Output
121 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サーバーでポートにマップされます 8000polls 容器。

これで、に移動できるようになります。 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アプリの管理インターフェースにアクセスできます。

の静的アセットに注意してください adminpolls アプリはオブジェクトストレージから直接配信されています。 これを確認するには、テストスペースの静的ファイル配信を参照してください。

探索が終了したら、 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リポジトリ:

  1. git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

に移動します django-polls ディレクトリ:

cd django-polls

を使用してイメージを構築します docker build:

  1. docker build -t polls .

を開きます env とファイル nano またはお気に入りの編集者:

nano env
django-polls / 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構成で貼り付けます。

conf / nginx.conf

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 指令:

conf / nginx.conf
upstream django {
	server APP_SERVER_1_IP;
	server APP_SERVER_2_IP;
}
. . .

このブロックでは、アップストリームに名前を付けます django 両方のDjangoアプリサーバーのIPアドレスを含めます。 アプリサーバーがDigitalOceanで実行されており、VPCネットワーキングが有効になっている場合は、ここでプライベートIPアドレスを使用する必要があります。 DigitalOceanでVPCネットワーキングを有効にする方法については、既存のドロップレットでVPCネットワーキングを有効にする方法を参照してください。

最初 server ブロックは、ドメインと一致しないリクエストをキャプチャし、接続を終了します。 たとえば、サーバーのIPアドレスへの直接HTTPリクエストは、次のブロックによって処理されます。

conf / nginx.conf
. . .
server {
	listen 80 default_server;
	return 444;
}
. . .

server ブロックは、 HTTP 301リダイレクトを使用して、ドメインへのHTTPリクエストをHTTPSにリダイレクトします。 これらのリクエストは、ファイナルによって処理されます server ブロック:

conf / nginx.conf
. . .
server {
    listen 80;
    listen [::]:80;
    server_name your_domain.com;
    return 301 https://$server_name$request_uri;
}
. . .

これらの2つのディレクティブは、TLS証明書と秘密鍵へのパスを定義します。 これらはCertbotを使用してプロビジョニングされ、次のステップでNginxコンテナにマウントされます。

conf / nginx.conf
. . .
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つの役立つガイドです。

conf / nginx.conf
. . .
	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 秒。

conf / nginx.conf
. . .
client_max_body_size 4G;
keepalive_timeout 5;
. . .

最初 location ブロックはNginxにリクエストをプロキシするように指示します upstream django HTTP経由のサーバー。 さらに、発信元IPアドレス、接続に使用されるプロトコル、およびターゲットホストをキャプチャするクライアントHTTPヘッダーを保持します。

conf / nginx.conf
. . .
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のドキュメントのチャレンジタイプを参照してください。

conf / nginx.conf
. . .
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 ホストポートをマッピングします 80443 それぞれのコンテナポートに。 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 ドメイン検証を実行するサーバー。

プロンプトが表示されたら、メールアドレスを入力し、利用規約に同意します。 ドメインの検証が成功すると、次の出力が表示されます。

Output
Obtaining 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 フラグを立てて、証明書の更新を実際に実行せずにすべてが正しく機能していることを確認します。

更新シミュレーションが成功すると、次の出力が表示されます。

Output
Cert 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 ルール:

/etc/ufw/after.rules
#
# 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設定ルールの次のブロックに貼り付けます。

/etc/ufw/after.rules
. . .

# 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設定ルールの次のブロックに貼り付けます。

/etc/ufw/after.rules
. . .

# 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イメージを実行している複数のホストで大規模に作業する場合、AnsibleChefなどの構成管理ツールを使用して手順を自動化する方が効率的です。 構成管理の詳細については、構成管理の概要および Ansibleを使用したサーバーセットアップの自動化:DigitalOceanワークショップキットを参照してください。

すべてのホストで同じイメージを構築する代わりに、 Docker Hub のようなイメージレジストリを使用して展開を合理化することもできます。このレジストリは、Dockerイメージを一元的に構築、保存し、複数のサーバーに配布します。 イメージレジストリに加えて、継続的インテグレーションとデプロイパイプラインは、イメージをビルド、テスト、およびアプリサーバーにデプロイするのに役立ちます。 CI / CDの詳細については、 CI/CDのベストプラクティスの概要を参照してください。

モバイルバージョンを終了