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が追加されます。
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の設定方法について記載しました。作業自体はそれほど大変ではなかったと思います。