見出し画像

Djangoを使ったWebアプリの作り方 ⑤Docker+Nginx+Gunicorn編

DjangoでWebアプリを本番環境で公開する場合、NginxとGunicornを使用すると思います。その理由について説明し、実際の設定手順について説明します。

1. Django簡易サーバーで公開するデメリット

Djangoには、開発中にアプリを動かすための簡易サーバーが付いています。
例えば、python manage.py runserverとコマンドを実行すると、自分のパソコン上でアプリをすぐに動かせます。

しかし、簡易サーバーは、基本的に1つのリクエストを1つずつ順番に処理します。そのため、例えば100人が同時にアクセスしてきたら、簡易サーバーはすぐにパンクしてしまいます。

また、簡易サーバーにはセキュリティ機能(暗号化、攻撃への対策)がほとんどありません。例えば、ログイン情報を盗まれる危険がある、大量リクエスト(DDoS攻撃)を防ぐ機能がないというリスクがあります。

そのため、Gunicorn(アプリケーションサーバー)やNginx(Webサーバー)を組み合わせて、速くて安全な運用をするのが標準的な方法です。

2. Nginx+Gunicornの役割

Nginx+Gunicornでサイトを公開する場合、NginxとGunicornのそれぞれの役割を知ることが重要になります。以下で、NginxとGunicornの役割について説明します。

2.1 Nginxの役割

Nginxは、WebサーバーとしてGunicornの手助けをする役割を持っています。Nginxがインターネットからのリクエストを最初に受け取り、Gunicornに渡します。

  • リバースプロキシ: Nginxはリバースプロキシ(仲介役)として機能します。クライアントからのリクエストを受け取り、適切なアプリケーションサーバ(この場合はGunicorn)に転送します。これにより、アプリケーションサーバが直接外部からのリクエストを処理する必要がなくなり、セキュリティが向上します。料理店に例えると、以下役割になります。

    • お客様(クライアント):Webサイトを利用するユーザー

    • お店の受付(リバースプロキシ):Nginx

    • 厨房(アプリケーションサーバ):GunicornやDjangoアプリ

  • 静的ファイルの処理: Nginxは静的ファイル(画像、CSS、JavaScriptなど)を効率的に処理することができます。これにより、アプリケーションサーバは動的コンテンツの生成に専念でき、全体のパフォーマンスが向上します。料理店に例えると、以下役割になります。

    • 静的ファイル(画像やCSSなど)は受付(Nginx)が直接処理し、厨房に渡しません。

    • 厨房は「料理(動的な処理)」だけに集中できるので、効率的です。

  • 負荷分散とスケーラビリティ: Nginxは負荷分散機能を持ち、複数のアプリケーションサーバにリクエストを分散することで、アプリケーションのスケーラビリティを向上させることができます。料理店に例えると、以下役割になります。

    • 例えば、厨房Aが忙しければ、厨房Bに注文を回す、といった調整ができます。

2.2 Gunicornの役割

Gunicornは、Pythonで作ったDjangoアプリを効率よく動かすための「アプリケーションサーバー」です。ブラウザ(Nginx)からのリクエストをDjangoに渡して処理します。処理する速度が速く、複数のリクエストを同時に処理できます。

  • WSGIサーバ: GunicornはPythonのWSGI(Web Server Gateway Interface)をサポートするアプリケーションサーバであり、Djangoアプリケーションを実行するためのインターフェースを提供します。これにより、HTTPリクエストをPythonの関数呼び出しに変換し、アプリケーションがそれを処理できるようにします。料理店に例えると、以下役割になります。

    • Nginx(Webサーバ):お客様(ブラウザ)から注文を受ける。

      • 注文は「HTTP」という形式のメッセージで書かれています。

    • Django(アプリケーション):注文を処理するシェフ。

      • シェフ(Django)は「Pythonの命令」という形式でしか仕事ができません。

    • Gunicorn(WSGIサーバ):翻訳家。

      • お客様(Nginx)が渡してきた注文(HTTPリクエスト)を、シェフ(Django)が理解できる形式(Pythonの関数)に翻訳します。

      • 逆に、シェフが作った料理(処理結果)もお客様が理解できる形式(HTTPレスポンス)に翻訳します。


  • パフォーマンスの向上: Gunicornはプリフォークモデルを採用しており、複数のワーカープロセスを生成して同時接続を処理することで、パフォーマンスを向上させます。料理店に例えると、以下役割になります。

    • Gunicornでは、リクエストを処理する「ワーカー(料理人)」を複数用意します。
      これにより、同時に複数のお客様を対応することができるようになります。

      • 1人の料理人だけの場合

        • お客様が1人ずつ順番を待つので、待ち時間が長くなります。

        • 忙しい時間帯には、料理が間に合いません。

      • 複数の料理人がいる場合

        • 料理人が多いほど、同時にたくさんの注文を処理できます。

        • 各お客様の待ち時間が短くなり、満足度も上がります。

    • Gunicornのプリフォークモデルは、この「料理人を最初から複数用意しておく」仕組みです。Gunicornでは、ワーカーの数を指定できます(例えば4人、8人など)。これは、アプリケーションの規模やサーバーの性能に応じて調整可能です。

これらのツールを組み合わせることで、Djangoアプリケーションはより効率的かつ安全に運用されるようになります。Nginxがリクエストのフロントエンドを担当し、Gunicornがバックエンドでアプリケーションロジックを処理するという役割分担が、全体のシステムの安定性と効率性を高めます。

4. 具体的な設定例

Nginx+Gunicornを使ってサイト公開する手順を以下に示します。以前参考サイトで作成したDjangoプロジェクト(Dcoker対応済)から変更を実施していきます。(以下サイトのDjangoプロジェクトを作成した後から手順を記載しています。)

4.1 必要なパッケージのインストール:

今回は、Gunicornを使用するため、gunicornというライブラリをインストールします。これにより、Djangoがgunicornと連携できるようになります。
今回は仮想環境にpipenvを使用しているので、pipenvでgunicornをインストールします。

pipenv install gunicorn

インストールされると、Pipfile.lockにgunicornが追加されます。

Pipfile.lock画面

4.2 環境変数の設定:

Nginx+Gunicornでサイトを公開する際に、環境変数としてDEBUGの設定を.envファイルに追記します。また、DjangoのSECRET_KEYについても追記します。

POSTGRES_NAME=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_HOST=db
POSTGRES_PORT=5432

DEBUG=False                        # << 追記
SECRET_KEY='hogehgoe'              # << 追記(settings.pyに記載されているSECRET_KEYを記載)

'''
# ディレクトリ構成
├── Django_Project                   # << カレントディレクトリ
    ├── manage.py
    ├── db.sqlite3
    ├── Pipfile
    ├── Pipfile.lock
    ├── docker-compose.yml
    ├── .env                         # << 修正
    ├── .gitignore 
    ├── containers/
    |   ├── django
    |      ├── Dcokerfile
    |      ├── entorypoint.sh
    |   ├── postgres
    |      ├── Dcokerfile
    │
    ├── mysite/
    │   ├── __init__.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    │   └── asgi.py
    │
    └── myapp/
        ├── migrations/
        ├── templates/
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── forms.py
        ├── models.py
        ├── tests.py
        ├── urls.py
        └── views.py
# コードの説明
DEBUG=False
>>本番環境のためFalseを設定。
 DEBUGがFalseの場合、本番モードとなり、エラー画面に詳しい情報が表示されない。(セキュリティ対策)
'''

4.3 Djangoの設定ファイルを変更:

mysite/settings.pyファイルを開き、4.2項で追加したDEBUGとSECRET_KEYについて以下のように変更します。また、ALLOWD_HOSTSに、'localhost'、'127.0.0.1'の2つを記載します。

# 環境変数からSECRET_KEYを取得する設定
SECRET_KEY = os.environ.get('SECRET_KEY')

# 環境変数からDEBUGを取得。デフォルトはTrue(本番環境モード)
DEBUG = os.environ.get('DEBUG') == True

# 許可するホストを記載
ALLOWED_HOSTS = ['localhost', '127.0.0.1']

'''
# ディレクトリ構成
├── Django_Project                   # << カレントディレクトリ
    ├── manage.py
    ├── db.sqlite3
    ├── Pipfile
    ├── Pipfile.lock
    ├── docker-compose.yml
    ├── .env
    ├── .gitignore 
    ├── containers/
    |   ├── django
    |      ├── Dcokerfile
    |      ├── entorypoint.sh
    │
    ├── mysite/
    │   ├── __init__.py
    │   ├── settings.py                   # << 修正
    │   ├── urls.py
    │   └── wsgi.py
    │   └── asgi.py
    │
    └── myapp/
        ├── migrations/
        ├── templates/
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── forms.py
        ├── models.py
        ├── tests.py
        ├── urls.py
        └── views.py
'''

'''

# コードの説明
SECRET_KEY = os.environ.get('SECRET_KEY')
>>環境変数に保存されているSECRET_KEYの値を取得し、DjangoのSECRET_KEYに設定しています。

DEBUG = os.environ.get('DEBUG') == True
>>環境変数からDEBUGという値を取得し、それがTrueかどうかを確認して設定します。
 環境変数に何も設定されていない場合、デフォルトでDEBUGはTrueになります。
 環境変数がTrueの場合、DEBUGはTrueとなります。
 環境変数がFalseの場合、DEBUGはFalseとなります。
 DEBUGがTrueの場合、開発モードとなり、エラー画面に詳しい情報を表示してくれる。
 DEBUGがFalseの場合、本番モードとなり、エラー画面に詳しい情報は表示されない。

ALLOWED_HOSTS = ['localhost', '127.0.0.1']
>>DjangoアプリがどのドメインやIPアドレスからのリクエストを受け付けるかを設定しています。
 localhost:開発中の自分のPCからのアクセス。
 127.0.0.1:同じく自分のPCを指すIPアドレス。

 Djangoではセキュリティのため、許可されたホスト以外からのアクセスをブロックします。
 本番環境では自分のドメイン名やサーバーのIPアドレスをここに追加します。
'''

4.4 Nginxコンテナの設定:

今回は、Dockerを使って環境を構築しているため、NginxもDockerを使って設定していきます。
まずはNginx用のDockerfile作成します。
以下内容を./containsers/Nginx/Dockerfileに記載します。

FROM nginx:1.21-alpine

COPY containers/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf

'''
# ディレクトリ構成
├── Django_Project                   # << カレントディレクトリ
    ├── manage.py
    ├── db.sqlite3
    ├── Pipfile
    ├── Pipfile.lock
    ├── docker-compose.yml
    ├── .env
    ├── .gitignore 
    ├── containers/
    |   ├── django/
    |      ├── Dcokerfile
    |      ├── entorypoint.sh
    |   ├── postgres/
    |      ├── Dcokerfile
    |   ├── nginx/                          # << 作成
    |      ├── Dcokerfile                   # << 作成
    |      ├── conf.d/                      # << 作成
    |         ├── default.conf              # << 作成
    │
    ├── mysite/
    │   ├── __init__.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    │   └── asgi.py
    │
    └── myapp/
        ├── migrations/
        ├── templates/
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── forms.py
        ├── models.py
        ├── tests.py
        ├── urls.py
        └── views.py

# コードの説明
FROM nginx:1.21-alpine
>>nginx:公式のNginx Dockerイメージ。
 1.21:Nginxのバージョン。
 alpine:Alpine Linuxという軽量なOSを使ったバージョン。

COPY containers/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
>>ローカル(自分のPC)にある設定ファイルcontainers/nginx/conf.d/default.confを、Nginxコンテナ内の/etc/nginx/conf.d/default.confにコピーします。
 この設定ファイルは、Nginxがどのようにリクエストを処理するかを定義するものです。
'''

次に、NginxがWebサーバーとしてどのようにリクエストを処理するかを設定します。設定ファイル(./containsers/Nginx/conf.d/default.conf)を作成します。
default.confには、NginxがDjangoアプリや静的ファイルをどこから取得し、どうユーザーに返すかを指定しています。

upstream django {
    server app:8000;
}

server {
    listen 80;

    location / {
        proxy_pass http://django;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

    location /static/ {
        alias /static/;
    }
}

'''
# ディレクトリ構成
├── Django_Project                   # << カレントディレクトリ
    ├── manage.py
    ├── db.sqlite3
    ├── Pipfile
    ├── Pipfile.lock
    ├── docker-compose.yml
    ├── .env
    ├── .gitignore 
    ├── containers/
    |   ├── django
    |      ├── Dcokerfile
    |      ├── entorypoint.sh
    |   ├── postgres
    |      ├── Dcokerfile
    |   ├── nginx/
    |      ├── Dcokerfile
    |      ├── conf.d/
    |         ├── default.conf              # << 修正
    │
    ├── mysite/
    │   ├── __init__.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    │   └── asgi.py
    │
    └── myapp/
        ├── migrations/
        ├── templates/
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── forms.py
        ├── models.py
        ├── tests.py
        ├── urls.py
        └── views.py

# コードの説明
upstream django {
    server app:8000;
}
>>djangoという名前のグループを作成。
 このグループの中に、Djangoアプリを動かしているサーバー(app:8000)を登録。
 app:8000 は「Djangoアプリが動いている場所(IPアドレスとポート番号)」です。
 この設定で、NginxはDjangoアプリがどこにいるかを認識できます。

server {
    listen 80;

    location / {
        proxy_pass http://django;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }
>>serverの役割:このセクションでは、Nginxがどのリクエストをどこに渡すかを定義します。
 listen 80:Nginxが「ポート80」でリクエストを受け付けるよう設定。
 location /の意味:/は、Webサイトの「すべてのリクエスト」を指します。
 つまり、http://example.com/にアクセスしたときの処理を定義します。

>>proxy_pass http://django;:/へのリクエストを、先ほど定義したdjangoグループ(Djangoアプリが動いている場所)に転送します。
 ここで、Djangoアプリが動いているGunicornにリクエストが渡されます。

>>proxy_set_header関連の設定:X-Forwarded-Forは、リクエスト元のユーザーのIPアドレスを追跡するための情報。
 Hostは、ブラウザで入力されたホスト名(例:example.com)。

>>proxy_redirect off;:リダイレクトが発生しても、元のURLをそのまま保つようにする。

location /static/ {
    alias /static/;
}
>>location /static/の意味:/static/に関連するリクエストを処理します。
 例えば、http://example.com/static/style.cssのようなリクエストです。
 alias /static/;:/static/というリクエストが来た場合、サーバー内の/static/フォルダからファイルを返す設定です。
 静的ファイル(CSSや画像など)はDjangoを通さず、Nginxが直接配信することで効率化しています。

'''

次に、docker-composeファイルにも、Ngnixの設定を追記します。ただ、今回は本番環境を想定しているため、./docker-compose.prod.ymlファイルを新たに作成し記載していきます。
(基本的には、docker-compose.ymlの内容と一緒ですが、修正・追加
する内容を#でコメントしています)

services:
  db:
    container_name: postgres

    build:
      context: .
      dockerfile: ./containers/postgres/Dockerfile

    volumes:
      - db_data:/var/lib/postgresql/data

    ports:
      - "5432:5432"

    env_file:
      - .env

  app:
    container_name: app

    build:
      context: .
      dockerfile: ./containers/django/Dockerfile

    volumes
      - .:/code

    expose:                                        # << 修正
      - "8000"                                     # << 修正

    command: sh -c "/usr/local/bin/entrypoint.sh"

    env_file:
      - .env

    depends_on:
      - db

  web:                                             # << 追加
    container_name: web                            # << 追加

    build:                                         # << 追加
      context: .                                   # << 追加
      dockerfile: ./containers/nginx/Dockerfile    # << 追加

    volumes:                                       # << 追加
      - ./static:/static                           # << 追加

    ports:                                         # << 追加
      - "80:80"                                    # << 追加

    depends_on:                                    # << 追加
      - app                                        # << 追加

volumes:
  db_data:


'''
# ディレクトリ構成
├── Django_Project                   # << カレントディレクトリ
    ├── manage.py
    ├── db.sqlite3
    ├── Pipfile
    ├── Pipfile.lock
    ├── docker-compose.yml
    ├── docker-compose.prod.yml                 # << 作成
    ├── .env
    ├── .gitignore 
    ├── containers/
    |   ├── django
    |      ├── Dcokerfile
    |      ├── entorypoint.sh
    |   ├── postgres
    |      ├── Dcokerfile
    |   ├── nginx/
    |      ├── Dcokerfile
    |      ├── conf.d/
    |         ├── default.conf
    │
    ├── mysite/
    │   ├── __init__.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    │   └── asgi.py
    │
    └── myapp/
        ├── migrations/
        ├── templates/
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── forms.py
        ├── models.py
        ├── tests.py
        ├── urls.py
        └── views.py

# コードの説明
expose:
  - "8000"
>>この設定は、Dockerコンテナ内で動作するアプリケーション(app)が、別のコンテナからアクセス可能になるポートを指定するものです。
 exposeは、コンテナ間通信に使うポートを公開するための設定です。
 8000ポートを公開して、他のコンテナがこのアプリ(appコンテナ)に通信できるようにします。
 exposeはコンテナ間通信専用であり、ホスト(外部)には公開されません。
 (外部(自分のPCやブラウザ)からはアクセスできませんが、同じDockerネットワーク内の他のコンテナからはアクセス可能です。)

web:
  container_name: web
>>Docker Composeでサービス(コンテナ)の名前をwebと定義しています。

build:
  context: .
  dockerfile: ./containers/nginx/Dockerfile
>>ビルドの設定をしています。
 現在のディレクトリ内のファイルを使ってDockerイメージを作ります。
 ./containers/nginx/DockerfileというパスにあるDockerfileを使って、Nginxイメージをビルドします。

volumes:
  - ./static:/static
>>ローカル(ホスト)の./staticフォルダをコンテナ内の/staticにマウントします。
 これにより、Nginxコンテナが静的ファイル(CSSや画像など)をホスト側から直接読み取れるようになります。

ports:
  - "80:80"
>>ホスト(自分のPC)とコンテナの間で、どのポートを接続するかを指定します。
 ホストのポート80 → コンテナのポート80 にリクエストを転送。

depends_on:
  - app
>>この設定は、webサービス(Nginx)が起動する前に、appサービスが起動する必要があることを指定します。
'''

4.5 コンテナのビルドと起動:

以下のコマンドを実行して、Dockerイメージをビルドし、コンテナを起動します。今回は、docker-compose.prod.ymlファイルからビルドを行うため、以下コードを実行します。

docker-compose -f docker-compose.prod.yml up -d --build

'''
# コードの説明
docker-compose up:
>>Docker Composeで定義されたサービス(コンテナ)を起動します。

-d(デタッチドモード):
>>コンテナをバックグラウンドで実行します。
 つまり、ターミナルを占有せずに他の作業ができます。

--build:
>>各サービスのDockerイメージをビルドしてから起動します。
 Dockerfileやコードに変更があった場合、新しいイメージを作成します。

-f:
>>使用するComposeファイルを指定します。
通常、Docker Composeはデフォルトでdocker-compose.ymlを使用します。
ここではdocker-compose.prod.ymlという別のファイルを指定しています。
'''

以上を行うと、Nginx+Gunicornを使ってWebサイトを公開することができます。ユーザー数が多いアプリケーションでは、Nginx+Gunicornを使ってサイトを公開する事が推奨されます。

5. まとめ

NginxとGunicornの役割の違いについて説明しました。そして、Nginx+Gunicornの設定方法について記載しました。作業自体はそれほど大変ではなかったと思います。




いいなと思ったら応援しよう!