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

提供:Dev Guides
移動先:案内検索

序章

クラウドベースの環境では、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アプリケーションサーバーの前に配置されます。 最後に、プロキシサーバーのみへの外部アクセスを制限することにより、この分散システムを強化します。

前提条件

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

  • 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を含むADNSレコード。 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_KEYDjango 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-pollsGitHubリポジトリの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;
    }

}

これらのupstreamserver、およびlocationブロックは、HTTPリクエストをHTTPSにリダイレクトし、手順1と2で構成された2つのDjangoアプリサーバー間で負荷分散するようにNginxを構成します。 Nginx構成ファイルの構造の詳細については、Nginx構成ファイルの構造と構成コンテキストの理解に関するこの記事を参照してください。 さらに、Nginxサーバーとロケーションブロック選択アルゴリズムの理解に関するこの記事が役立つ場合があります。

この構成は、 GunicornCerbot 、および 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という名前を付け、ホストポート80443をそれぞれのコンテナポートにマップします。 -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アドレスにマップされていることを確認します。 次に、プロキシサーバーで、certbotDockerイメージを使用して証明書のステージングバージョンをプロビジョニングします。

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

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