[06] "まっしろ"対策として Nginx を導入する

# はじめに

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)
・[[05] runserver を Gunicorn へ置き換える](https://qiita.com/robozushi10/items/406ef206efaf8c1f5c39)
・本記事の内容


# 概要

今回は下図・下表の環境を構築する. (前章からの変更点は🔥である)
より具体的な変更点としては次である.
**・Nginx コンテナを稼働させて前章の "まっしろ" を解消する.**
**・外部から Django へのアクセスは Nginx を通すようにする.**

## 概念図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/09c82bda-9bb1-a771-da4f-c27bd1c87090.png)

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

<br/>

## コンテナ内の構成

|項目|値|補足|
|:--|:--|:--|
|Python|バージョン3.8.3|`python:3.8.3-slim-buster` を使う|
|Django|バージョン3.0.7||
|Webサーバ|Nginx🔥|8000番ポートを使用する🔥|
|アプリサーバ|gunicorn|53000番ポートを使用する🔥. <br/>ただし、53000番ポートはホストには公開しない🔥|
|データベース|PostgreSQL 12.0|ポートは次の通り<br/>・ホスト側「53432」<br/>・コンテナ側「5432」<br/>管理者アカウントは次の通り<br/>・アカウント「`admin`」<br/>・パスワード「`admin`」|
|settings.py|次の定義を追加する<br/>・`STATIC_ROOT`🔥<br/>・`MEDIA_URL` 🔥<br/>・`MEDIA_ROOT` 🔥|ホスト上の `./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 🏷️
|-- nginx 🆕
|   |-- Dockerfile 🆕
|   `-- conf.d 🆕
|       `-- nginx.conf 🆕
|-- setupapp.sh 🏷️........... セットアップスクリプト
`-- web
    |-- Dockerfile
    `-- assets .............. コンテナへコピーされるデータ
        |-- entrypoint.sh
        |-- requirements.txt
        |-- sample
        |   `-- shop
        |       |-- admin.py
        |       `-- models.py
        `-- settings.py 🏷️

```

## 解説

### docker-compose.yml

■ Djangoコンテナで「`manage.py collectstatic --noinput`」を実行する.
&nbsp;&nbsp;&nbsp;・これにより **"まっしろ"** が解消する. (詳細は [setupapp.sh](#setupapp.sh) を参照)
■ Djangoコンテナでは 8000番ポートの使用を止めて、代わりに 53000番を使う.
&nbsp;&nbsp;&nbsp;・53000番ポートは同一 LAN コンテナに限定公開する.
&nbsp;&nbsp;&nbsp;・つまり、外部から 53000番ポートにはアクセスできなくする.
■ Nginxコンテナを新たに立ち上げる.
&nbsp;&nbsp;&nbsp;・外部に対して 8000番ポートを公開する.
&nbsp;&nbsp;&nbsp;・Nginx の 8000番ポートと Django の 53000番が繋がっている. (詳細は [./nginx/conf.d/nginx.conf](#nginx%2Fconf.d%2Fnginx.conf) を参照)

```diff
--- ../5/docker-compose.yml	2021-07-10 21:00:24.845598138 +0900
+++ docker-compose.yml	2021-07-10 21:58:09.425220899 +0900
@@ -7,15 +7,16 @@
     container_name: mydjango307
     command: >
       bash -c '
+        python manage.py collectstatic --noinput &&
         gunicorn config.wsgi:application \
           --workers 8 \
           --access-logfile /usr/src/app/log/gunicorn.log \
-          --bind 0.0.0.0:8000
+          --bind 0.0.0.0:53000
       '
     volumes:
       - ./web/app/:/usr/src/app/
-    ports:
-      - 8000:8000
+    expose:
+      - 53000
     depends_on:
       - db
   db:
@@ -32,6 +33,19 @@
         POSTGRES_USER: 'admin'
         POSTGRES_PASSWORD: 'admin'
         PGDATA: '/data'
+  nginx:
+    build: ./nginx
+    image: mynginx0120
+    restart: always
+    container_name: mynginx0120
+    volumes:
+      - ./web/app/static:/var/www/app/static
+      - ./web/app/media:/var/www/app/media
+      - ./nginx/conf.d:/etc/nginx/conf.d
+    ports:
+      - "8000:1337"
+    depends_on:
+      - web
   dockerize:
     image: jwilder/dockerize
     container_name: jwilder_dockerize
```

### nginx/Dockerfile

新規作成. 特筆すべき事項はない.

```dockerfile
FROM nginx:1.20.0-alpine
 
## プロキシサーバを使う場合
# ENV http_proxy="http://proxy.co.jp:8080"	
# ENV https_proxy="http://proxy.co.jp:8080"

RUN rm /etc/nginx/conf.d/default.conf

```

### nginx/conf.d/nginx.conf

・Nginx コンテナの /etc/nginx/conf.d/nginx.conf として配置される.
・下記の定義の読み取り方法を、[図示](#補足)している.

```
upstream django {
    server web:53000;
}
 
server {
    listen 1337;
 
    location / {
        proxy_pass http://django;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host:8000;
        proxy_redirect off;
    }
 
    # 静的ファイルの要求を static にルーティングする
    location /static/ {
        alias /var/www/app/static/;
    }
 
    # 動画ファイルの要求を media にルーティングする
    location /media/ {
        alias /var/www/app/media/;
    }
}
```

#### 補足

・(1)〜(3) がひとつづきである.
・(4)(5) がひとつづきである.
・「Django admin ページ」にアクセスすると、(4)(5)のシーケンスが発生する.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/4eee3714-75d7-886b-7050-1f678a406b19.png)

<br/>

### setupapp.sh

■ 「`python manage.py collectstatic`」により、下図のように静的データ(CSS など)が配置される
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;・`STATIC_ROOT` は Djangoコンテナ と Nginxコンテナで共有されている.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;・`http://localhost:8000/static` にアクセスすると、Nginx が静的データを返してくれる.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;・これにより背景の **"まっしろ"** が発生しなくなる.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/b09d537d-7dbf-53f9-8544-7f6378c0d7fb.png)

<br/>

:::message
本書では MEDIA_ROOT は使わないので記載を省略する.
:::

■ 「`jwilder/dockerize`」による Django の疎通確認先ポートを 8000 から 53000番に変更する.

■ 「`jwilder/dockerize`」による Nginx の疎通確認先ポートを 1337 番とする.


```diff
--- setupapp.sh.ORIG
+++ setupapp.sh
@@ -14,11 +14,15 @@
 # 必須ディレクトリを作成する
 sudo mkdir -p web/app
 sudo mkdir -p web/app/log
+sudo mkdir -p web/app/static
+sudo mkdir -p web/app/media
 sudo mkdir -p db/data
 sudo mkdir -p db/init
 
 # 必要な権限を付与しておく
 sudo chmod 777 web/app/log
+sudo chmod 777 web/app/static
+sudo chmod 777 web/app/media
 
 # entrypoint.sh を配置する
 sudo cp ./web/assets/entrypoint.sh ./web/app/
@@ -34,8 +38,8 @@
 # 次の jwilder/dockerize でタイムアウトになった場合にセットアップスクリプトを停止させるようにする.
 set -e
 
-# 0.5秒周期で Django と PostgreSQL コンテナから応答があるまで待機する. (上限 1分間)
-docker-compose run --rm dockerize sh -c 'dockerize -wait http://web:8000/admin -wait tcp://db:5432 -timeout 1m -wait-retry-interval 0.5s'
+# 0.5秒周期で Django と PostgreSQL と Nginx コンテナから応答があるまで待機する. (上限 1分間)
+docker-compose run --rm dockerize sh -c 'dockerize -wait http://web:53000/admin -wait tcp://db:5432 -wait http://nginx:1337/admin -timeout 1m -wait-retry-interval 0.5s'
 
 # jwilder/dockerize の処理が済んだので、set -e を無効にする.
 set +e
```

### web/assets/settings.py

```diff
--- web/assets/settings.py.ORIG
+++ web/assets/settings.py
@@ -123,3 +123,7 @@
 # https://docs.djangoproject.com/en/3.0/howto/static-files/
 
 STATIC_URL = '/static/'
+STATIC_ROOT = os.path.join(BASE_DIR, 'static')
+MEDIA_URL   = '/media/'
+MEDIA_ROOT  = os.path.join(BASE_DIR, 'media')
+
```

# セットアップ方法

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

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

# セットアップ後

## ファイル構成

```bash
$ tree . --charset=c -a -L 5
.
|-- db
|   |-- Dockerfile
|   |-- data [error opening dir]
|   `-- init
|-- docker-compose.yml
|-- nginx
|   |-- Dockerfile
|   `-- conf.d
|       `-- nginx.conf
|-- 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
    |   |-- media
    |   |-- shop
    |   |   |-- __init__.py
    |   |   |-- admin.py
    |   |   |-- apps.py
    |   |   |-- migrations
    |   |   |   |-- 0001_initial.py
    |   |   |   `-- __init__.py
    |   |   |-- models.py
    |   |   |-- tests.py
    |   |   `-- views.py
    |   `-- static
    |       `-- admin
    |           |-- css .... これより下層の表示は省略している
    |           |-- fonts .. これより下層の表示は省略している
    |           |-- img .... これより下層の表示は省略している
    |           `-- js  .... これより下層の表示は省略している
    `-- 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       53000/tcp                     
mynginx0120         /docker-entrypoint.sh ngin ...   Up       0.0.0.0:8000->1337/tcp, 80/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/d3ec0713-6dfa-369a-d110-cd8dde62e68f.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/ccb608ea-fcd5-aa6a-f555-c8200d7337be.png)

<br/>

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

&nbsp;&nbsp;&nbsp;・下記🛑のように静的データが `/usr/src/app/static/.` に配置された旨のログが出ていること
&nbsp;&nbsp;&nbsp;・53000番ポートを公開していること
&nbsp;&nbsp;&nbsp;・8個のプロセスが稼働していること(下図🦄)

```bash
$ docker-compose logs web | tail -n 13
mydjango307  |
mydjango307🛑| 0 static files copied to '/usr/src/app/static', 130 unmodified.
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/>

以上.