何百万人ものユーザーが、学習、エンターテインメント、ニュース、友人との近況’の共有など、さまざまな目的で情報にアクセスするためにインターネットを利用しています。したがって、アプリをデプロイする際には、アプリケーションのために安全性が高くスケーラブルなインフラストラクチャを実装することが最善の利益となります。クラウドは、Django アプリケーションを保護し、スケールするためのさまざまな方法を提供します。水平スケーリングは、アプリの複数のコピーを実行できるようにする手法の1つです。これにより、耐障害性と高可用性が向上します。また、複数のリクエストを同時に処理するためのパフォーマンスも向上します。
Djangoアプリケーションの水平スケーリング
Djangoアプリケーションと、そのWSGI HTTPサーバー(Gunicorn や uWSGI など)を実行する複数のアプリサーバーをプロビジョニングすることで、Djangoアプリケーションを水平スケーリングできます。その後、これらのアプリサーバーに受信リクエストを分散するためのインフラストラクチャをセットアップする必要があります。ロードバランサーとNginxのようなリバースプロキシ は、インフラストラクチャのトラフィック分散に役立ちます。NginxはSSL certificatesをデプロイして、HTTPSを介したアプリへの安全な接続を確保できます。最後に、Nginxは静的コンテンツのキャッシュを提供して、サーバーへの負荷を最小限に抑えることもできます。
これらのさまざまなコンポーネントを個別に構成し、それらが確実に通信できるようにすることは、困難な作業となる場合があります。幸いなことに、Dockerを使用すると、構成プロセスが簡素化され、どこにデプロイされてもさまざまなコンポーネントが同じように動作するようになります。
このガイドで行うこと
このガイドでは、Gunicorn WSGI HTTPサーバーで提供される、コンテナ化されたDjangoアプリケーションを水平スケーリングする方法を学びます。Dockerがインストールされ、DjangoとGunicornのアプリコンテナの同じコピーを実行している2つのアプリケーションサーバーをプロビジョニングします。
また、Let’s Encrypt SSL証明書を使用してアプリケーションを保護します。そのために、3番目のプロキシサーバーをプロビジョニングおよび構成し、そこでNginx reverse proxyコンテナとCertbotクライアントコンテナを実行します。Certbotは、Let’s Encrypt認証局からのSSL証明書の管理を支援するパッケージです。証明書を取得し、証明書の場所を使用してNginxサーバーブロックを構成し、自動更新を管理します。これは、証明書の有効期限が切れそうで更新が必要かどうかを定期的に確認するcronジョブを構成することによって行われます。SSL証明書を最新の状態に保つことで、Webサイトは常にSSL Labs.
で高いセキュリティ評価を得ることができます。3番目のプロキシサーバーは、分散アーキテクチャの最前面に配置され、すべての外部からの受信トラフィックを受け取ります。その後、トラフィックをアプリサーバーに分散します。アプリサーバーはファイアウォールの背後に配置され、プロキシサーバーからのアクセスのみを許可します。
このチュートリアルは、Django、Docker、およびKubernetesを扱う3つのチュートリアルシリーズの2番目です。まず、Building a Django and Gunicorn Application with Docker on Ubuntuのチュートリアルで説明されている手順に従う必要があります。そのチュートリアルでは、ベースとなるプロジェクトコードとDockerfileをセットアップし、アプリをMinIo Simple Storage Service (S3) に接続して静的ファイルを提供しました。
前提条件
このチュートリアルを進めるには、以下が必要です。
- 4台のUbuntu 20.04サーバー:
前提条件のチュートリアルであるBuilding a Django and Gunicorn Application with Docker on Ubuntuの手順に従っている場合、4台のサーバーのうちすでに2台が用意されています。
-
最初のサーバーはPostgreSQL database instanceを実行します。チュートリアル「Building a Django and Gunicorn Application with Docker on Ubuntu」のステップ1と2に従ってデータベースをセットアップします。Postgresの構成は、アプリサーバーのIPのみからの外部接続を許可するように変更する必要があります。
-
The 2番目および3番目のサーバーは、アプリケーションコードのコンテナをホストします。すでに前提条件のチュートリアルから2番目のサーバーが実行されているはずです。プロキシサーバーのIPからの外部接続のみを許可するように、そのファイアウォールを変更します。Ubuntuサーバーのセットアップに役立つ、このステップバイステップのチュートリアルのステップ1から4に従うことができます。 CloudSigma上。
-
その 4番目の サーバーは、プロキシサーバーとなり、2つのアプリケーションサーバーコンテナへのロードバランシングとトラフィックの分散を処理します。
-
Dockerは、2つのアプリサーバーとプロキシサーバーにインストールされている必要があります。
以下のステップに従った後、前提条件のチュートリアル、サーバーの1つにすでにDockerがインストールされているはずです。当社のDockerのインストールと操作に関するチュートリアルのステップ1、2、3に従うことができます。上記で作成したsudoユーザーをDockerグループに追加することを忘れないでください。
- 登録済みのドメイン名を取得し、そのDNSレコードを設定してプロキシサーバーのパブリックIPアドレスを指すようにします。デモンストレーションの目的で、以下を使用します。 example_domain.com.
-
S3オブジェクトストレージサービスをセットアップします。前提条件のチュートリアルでは、ストレージサービスとしてMinIOを使用しました。したがって、ステップ5の前提条件のチュートリアルの説明に従って、MinIOストレージバケットをセットアップしてください。
ステップ1:最初のDjangoアプリケーションサーバーが動作していることの確認
以下に説明されているように、前提条件、このガイドはUbuntu上でDockerを使用してDjangoとGunicornアプリケーションを構築するに関するチュートリアルの後に続きます。そのチュートリアルから来て、すでにステップを実行している場合は、最初のサーバーが稼働しているはずです。アプリケーションのコードは、Djangoドキュメントの投票アプリケーションのチュートリアルに基づいています。初期セットアップを理解するために、これらのステップをよく読むことが重要です。チュートリアルのステップを実行済みの場合は、この最初のステップをスキップできます。
そうでない場合は、Docker化されたブランチをサーバーにクローンするだけです。まず、最初のアプリサーバーにログインし、次の gitコマンドを実行して、 django-polls-dockerブランチをdjango-pollsリポジトリから取得します:
|
1 |
git clone --single-branch --branch django-polls-docker --depth 1 https://github.com/jaymoh/django-polls.git |
次に、 django-pollsディレクトリに移動します:
cd django-polls
このディレクトリには、アプリケーションイメージを構築するためにDockerが使用する Dockerfile、Pythonアプリケーションコードを含む django-pollsディレクトリ、および起動時にコンテナに渡されてその動作を変更する環境変数のリストを含む envファイルがあります。この Dockerfileでは、 requirements.txtファイルを通じてDjangoパッケージの依存関係を定義します。さらに、着信トラフィックを受け入れるために使用するポート 8000を宣言し、3つのワーカーを持つ gunicornサーバーを実行するように設定する必要があります。Dockerfileの命令について詳しく知るには、チュートリアルステップ7のUbuntu上でDockerを使用してDjangoとGunicornアプリケーションを構築する.
次のコマンドを使用してDockerイメージを構築できます:
docker build -t django-polls:v1 .
Dockerがイメージを構築した後、次のコマンドを使用してサーバー上の利用可能なイメージを一覧表示できます:
docker images
コマンドを実行したときの出力は次のとおりです:
次に、実行環境の構成に使用される envファイルを変更する必要があります。このファイルは、コンテナの起動時にDocker実行コンテナに渡されます。nanoエディタで envファイルを開きます:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
DJANGO_SECRET_KEY=your_secret_key DEBUG= DJANGO_LOGLEVEL=info DJANGO_ALLOWED_HOSTS=your_server_IP_address DB_ENGINE=postgresql_psycopg2 DB_DATABASE=polls_db DB_USERNAME=hackins DB_PASSWORD=your_database_password DB_HOST=your_database_host DB_PORT=your_database_port STATIC_DEFAULT_FILE_STORAGE=storages.backends.s3boto3.S3Boto3Storage STATIC_MINIO_BUCKET_NAME=test-bucket MINIO_ACCESS_KEY=your_minio_access_key MINIO_SECRET_KEY=your_minio_secret_key MINIO_URL=your_minio_url:your_minio_port |
この env ファイルには、変更して正しい値を入力する必要があるプレースホルダーテキストが含まれています。
-
DJANGO_SECRET_KEY:次のドキュメントで説明されているように、一意で予測不可能な値を生成します:Djangoドキュメント。このコマンドを使用してランダムな文字列を生成し、変数に設定できます: python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'.
-
DJANGO_ALLOWED_HOSTS:この値は、HTTPホストヘッダー攻撃からアプリを保護するために使用されます。開発モードの場合は、すべてのホストに一致するワイルドカードである *に設定できます。アプリを本番環境にデプロイするときは、これを登録済みのドメイン名に設定します。このデモンストレーションでは、 example_domain.com.
-
DB_DATABASE:次のセクションで作成したPostgreSQLデータベースの名前に設定します:前提条件セクション。今回の場合は polls_db.
-
DB_USERNAME:データベース用に選択したユーザー名に設定します。
-
DB_PASSWORD:データベース用に選択したパスワードに設定します。
-
DB_HOST:次のセクションでセットアップした、データベースインスタンスを実行しているホストに設定します:前提条件セクション。これは、データベースをセットアップするためのチュートリアルBuilding a Django and Gunicorn Application with Docker on Ubuntuのステップ1と2で説明されています。.
-
DB_PORT:データベースのポートに設定します。
編集が終わったら、ファイルを保存して閉じます。データベースの資格情報が設定されたので、コンテナを実行し、Dockerfileで設定されたCMDコマンドをオーバーライドすることで、データベーススキーマを作成できます。詳細については、公式ドキュメントのDockerfileエントリーポイントを参照してください。次に、以下のコマンドを実行します:
|
1 |
docker run --env-file env django-polls:v1 sh -c "python manage.py makemigrations && python manage.py migrate" |
このコマンドでは、 django-polls:v1イメージを実行し、先ほど変更した envファイルを渡しています。次の部分: sh -c "python manage.py makemigrations && python manage.py migrate”は、アプリコードで定義されたデータベーススキーマを作成します。コマンドを初めて実行する場合は、データベーススキーマの作成を示す同様の出力が表示されるはずです:
スキーマが作成されたら、Djangoのスーパーユーザーを作成できます。次のコマンドを実行して、対話型シェルでコンテナを起動します:
|
1 |
docker run -i -t --env-file env django-polls:v1 sh |
このコマンドは、Pythonシェルと対話するために使用できるシェルプロンプトでコンテナを起動します。次のコマンドでユーザーを作成しましょう:
|
1 |
python manage.py createsuperuser |
プロンプトに従って、ユーザー名、メールアドレス、パスワードを入力します。パスワードを再入力し、Enterキーを押してユーザーを作成します。シェルを終了し、次のキーを押してコンテナを停止します: CTRL+D.
次に、デフォルトのコマンドを collectstatic Djangoコマンドでオーバーライドして、コンテナを再度実行する必要があります。このコマンドは、アプリの静的ファイルを生成し、MinIOクラウドストレージにアップロードします:
|
1 |
docker run --env-file env django-polls:v1 sh -c "python manage.py collectstatic --noinput" |
このコマンドは、設定されたオブジェクトストレージサービスにファイルを生成してアップロードします。出力は次のとおりです:
これで、Dockerfileで定義されているデフォルトのCMDコマンドをオーバーライドする追加のコマンドを指定せずに、アプリケーションを実行できます:
|
1 |
docker run --env-file env -p 80:8000 django-polls:v1 |
DockerはDockerfileで定義されたデフォルトのコマンドを実行し、 gunicorn サーバーでコンテナを起動し、コンテナポート 8000を公開して、それをUbuntuのポート 80にマッピングします。これで、ブラウザのアドレスバーで最初のサーバーのIPアドレスにアクセスして、アプリケーションのインターフェースを表示できます: http://FIRST_SERVER_IP.
次のエラーが表示されます:404 Page Not Found。これは、次のものに対して何も定義していないためです: / パス。以下にアクセスします。 http://FIRST_SERVER_IP/polls にアクセスして、Polls(投票)インターフェースを確認します。
管理インターフェースにアクセスして、いくつかの投票を作成します: http://FIRST_SERVER_IP/admin:
管理インターフェースにアクセスするには、上記の createsuperuser コマンドで設定した資格情報を入力します:
ページのソースを表示すると、定義通りにストレージバケットから静的ファイルが取得されていることがわかります。コンテナが期待通りにアプリを提供していることを確認したら、ターミナルで CTRL+C を押してコンテナを終了できます。
次に、最初のサーバーのSSHセッションを終了できるように、コンテナを detached モードで実行し続ける必要があります。これにより、コンテナはバックグラウンドで実行されたままになります。次のコマンドを実行します:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
The -d フラグは、コンテナを detached モードで起動し、バックグラウンドで実行し続けることができます。 --rm フラグは、コンテナの終了後にそのファイルシステムをクリーンアップします。コンテナを一覧表示したときに確認できるように、コンテナに polls という名前を付けます。
最初のサーバーのSSHセッションを終了し、ブラウザで http://FIRST_SERVER_IP/polls にアクセスして、期待通りに動作していることを確認します。投票インターフェースが表示されれば、最初のアプリサーバーのセットアップは成功です。次のステップで2番目のアプリケーションサーバーをセットアップしましょう。
ステップ 2: 2番目のアプリケーションサーバーのセットアップ
「Building a Django and Gunicorn Application with Docker on Ubuntu」チュートリアルで作成した、アプリケーションのDocker化されたブランチをクローンします。ここで使用するコマンドの詳細については、そのチュートリアルを参照するか、ステップ 1.
「Prerequisites」セクションの説明に従って、2番目のサーバーが稼働しており、非ルートのsudoユーザーが追加され、Dockerがインストールされている必要があります。
次のステップは、このサーバーをPostgreSQLサーバーインスタンスに接続するように設定することです。「Building a Django and Gunicorn Application with Docker on Ubuntu」チュートリアルのステップ1で説明されているように、 ufw およびPostgreSQLの設定で2番目のサーバーのIPアドレスを許可する必要があります。
まず、非ルートのsudoユーザーでPostgreSQLデータベースサーバーインスタンスにログインします。 ufw ルールを追加するには、次のコマンドを実行します:
|
1 |
sudo ufw allow from SECOND_SERVER_IP to any port 5432 |
次に、このコマンドを実行して、2番目のサーバーのIPアドレスをPostgreSQLクライアント認証ファイルに追加します:
|
1 |
sudo nano /etc/postgresql/12/main/pg_hba.conf |
コメントを読んで設定の詳細を理解してください。次に、hostsセクションの下に、お使いのIPアドレスを指定して次の行を追加します:
|
1 |
host all all SECOND_SERVER_IP/24 md5 |
編集が終わったら、ファイルを保存して閉じます。
その後、変更を有効にするためにPostgreSQLサービスを再起動します:
|
1 |
sudo service postgresql restart |
PostgreSQLデータベースサーバーインスタンスからログアウトし、2番目のアプリサーバーインスタンスの設定に進みます。
SSHでsecond app serverにsshでログインします。その後、次のコマンドを実行して、 django-polls-ブランチの django-pollsリポジトリをクローンします:
|
1 2 |
git clone --single-branch --branch django-polls-docker --depth 1 https://github.com/jaymoh/django-polls.git |
Move into the django-polls ディレクトリに移動します:
|
1 |
cd django-polls |
その後、次のコマンドでイメージをビルドします:
|
1 |
docker build -t django-polls:v1 . |
イメージのビルドプロセスが完了したら、 env ファイルを、Step 1で説明されている設定値で修正します。nanoでファイルを開きます:
|
1 |
nano env |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
DJANGO_SECRET_KEY=your_secret_key DEBUG= DJANGO_LOGLEVEL=info DJANGO_ALLOWED_HOSTS=your_server_IP_address DB_ENGINE=postgresql_psycopg2 DB_DATABASE=polls_db DB_USERNAME=hackins DB_PASSWORD=your_database_password DB_HOST=your_database_host DB_PORT=your_database_port STATIC_DEFAULT_FILE_STORAGE=storages.backends.s3boto3.S3Boto3Storage STATIC_MINIO_BUCKET_NAME=test-bucket MINIO_ACCESS_KEY=your_minio_access_key MINIO_SECRET_KEY=your_minio_secret_key MINIO_URL=your_minio_url:your_minio_port |
プレースホルダーのテキストを、ステップ1で追加した実際の値に置き換えてください。忘れずに DJANGO_ALLOWED_HOSTS変数を適切に変更してください。完了したらファイルを保存して閉じます。前のステップと同様に、 envファイル内のMinIO認証情報を更新します。
これで、次のコマンドを使用して、デタッチドモードでアプリコンテナを実行できます。
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
このコマンドはコンテナを起動し、バックグラウンドで実行し続けます。2番目のアプリサーバーのSSHセッションを終了し、ブラウザで http://SECOND_SERVER_IP/pollsにアクセスして、期待通りに動作していることを確認します。すべてが正常に機能していれば、投票(polls)インターフェースが表示されるはずです。
これで、アプリケーションの同じコピーを実行する2つのアプリサーバーが用意できました。次のステップでは、リバースプロキシとして機能するようにNginxコンテナを構成します。
ステップ3:Nginx Dockerコンテナのセットアップ
Nginxは、世界で最も人気のあるオープンソースのWebサーバーソフトウェアの1つです。インターネット上で最もトラフィックの多いサイトの可用性とスケーラビリティを確保する役割を担っています。また、セキュリティを保証し、非常に多用途です。これを使用して、リバースプロキシ、キャッシュ、およびロードバランシングを行うことができます。今回のアプリケーションは、静的ファイルとメディアファイルを処理するために別のオブジェクトストレージサービスを使用するようにセットアップされています。そのため、Nginxのキャッシュ機能は使用しません。代わりに、Nginxのリバースプロキシ機能とロードバランシング機能を使用します。フロントに位置するNginxサーバーが着信トラフィックを受信し、それをバックエンドのアプリケーションサーバーに分散します。そして、次のサービスから取得したSSL証明書を使用してトラフィックを保護し、クライアントとサーバー間の安全な通信を確保します:Let’s Encrypt.
。Nginxのリバースプロキシとロードバランシングを実装する方法はいくつかあります。方法の1つは、このチュートリアルで行ったように、Nginxリバースプロキシをバックエンドアプリケーションサーバーとは別に設定することです。このセットアップは柔軟性があり、Nginxプロキシレイヤーとアプリケーションレイヤーの両方をスケールさせることができます。複数のNginxプロキシを追加したり、クラウドロードバランサーを実装したりできます。リバースプロキシを実装する別の方法は、バックエンドアプリサーバーの1つをNginxプロキシとして使用することです。これにより、着信リクエストをローカルおよび他のアプリサーバーにプロキシできます。オプションとして、すべてのバックエンドアプリサーバーにNginxコンテナを構成し、フロントにクラウドロードバランサーを配置して着信トラフィックを受信し、それをバックエンドアプリサーバーに分散することもできます。
プロキシサーバーの構成を開始しましょう。Nginxプロキシとして使用するように設定した4番目のUbuntuサーバーにログインし、構成ディレクトリを作成します。
|
1 |
mkdir conf |
ディレクトリ内でnanoを使用して構成ファイルを開きます。
|
1 |
nano conf/nginx.conf |
次に、ファイルに以下の構成を追加します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
upstream django { server FIRST_SERVER_IP; server SECOND_SERVER_IP; } server { listen 80 default_server; return 444; } server { listen 80; listen [::]:80; server_name example_domain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example_domain.com; # SSL ssl_certificate /etc/letsencrypt/live/example_domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example_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; } } |
この設定ファイルでは、 server, upstream、および location ブロックを指定して、NginxにHTTPリクエストをHTTPSにリダイレクトし、ステップ 1 と ステップ 2 でセットアップした2つのアプリサーバーにリクエストを分散するように指示します。一般的な情報については、公式ドキュメントの「Nginx設定ファイルの構造」を参照してください。.
私たちは、Docker HubのNginxイメージドキュメント、, Certbot、およびGunicornによって提供されている設定ファイルを参考にしました。この最小限のNginx設定ファイルを作成しました。これはデモンストレーション目的であり、セットアップを稼働させるためだけのものですが、Nginxガイドに従ったその他の設定を自由に探索し、実験することができます。.
upstreamブロックは、受信リクエストを処理するサーバーのグループを定義するために使用されます。グループには 名前が与えられ、 proxy_passディレクティブによって呼び出されます。ブロックの名前を djangoとし、2つのバックエンドアプリサーバーのIPアドレスを指定しました:
|
1 2 3 4 |
upstream django { server FIRST_SERVER_IP; server SECOND_SERVER_IP; } |
また、3つのserverブロックも定義しました。最初のserverブロックは、ドメインに一致しないすべてのリクエストをキャプチャし、 444コードを返します(クライアントにレスポンスを送信せずに接続を閉じるため、悪意のあるリクエストや不正なリクエストを拒否します)。default_serverとして定義されているため、サーバーのIPアドレスへの直接のHTTPリクエストはこのブロックによって処理されます。:
|
1 2 3 4 |
server { listen 80 default_server; return 444; } |
The 2番目のserverブロックは、受信HTTP(ポート 80)リクエストを、443に、HTTP 301リダイレクトを使用してリダイレクトします。:
|
1 2 3 4 5 6 |
server { listen 80; listen [::]:80; server_name example_domain.com; return 301 https://$server_name$request_uri; } |
現在、3番目のserverブロックがリクエストを処理します。これにはいくつかのディレクティブがあり、その重要性について以下で説明します。
CertbotによってプロビジョニングされたTLS証明書とキーへのパスを定義する2つのディレクティブがあります。証明書は、起動時にNginxコンテナにマウントされます。
|
1 2 3 |
# SSL ssl_certificate /etc/letsencrypt/live/example_domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example_domain.com/privkey.pem; |
次に、Certbotによって推奨されているSSLセキュリティのデフォルト設定があります。詳細については、ngx_http_ssl_moduleに関するNginx公式ドキュメントを参照してください。Mozillaも、サーバー側のセキュリティに関する詳細情報を提供しています。 ssl_ciphersの値は、 confファイルにおいて、Mozillaのページから引用されています:
|
1 2 3 4 5 6 |
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"; |
次の2つのディレクティブでは、クライアントリクエストボディの最大許容サイズを定義し、クライアントとの keep-alive接続のタイムアウトを設定します。Nginxは、 keepalive_timeoutディレクティブで設定した秒数が経過すると、クライアントとの接続を閉じます。詳細については、公式ドキュメントのGunicornをデプロイするためのNginx設定を参照してください:
|
1 2 |
client_max_body_size 4G; keepalive_timeout 5; |
設定ファイルでは、2つのlocationブロックも定義しています。最初のブロックは、proxyディレクティブで定義されているリクエストのプロキシ処理を処理します。受信リクエストは、前に定義した upstream djangoサーバーにプロキシされます:
|
1 2 3 4 5 6 7 |
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 Module ngx_http_proxy_module および、次のドキュメント:deploying a Gunicorn server.
2つ目のlocationブロックでは、パスを定義します: /well-known/acme-challenge/。これは通常、SSL証明書のプロビジョニングまたは更新を行う前に、CertbotがLet's Encryptでドメイン名を検証するために使用されます:
|
1 2 3 |
location ^~ /.well-known/acme-challenge/ { root /var/www/html; } |
Nginx設定ファイルに関する説明は以上です。編集が完了したら、ファイルを保存して閉じることができます。
定義したばかりの設定ファイルは、Nginxコンテナを実行するために使用できます。ただし、Let's EncryptからSSL証明書をプロビジョニングしていないため、失敗します。このチュートリアルでは、 nginx:1.20.2 の公式 Docker Hub上のNginxイメージリポジトリから、Dockerイメージバージョン1.20.2を使用します。.
以下のコマンドを実行してイメージをダウンロードし、すべてが正しく動作しているか確認できます:
|
1 2 3 4 |
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.20.2 |
このコマンドは、 nginx という名前のコンテナを作成し、ホストシステムとコンテナの間でポート 80 と 443 をマッピングします。 --rm フラグは、ビルドが成功した後に中間コンテナを削除します。また、 -v フラグを使用して、設定ファイルをコンテナ内の /etc/nginx/conf.d/nginx.conf(デフォルトのNginx設定ディレクトリ)にマウントします。Nginxコンテナによる変更を防ぐため、 ro フラグを使用して読み取り専用モードでマウントされます。デフォルトの webroot ディレクトリを設定し、それを /var/www/html としてマウントします。最後に、Dockerにこのビルドで nginx:1.20.2 イメージを使用するように指示します。次のステップで、Let's EncryptからTLS/SSL証明書とキーを取得しましょう。
ステップ4:Let's EncryptからのSSL/TLS証明書のプロビジョニングとCertbot自動更新の設定
Certbotは、Let’s Encryptから無料のTLS証明書をプロビジョニングし、有効期限が切れる前の自動更新を管理するのに役立ちます。これにより、ウェブサイトのセキュリティが向上し、HTTPS経由で提供されるようになります。アーキテクチャをコンテナ化した状態に保つため、CertbotのDockerイメージを使用してSSL/TLS証明書を取得し、自動更新を設定します。プロキシサーバーにDockerがインストールされていることを、前提条件の指示に従って確認してください。
また、登録済みのドメイン名のDNS AレコードがプロキシサーバーのIPアドレスを指している必要があります。certbotのDockerイメージを実行し、 --staging フラグを渡すことで検証できます:
|
1 2 3 4 |
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 example_domain.com |
このコマンドは、Certbotイメージをダウンロードし、インタラクティブモードで実行します。つまり、シェルが起動し、詳細情報を入力できるようになります。ホストのポート 80 をコンテナ内のポート 80 にマッピングします。 -v フラグを使用して、ホストの2つのディレクトリをコンテナにマウントします: /etc/letsencrypt/ および /var/lib/letsencrypt/ です。 --standalone フラグは、Nginxを使用せずにCertbotイメージを実行することを指定します。最後に、 --staging フラグがあり、これによりCertbotがステージングサーバー上で実行され、ドメイン名が検証されます。
メールアドレスを入力し、利用規約に同意してください。以下は検証に成功したときの出力です:
|
1 2 3 4 5 6 7 |
アカウント 登録済み. リクエスト中 a 証明書 〜用 example_domain.com 正常に 受信しました 証明書. 証明書 は 保存されました に: /etc/letsencrypt/live/example_domain.com/fullchain.pem キー は 保存されました に: /etc/letsencrypt/live/example_domain.com/privkey.pem この 証明書 有効期限 に 2022-04-29. これらの ファイル は 〜される 更新される のときに その 証明書 更新される. |
次のステップ:
証明書は有効期限が切れる前に更新する必要があります。Certbotはバックグラウンドで自動的に証明書を更新できますが、その機能を有効にするための手順を実行する必要がある場合があります。こちらのリンク で手順を確認してください。
次の cat コマンドを使用して証明書を表示できます:
|
1 |
sudo cat /etc/letsencrypt/live/example_domain.com/fullchain.pem |
上記のコマンドを実行すると、ターミナルに証明書が表示されます。Certbotが証明書をプロビジョニングしたことを確認したら、ステップ 3 で作成したNginxの設定をテストできます。以下のDockerコマンドを実行して、Nginxコンテナを起動します:
|
1 2 3 4 5 6 |
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.20.2 |
このコマンドでは、 -v フラグを使用して、Let's EncryptのSSL/TLS証明書ディレクトリの場所をマウントしています。
コンテナが起動して実行されたら、ブラウザでウェブページを開きます: http://example_domain.com。ウェブサイトが安全ではないという警告が表示される可能性があります:
これは、Let's Encryptから本番用(production) の証明書ではなく、ステージング/テスト用の証明書のみをプロビジョニングしたためです。以下のCertbotコマンドを --staging フラグなしで実行して、本番用の証明書を取得しましょう:
|
1 2 3 4 |
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 example_domain.com |
プロンプトで、既存の証明書を更新して置き換える(renew and replace) ことを確認するために、 2 を入力して ENTER を押します。これにより、本番環境に対応した証明書がプロビジョニングされます。これでNginxコンテナを実行でき、すべてが正常に動作するはずです:
|
1 2 3 4 5 6 |
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.20.2 |
コンテナが起動して実行されたら、ブラウザでウェブページをもう一度開きます: http://example_domain.com。HTTPを入力した場合でも、ブラウザがHTTPSにリダイレクトされることに注意してください。これは、Nginx設定内のサーバーとプロビジョニングされたSSL/TLS証明書が正常に動作していることを意味します。ホームパスのルートが定義されていないため、 polls ルート http://example_domain.com/polls に移動してください。 /. 投票インターフェースが表示されるはずです:
ここまでの手順で、本番環境に対応したアーキテクチャの構成に成功しました。プロキシサーバーからプロキシされたリクエストを処理する2つのバックエンドサーバーを実装しました。プロキシサーバーは、プロビジョニングされたTLS証明書を使用して、負荷分散とトラフィックのセキュリティ保護を処理します。
ただし、Let’s Encryptの証明書の有効期限は90日であることに注意してください。そのため、90日が経過する前に更新する必要があります。Nginxコンテナが実行されているため、証明書更新のためにcertbotコマンドを実行する際は、 webroot モードを使用し、 standalone モードは使用しないようにしてください。以前に、 /var/www/html/.well-known/acme-challenge/ ディレクトリをNginx設定ファイルで指定したことを思い出してください(Step 3)。Certbotはこのパスを使用して検証ファイルを保存します。また、証明書を更新しようとすると、Let’s Encryptクライアントが検証リクエストを伴ってこのパスを呼び出します。更新コマンドの実行が完了したら、Nginxをリロードして変更を適用できます。
ターミナルで CTRL+C を押してコンテナを終了し、 -d フラグを使用して、デタッチモードで再度起動しましょう。
|
1 2 3 4 5 6 |
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.20.2 |
これにより、Nginxコンテナはバックグラウンドで実行されたままになります。以下のコマンドを実行して、 --dry-run フラグを使用して証明書の更新手順をテストしてみましょう。
|
1 2 3 4 5 |
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 プラグインと、 -w フラグで検証リクエストに使用するパスを指定しました。また、 --dry-run フラグを指定して、実際に証明書をプロビジョニングすることなく自動更新手順を検証します。
シミュレーションが成功すると、以下のような出力が表示されるはずです:
|
1 2 3 4 5 6 7 8 |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Processing /etc/letsencrypt/renewal/example_domain.com.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Simulating renewal of an existing certificate for example_domain.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulations, all simulated renewals succeeded: /etc/letsencrypt/live/hackinroms.com/fullchain.pem (success) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
実行中のアプリケーションの証明書を更新するときは常に、コンテナが新しい証明書の使用を開始できるようにNginxをリロードする必要があります。次のDockerコマンドは、 nginx (コンテナ名をnginxとしたことを思い出してください)コンテナをリロードします:
|
1 |
docker kill -s HUP nginx |
このコマンドは、HUP Unixシグナルを、 nginx Dockerコンテナ内で実行されているNginxプロセスに送信します。これにより、Nginxはその設定をリロードし、更新された証明書の使用を開始します。
プロキシサーバーにTLS/SSLがインストールされ、ウェブサイトがHTTPSで提供されるようになったため、次はバックエンドのアプリサーバーを保護して、プロキシサーバーからのリクエストのみを許可するようにする必要があります。
Step 5: Securing the Backend Django Servers from External Access
このチュートリアルで実装したプロキシサーバーは、SSL接続を復号して暗号化されていないパケットをバックエンドのアプリサーバーに転送するSSL終端を処理します。バックエンドサーバーを外部アクセスから保護するため、ほとんどの場合、このレベルのセキュリティで十分です。ただし、銀行情報や健康データなどの機密データを送信するアプリケーションをデプロイする場合は、以下を実装する必要があります。エンドツーエンド暗号化.
このチュートリアルでは、バックエンドの Gunicorn サーバーは外部に直接公開されることを想定していないため、Nginx によって保護されています。Nginx プロキシサーバーはバックエンドサーバーへのゲートウェイのような役割を果たし、外部クライアントがバックエンドのアプリサーバーに直接アクセスするのを防ぎます。すべてのリクエストがプロキシサーバーを経由するようにする必要があります。そうした状況において、Docker にはufwをバイパスする問題 があり、ファイアウォールルールをすり抜けてポートを外部に開放してしまい、インフラストラクチャのセキュリティが損なわれる可能性があります。これは、実際にステップ 1 および ステップ 2 で、ポート 80 を ufw ルールで許可することなくアプリサーバーをセットアップしたことからも明らかです。しかし、ブラウザでいずれかのサーバーのパブリックIPアドレスにアクセスすると、依然としてウェブページにアクセスできます。この問題を解決する1つの方法は、iptables を、 ufw を経由せずに直接使用することです。詳細については、Docker and iptables の公式ドキュメントを参照してください。もう1つの推奨される方法は、クラウドファイアウォールを使用することです。
Dockerによって開かれた可能性のあるすべてのポートへの外部アクセスをブロックするために、UFWの設定を変更しましょう。ホストのポート 80 をDockerコンテナのポート 8000 に、Dockerコマンドのフラグ -p 80:8000 でマッピングした際、意図せずホストマシンのポート 80 も開いてしまいました。このアクセスを無効にするには、ufw-docker リポジトリ の README に記載されている通りに UFW の設定を変更します。
最初の Django アプリサーバーの設定を変更しましょう。サーバーにログインし、 /etc/ufw/after.rules のファイルを、sudo ユーザーとして nano で開きます:
|
1 |
sudo nano /etc/ufw/after.rules |
このファイルには、以下の ufw ルールが含まれています:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# # rules.input-after # # ufw コマンドラインで追加されたルールの後に実行されるべきルール。カスタム # ルールは以下のチェーンのいずれかに追加する必要があります: # ufw-after-input # ufw-after-output # ufw-after-forward # # これらの必須行は削除しないでください。削除するとエラーが発生します *filter :ufw-after-input - [0:0] :ufw-after-output - [0:0] :ufw-after-forward - [0:0] # 必須行の終わり # デフォルトでノイズの多いサービスをログに記録しない -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 # ノイズの多いブロードキャストをログに記録しない -A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input # 'COMMIT' 行は削除しないでください。削除するとこれらのルールが処理されません COMMIT |
ファイルの末尾に以下の UFW 設定行のブロックを追加します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 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範囲からのアクセスが許可されます。ルールの詳細については、ufw-dockerのREADMEを参照してください。編集が終わったら、ファイルを保存して閉じます。この設定は、仮想プライベートクラウドネットワーク(VPC)をセットアップし、3つのサーバーすべてがそのVPC内にあり、NginxのupstreamディレクティブでDjangoサーバーのプライベートIPを指定している場合に機能します。 設定 ファイル。
しかし、パブリックIPを使用しており、VPCがない可能性があります。そのため、 ufwにルールを追加して、Nginxプロキシサーバーからポート 80を介した両方のDjangoアプリサーバーへのトラフィックを許可する必要があります。許可ルールを ufwに追加し、送信元のサーバーIPからポート 80へのアクセスを、次のコマンドを使用して指定できます。
|
1 |
sudo ufw allow from NGINX_PROXY_IP to any port 80 |
変更が完了したら、Djangoアプリサーバーを再起動して変更を適用します。 sudo ufw reloadを実行するだけでは変更が適用されないようです。
|
1 |
sudo reboot |
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
次に、ブラウザで最初のDjangoサーバーのIPにアクセスして、Pollsインターフェースが表示されるか確認します。 http://FIRST_SERVER_IP/polls。これは失敗します。ここで、最初のサーバーからログアウトし、2番目のサーバーに対してここで行った手順を繰り返します。sudoユーザーとしてnanoで /etc/ufw/after.rulesを開きます。
|
1 |
sudo nano /etc/ufw/after.rules |
先ほどと同様に、一番下までスクロールしてUFW設定ブロックを追加します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 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 に追加し、送信元のserver IPからポート 80への接続を指定するために、次のコマンドを使用します:
|
1 |
sudo ufw allow from NGINX_PROXY_IP to any port 80 |
変更を有効にするためにサーバーを再起動します:
|
1 |
sudo reboot |
サーバーが再起動したら、次のコマンドでコンテナを再度起動します:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
2番目のサーバーのIPアドレスに直接アクセスして、pollsインターフェースが表示されるかテストします: http://SECOND_SERVER_IP/polls。これも失敗するはずです。
これで、このアーキテクチャをテストする準備が整いました。ブラウザから https://example_domain_here/pollsにアクセスして、デフォルトのPollsインターフェースを表示できます。これは、Nginxプロキシサーバーが依然としてDjangoバックエンドサーバーにアクセスできることを意味します。
結論
このガイドでは、Dockerコンテナを使用してスケーラブルなインフラストラクチャを構築する方法を説明しました。このインフラストラクチャには、独立したPostgreSQLデータベースサーバー、2つのバックエンドアプリケーションサーバー、および2つのサーバー間でトラフィックを負荷分散して分散するNginxプロキシサーバーが含まれています。アプリケーションのベースとしてDjango Pollsアプリケーションを使用しましたが、Node.js, Laravelなどの異なるフレームワークを使用して、さまざまなアプリケーション向けにこのアーキテクチャをカスタマイズできます。
これは始めるための基本的なガイドラインです。追加できるいくつかの改善点としては、イメージをDocker Hubなどのイメージリポジトリでホストし、複数のサーバーにイメージを簡単に配布できるようにすることです。また、イベントが発生したときにいつでもアプリサーバーにイメージを自動的にビルド、テスト、デプロイするための継続的インテグレーションおよびデプロイメントパイプラインを追加することもできます。たとえば、イベントとは、gitリポジトリの指定されたブランチに新しいコードをプッシュすることなどです。また、コンテナでエラーが発生したときの動作を自動化することもできます。Dockerの公式ドキュメントには、エラーやシステムの再起動が発生した場合のコンテナの自動起動に関する優れたガイドラインが記載されています。
ハッピーコンピューティング!








コメント
コメントはまだありません。最初のコメントを投稿しましょう。