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の実行に使用されます。
- すべてのユーザーには、
sudo
権限を持つroot以外のユーザーと、アクティブなファイアウォールが必要です。 これらの設定方法のガイダンスについては、この初期サーバー設定ガイドを参照してください。
- Dockerは3つのサーバーすべてにインストールされています。 Dockerのインストールに関するガイダンスについては、 Ubuntu18.04にDockerをインストールして使用する方法のステップ1と2に従ってください。
- 登録されたドメイン名。 このチュートリアルでは、全体を通して
your_domain.com
を使用します。 Freenom で無料で入手するか、選択したドメインレジストラを使用できます。 - プロキシサーバーのパブリックIPアドレスを指す
your_domain.com
を含むA
DNSレコード。 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
を使用して、Django Tutorial Polls AppGitHubリポジトリのpolls-docker
ブランチのクローンを作成します。 このリポジトリには、Djangoドキュメントのサンプルポーリングアプリケーションのコードが含まれています。 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コード、Dockerがコンテナイメージのビルドに使用するDockerfile
、およびコンテナに渡される環境変数のリストを含むenv
ファイルが含まれています。実行環境。 cat
を使用してDockerfile
を検査します。
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-polls/requirements.txt
ファイルで定義されているDjangoおよびGunicornのPythonパッケージ要件をインストールします。 次に、不要なビルドファイルをいくつか削除し、アプリケーションコードをイメージにコピーして、実行を設定しますPATH
。 最後に、ポート8000
を使用して着信コンテナー接続を受け入れることを宣言し、ポート8000
でリッスンする3人のワーカーでgunicorn
を実行します。
このDockerfileの各ステップの詳細については、Dockerを使用してDjangoおよびGunicornアプリケーションを構築する方法のステップ6を参照してください。
次に、docker build
を使用してイメージをビルドします。
docker build -t polls .
-t
フラグを使用してイメージにpolls
という名前を付け、現在のディレクトリをビルドコンテキストとして渡します。これは、イメージを構築するときに参照するファイルのセットです。
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-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
次のキーの不足している値を入力します。
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に設定します。たとえば、スペースがnyc3
リージョンにある場合は、https://space-name.nyc3.digitaloceanspaces.com
に設定します。
編集が終了したら、ファイルを保存して閉じます。
次に、docker run
を使用して、Dockerfileに設定されているCMD
をオーバーライドし、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
を押してコンテナーを終了して強制終了します。
最後に、アプリの静的ファイルを生成し、collectstatic
を使用してDigitalOceanSpaceにアップロードします。 これが完了するまでに少し時間がかかる場合があることに注意してください。
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
を公開して、Ubuntuサーバーのポート80
がポート[にマップされるようにします。 polls
コンテナのX175X]。
これで、URLバーにhttp://APP_SERVER_1_IP
と入力して、Webブラウザーを使用してpolls
アプリに移動できるようになります。 /
パスにルートが定義されていないため、404 Page Not Found
エラーが発生する可能性があります。これは予想どおりです。
警告: DockerでUFWファイアウォールを使用する場合、Dockerは、この GitHubの問題に記載されているように、構成されたUFWファイアウォールルールをバイパスします。 これは、前提条件のステップでUFWアクセスルールを明示的に作成していなくても、サーバーのポート80
にアクセスできる理由を説明しています。 ステップ5では、UFW構成にパッチを適用することにより、このセキュリティホールに対処します。 UFWを使用しておらず、DigitalOceanのクラウドファイアウォールを使用している場合は、この警告を無視しても問題ありません。
http://APP_SERVER_1_IP/polls
に移動して、Pollsアプリのインターフェイスを確認します。
管理インターフェースを表示するには、http://APP_SERVER_1_IP/admin
にアクセスしてください。 Pollsアプリの管理者認証ウィンドウが表示されます。
createsuperuser
コマンドで作成した管理者のユーザー名とパスワードを入力します。
認証後、Pollsアプリの管理インターフェースにアクセスできます。
admin
およびpolls
アプリの静的アセットは、オブジェクトストレージから直接配信されていることに注意してください。 これを確認するには、テストスペースの静的ファイル配信を参照してください。
探索が終了したら、Dockerコンテナを実行しているターミナルウィンドウでCTRL+C
を押して、コンテナを強制終了します。
アプリコンテナが期待どおりに実行されることを確認したので、 detached モードで実行できます。これにより、バックグラウンドで実行され、SSHセッションからログアウトできるようになります。
docker run -d --rm --name polls --env-file env -p 80:8000 polls
-d
フラグは、コンテナーをデタッチモードで実行するようにDockerに指示し、-rm
フラグは、コンテナーの終了後にコンテナーのファイルシステムをクリーンアップし、コンテナーにpolls
という名前を付けます。
最初のDjangoアプリサーバーからログアウトし、http://APP_SERVER_1_IP/polls
に移動して、コンテナーが期待どおりに実行されていることを確認します。
これで、最初のDjangoアプリサーバーが稼働しているので、2番目のDjangoアプリサーバーをセットアップできます。
ステップ2—2番目のDjangoアプリケーションサーバーを構成する
このサーバーをセットアップするためのコマンドの多くは前のステップのコマンドと同じであるため、ここでは省略形で示します。 このステップの特定のコマンドの詳細については、ステップ1を確認してください。
secondDjangoアプリケーションサーバーにログインすることから始めます。
django-polls
GitHubリポジトリのpolls-docker
ブランチのクローンを作成します。
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-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
nano
またはお気に入りのエディターを使用して、nginx.conf
という構成ファイルを作成します。
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構成ファイルを生成できます。
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
ブロックは、HTTPを介してupstream django
サーバーにリクエストをプロキシするようにNginxに指示します。 さらに、発信元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
イメージ、Nginxによって維持されている公式Dockerイメージのバージョン1.19.0
を使用します。
コンテナを初めて実行すると、構成ファイルで定義された証明書をまだプロビジョニングしていないため、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
をそれぞれのコンテナポートにマップします。 -v
フラグは、構成ファイルを/etc/nginx/conf.d/nginx.conf
のNginxコンテナーにマウントします。このコンテナーは、Nginxイメージがロードするように事前構成されています。 ro
または「読み取り専用」モードでマウントされているため、コンテナーはファイルを変更できません。 Webルートディレクトリ/var/www/html
もコンテナにマウントされます。 最後に、nginx:1.19.0
は、Dockerhubからnginx:1.19.0
イメージをプルして実行するようにDockerに指示します。
Dockerはイメージをプルして実行し、構成されたTLS証明書と秘密鍵が見つからない場合はNginxがエラーをスローします。 次のステップでは、DockerizedCertbotクライアントとLet'sEncrypt認証局を使用してこれらをプロビジョニングします。
ステップ4—Certbotの構成と証明書の更新を暗号化しましょう
Certbot は、 Electronic FrontierFoundationによって開発されたLet'sEncryptクライアントです。 Let's Encrypt 認証局から無料のTLS証明書をプロビジョニングし、ブラウザがWebサーバーのIDを確認できるようにします。 NginxプロキシサーバーにDockerがインストールされている場合、CertbotDockerイメージを使用してTLS証明書をプロビジョニングおよび更新します。
まず、DNSA
レコードがプロキシサーバーのパブリック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
に転送します。 /etc/letsencrypt/
と/var/lib/letsencrypt/
の2つのホストディレクトリを作成してコンテナにマウントします。 certbot
はNginxなしでstandalone
モードで実行され、Let's Encryptstaging
サーバーを使用してドメイン検証を実行します。
プロンプトが表示されたら、メールアドレスを入力し、利用規約に同意します。 ドメインの検証が成功すると、次の出力が表示されます。
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を実行している場合は、standalone
モードではなくwebroot
モードでCertbotクライアントを使用する必要があります。 つまり、Certbotは/var/www/html/.well-known/acme-challenge/
ディレクトリにファイルを作成して検証を実行し、このパスへのLet's Encrypt検証要求は、のNginx構成で定義されたlocation
ルールによってキャプチャされます。 ]ステップ3。 その後、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
このコマンドは、 HUP Unixシグナルを、nginx
Dockerコンテナー内で実行されているNginxプロセスに送信します。 このシグナルを受信すると、Nginxは構成と更新された証明書をリロードします。
HTTPSが有効になっていて、このアーキテクチャのすべてのコンポーネントが稼働している場合、最後のステップは、2つのバックエンドアプリサーバーへの外部アクセスを防止することにより、セットアップをロックダウンすることです。 すべてのHTTPリクエストはNginxプロキシを経由する必要があります。
ステップ5—DjangoAppサーバーへの外部アクセスを防止する
このチュートリアルで説明するアーキテクチャでは、SSLターミネーションはNginxプロキシで発生します。 これは、NginxがSSL接続を復号化し、パケットが暗号化されていない状態でDjangoアプリサーバーにプロキシされることを意味します。 多くのユースケースでは、このレベルのセキュリティで十分です。 財務データまたは健康データを含むアプリケーションの場合、エンドツーエンドの暗号化を実装することをお勧めします。 これを行うには、暗号化されたパケットをロードバランサーを介して転送し、アプリサーバーで復号化するか、プロキシで再暗号化して、Djangoアプリサーバーで再度復号化します。 これらの手法はこの記事の範囲を超えていますが、詳細については、エンドツーエンド暗号化を参照してください。
Nginxプロキシは、外部トラフィックと内部ネットワークの間のゲートウェイとして機能します。 理論的には、外部クライアントが内部アプリサーバーに直接アクセスすることはできず、すべてのリクエストはNginxサーバーを経由する必要があります。 ステップ1のメモでは、Dockerの未解決の問題について簡単に説明しています。Dockerはデフォルトでufw
ファイアウォール設定をバイパスし、外部のポートを開きます。これは安全でない可能性があります。 このセキュリティ上の懸念に対処するために、Docker対応サーバーで作業する場合はクラウドファイアウォールを使用することをお勧めします。 DigitalOceanを使用したクラウドファイアウォールの作成の詳細については、ファイアウォールの作成方法を参照してください。 ufw
を使用する代わりに、iptables
を直接操作することもできます。 Dockerでのiptables
の使用の詳細については、Dockerとiptablesを参照してください。
このステップでは、Dockerによって開かれたホストポートへの外部アクセスをブロックするようにUFWの構成を変更します。 アプリサーバーでDjangoを実行するときに、-p 80:8000
フラグをdocker
に渡しました。これにより、ホストのポート80
がコンテナポート8000
に転送されます。 これにより、外部クライアントへのポート80
も開かれました。これは、http://your_app_server_1_IP
にアクセスして確認できます。 直接アクセスを防ぐために、ufw-dockerGitHubリポジトリで説明されている方法を使用してUFWの構成を変更します。
最初のDjangoアプリサーバーにログインすることから始めます。 次に、nano
またはお気に入りのエディターを使用して、スーパーユーザー権限で/etc/ufw/after.rules
ファイルを開きます。
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を使用しておらず、Nginx構成のupstream
ブロックにアプリサーバーのパブリックIPアドレスを入力している場合は、UFWファイアウォールを明示的に変更してNginxサーバーからのトラフィックを許可する必要がありますDjangoアプリサーバーのポート80
を介して。 UFWファイアウォールを使用してallow
ルールを作成するためのガイダンスについては、 UFW Essentials:Common Firewall Rules andCommandsを参照してください。
編集が終了したら、ファイルを保存して閉じます。
ufw
を再起動して、新しい構成を取得します。
sudo systemctl restart ufw
Webブラウザでhttp://APP_SERVER_1_IP
に移動し、ポート80
を介してアプリサーバーにアクセスできなくなったことを確認します。
2番目のDjangoアプリサーバーでこのプロセスを繰り返します。
最初のアプリサーバーからログアウトするか、別のターミナルウィンドウを開いて、2番目のDjangoアプリサーバーにログインします。 次に、nano
またはお気に入りのエディターを使用して、スーパーユーザー権限で/etc/ufw/after.rules
ファイルを開きます。
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
Webブラウザでhttp://APP_SERVER_2_IP
に移動し、ポート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のベストプラクティスの概要を参照してください。