[05] runserver を Gunicorn へ置き換える

# はじめに

Docker (docker-compose)と Linux に標準で搭載されているシェルを組み合せて、
下記 [概要](#概要)に挙げた Django 環境を 1コマンドで即座に立ち上げるためのコードとその解説です.

本記事の内容は、Zenn で[書籍](https://zenn.dev/robozushi10/books/3d32504b4b5707)として販売している内容と同一です.
Zenn でも書籍の内容は全て無料公開しています.

次の順でコードを作成してください.
動作するコードが作成できるように可能な限り注意を払って執筆しました.

・[[01] デフォルト環境 runserver + SQLite3 の構築](https://qiita.com/robozushi10/items/fc8e40ad7ecc29131ea3/)
・[[02] SQLite3 を PostgreSQL へ置き換える](https://qiita.com/robozushi10/items/7dc07ab6412b285c75da)
・[[03] Django アプリの立ち上げもさせる](https://qiita.com/robozushi10/items/bda4e5f1c743c4c26af5)
・[[04] jwilder/dockerize を使ってセットアップ処理を改良する](https://qiita.com/robozushi10/items/3d955c5c50d845c73e82)
・本記事の内容


# 概要

今回は下図・下表の環境を構築する. (前章からの変更点は🔥である)
より具体的な変更点としては次である.
**・アプリサーバを runserver から gunicorn に置き換える**

## 概念図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/34dbe40c-634b-c752-9d02-a8642d3b9c8b.png)

:::message
図を簡単にするために、jwilder/dockerize の線は省略している.
:::

## コンテナ内の構成

|項目|値|補足|
|:--|:--|:--|
|Python|バージョン3.8.3|`python:3.8.3-slim-buster` を使う|
|Django|バージョン3.0.7||
|アプリサーバ|gunicorn🔥|8000番ポートを使用する|
|データベース|PostgreSQL 12.0|ポートは次の通り<br/>・ホスト側「53432」<br/>・コンテナ側「5432」<br/>管理者アカウントは次の通り<br/>・アカウント「`admin`」<br/>・パスワード「`admin`」|
|settings.py| |ホスト上の `./web/assets/settings.py` が、<br/>コンテナ内に `/usr/src/app/config/settings.py` <br/>として配置される.|
|Django admin ページ||管理者アカウントは次の通り<br/>・アカウント「`admin`」<br/>・パスワード「`admin`」|
|アプリケーション|アプリ名「shop」|**各ユーザが使用したいアプリに置き換えてほしい**|
|コンテナ起動確認ツール|[jwilder/dockerize](https://github.com/jwilder/dockerize)||

# セットアップコード

## 全文

前章で作成したコードに対して、本記事の内容を適用してください.

:::message
web/assets/settings.py より、「DEBUG = True」と「DEBUG = False」の両パターンでセットアップできることを確認している.
:::

## ファイル構成

上記 GitHub からコードを取得してきた直後の構成である.
前章から変更が生じたファイルには🏷️を、新規作成については🆕を付与している.

```bash
.
|-- db
|   `-- Dockerfile
|-- docker-compose.yml 🏷️
|-- setupapp.sh 🏷️........... セットアップスクリプト
`-- web
    |-- Dockerfile
    `-- assets .............. コンテナへコピーされるデータ
        |-- entrypoint.sh
        |-- requirements.txt 🏷️
        |-- sample
        |   `-- shop
        |       |-- admin.py
        |       `-- models.py
        `-- settings.py
```

## 解説

### docker-compose.yml

・runserver の代わりに gunicorn を起動させる.
・`./config/wsgi.py` というファイル構成のため、引数を「`config.wsgi:application`」としている.
・gunicorn は 8プロセスで 8000番ポートで待ち受けする.
・gunicorn の ログを /usr/src/app/log/gunicorn.log として出力する.

```diff
--- docker-compose.yml.ORIG
+++ docker-compose.yml
@@ -6,7 +6,12 @@
     image:  mydjango307
     container_name: mydjango307
     command: >
-      bash -c 'python manage.py runserver 0.0.0.0:8000'
+      bash -c '
+        gunicorn config.wsgi:application \
+          --workers 8 \
+          --access-logfile /usr/src/app/log/gunicorn.log \
+          --bind 0.0.0.0:8000
+      '
     volumes:
       - ./web/app/:/usr/src/app/
     ports:
```

### setupapp.sh

■ gunicorn のログ出力先として ./web/app/log/ を作成する.

■ ./web/app/log/ に対して書き込みができるように権限を付与する.

■ 「`jwilder/dockerize`」コンテナから Django コンテナに対して疎通確認をする.
&nbsp;&nbsp;&nbsp;・リクエスト先を「Django admin ページ」すなわち  `http://localhost:53000/admin` に変更する.
&nbsp;&nbsp;&nbsp;・前章のように "ルートパス" だと「DEBUG = False」時に**疎通できず** に「404」(下表🛑)と判断されてしまう.

|settings.py の DEBUG の値|問合せ先 URL|レスポンス(HTTPステータスコード)|
|:--|:--|:--|
| True | `http://localhost:53000` | 200 |
| **False** | `http://localhost:53000` | **404**🛑 |


```diff
--- setupapp.sh.ORIG
+++ setupapp.sh
@@ -13,9 +13,13 @@
 
 # 必須ディレクトリを作成する
 sudo mkdir -p web/app
+sudo mkdir -p web/app/log
 sudo mkdir -p db/data
 sudo mkdir -p db/init
 
+# 必要な権限を付与しておく
+sudo chmod 777 web/app/log
+
 # entrypoint.sh を配置する
 sudo cp ./web/assets/entrypoint.sh ./web/app/
 # settings.py をコンテナ内に .settings.py として配置する
@@ -31,7 +35,7 @@
 set -e
 
 # 0.5秒周期で Django と PostgreSQL コンテナから応答があるまで待機する. (上限 1分間)
-docker-compose run --rm dockerize sh -c 'dockerize -wait http://web:8000 -wait tcp://db:5432 -timeout 1m -wait-retry-interval 0.5s'
+docker-compose run --rm dockerize sh -c 'dockerize -wait http://web:8000/admin -wait tcp://db:5432 -timeout 1m -wait-retry-interval 0.5s'
 
 # jwilder/dockerize の処理が済んだので、set -e を無効にする.
 set +e

```

### web/assets/requirements.txt

・pip で `gunicorn` をインストールするように定義を追加する.

```diff
--- web/assets/requirements.txt.ORIG
+++ web/assets/requirements.txt
@@ -4,3 +4,4 @@
 djangorestframework==3.11.0
 psycopg2==2.8.5
 psycopg2-binary==2.8.5
+gunicorn==20.0.4

```

# セットアップ方法

次の通りコマンドを実行する.

```
$ ./setupapp.sh
```

# セットアップ後

## ファイル構成

```bash
$ tree . --charset=c -a 
.
|-- db
|   |-- Dockerfile
|   |-- data [error opening dir]
|   `-- init
|-- docker-compose.yml
|-- setupapp.sh
`-- web
    |-- Dockerfile
    |-- app
    |   |-- .settings.py
    |   |-- config
    |   |   |-- __init__.py
    |   |   |-- asgi.py
    |   |   |-- settings.py
    |   |   |-- urls.py
    |   |   `-- wsgi.py
    |   |-- entrypoint.sh
    |   |-- log
    |   |   `-- gunicorn.log
    |   |-- manage.py
    |   `-- shop
    |       |-- __init__.py
    |       |-- admin.py
    |       |-- apps.py
    |       |-- migrations
    |       |   |-- 0001_initial.py
    |       |   `-- __init__.py
    |       |-- models.py
    |       |-- tests.py
    |       `-- views.py
    `-- assets
        |-- entrypoint.sh
        |-- requirements.txt
        |-- sample
        |   `-- shop
        |       |-- admin.py
        |       `-- models.py
        `-- settings.py
```

## 動作確認方法

次の点を確認すれば良い.

✔ Dockerコンテナが次のように起動していること

コンテナ「`jwilder_dockerize`」は使い捨て型なので、Exit 2 は意図した結果であり問題無し.

```bash
$ docker-compose ps
      Name                     Command               State             Ports         
-------------------------------------------------------------------------------------
jwilder_dockerize   dockerize --help                 Exit 2                          
mydjango307         /usr/src/app/entrypoint.sh ...   Up       0.0.0.0:8000->8000/tcp 
mypostgres12        docker-entrypoint.sh postgres    Up       0.0.0.0:53432->5432/tcp
```

<br/>

✔ http://localhost:8000 にアクセスして次の画面が表示されること.

&nbsp;&nbsp;&nbsp;このとき、背景画像が適用されておらず **"まっしろ"** になっていること

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/eb04ff8f-d3e8-1999-edc8-85c9a82c77c0.png)

<br/>

✔ http://localhost:8000/admin にアクセスして、アカウント「`admin`」、パスワード「`admin`」でログインできること.

&nbsp;&nbsp;&nbsp;このとき、背景画像が適用されておらず **"まっしろ"** になっていること

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/4ca9b15f-1f8e-5fff-1f4a-c152d695f0c7.png)

<br/>

✔ Djangoコンテナのログに Gunicorn が起動したログが表示されていること

&nbsp;&nbsp;&nbsp;・53000番ポートを公開していること(下図🛑)
&nbsp;&nbsp;&nbsp;・計8個のプロセスが稼働していること(下図🦄)

```bash
$ docker-compose logs web | tail -n 11
mydjango307  | [2021-07-11 03:16:59 +0900] [1] [INFO] Starting gunicorn 20.0.4
mydjango307🛑| [2021-07-11 03:16:59 +0900] [1] [INFO] Listening at: http://0.0.0.0:53000 (1)
mydjango307  | [2021-07-11 03:16:59 +0900] [1] [INFO] Using worker: sync
mydjango307  | [2021-07-11 03:16:59 +0900] [9] [INFO] Booting worker with pid: 9
mydjango307🦄| [2021-07-11 03:16:59 +0900] [10] [INFO] Booting worker with pid: 10
mydjango307🦄| [2021-07-11 03:16:59 +0900] [11] [INFO] Booting worker with pid: 11
mydjango307🦄| [2021-07-11 03:17:00 +0900] [12] [INFO] Booting worker with pid: 12
mydjango307🦄| [2021-07-11 03:17:00 +0900] [13] [INFO] Booting worker with pid: 13
mydjango307🦄| [2021-07-11 03:17:00 +0900] [14] [INFO] Booting worker with pid: 14
mydjango307🦄| [2021-07-11 03:17:00 +0900] [15] [INFO] Booting worker with pid: 15
mydjango307🦄| [2021-07-11 03:17:00 +0900] [16] [INFO] Booting worker with pid: 16
```

<br/>

以上.