NGINX Unit+Django(WSGI) Dockerコンテナ構築

DjangoをNGINX/Unitで公開するためのDockerコンテナを作成します。

## 内容

* [前記事][1]で`Windows10+WSL+Docker`環境を構築
* NGINX/Unit+Djangoのコンテナを構築

## カスタマイズ

### NGINX/Unitとは？

[公式サイト][2]に記載の通り、(誤解を恐れず表現すると)NGINXの動的アプリサーバ版です。
[WSGI][3]に沿ってPythonスクリプトを記述することで、動的アプリを公開できます。

* WSGIに準拠したスクリプトを直接起動できる (中間モジュールを必要としない)
* 設定更新を動的に実施できる(ソケットに流し込む)

### Djangoとは？

[Django][4]はPythonの代表的フルスタックフレームワークです。

### 用意するもの

|| 名称 | 内容 |
|---|---|---|
| Docker | Dokerfile | Dockerイメージのレシピ |
|| unit.conf.json | NGINX Unitの設定 |
|| entrypoint.sh | コンテナ起動スクリプト |
|| requirements.pip | `pip freeze`の出力 |
|| .docerignore | [参考サイト][10]  |
| コンテンツ | Pythonスクリプト | Djangoで作成 |

src/
├── Dockerfile
├── .dockerignore
├── unit.conf.json 
├── entrypoint.sh
├── requirements.pip
└── xxx/ (Django)
　　　├── xxx
　　　|　　　├── wsgi.py
　　　|　　　└── etc...
　　　└── polls

※Djangoは[はじめてのDjango][9]あたりを参考に学習してください。
※ディレクトリツリーキレイに書きたい

### Dokerfile

[公式イメージ][6]の[Dockerfile][7]を参考にしつつ作成します。
公式はDebianでPythonバージョンも古いため、UbuntuイメージにNGINX/Unitを導入する形にします。
ENVにユーザーとパスワードを記述している点が気になる方は、適宜ARGに置き換えてください。

```dockerfile
FROM ubuntu:cosmic
ENV UBUNTU_VER cosmic
ENV PY_VER 3.6

SHELL ["/bin/bash", "-c"]
ENV DEBCONF_NOWARNINGS yes

LABEL MAINTAINER="auther"
LABEL version="1.0"
LABEL description=""

ENV CONTENTS ./project
ENV EXPOSE_PORT 8000

ENV USER api
ENV PASSWORD pass
ENV HOME /home/${USER}
ENV PROJECT_NAME xxx
ENV PROJECT_DIR ${HOME}/${PROJECT_NAME}

# initialize
RUN : "locale setting and base package" && \
    set -o pipefail && \
    perl -p -i.bak -e 's%https?://(?!security)[^ \t]+%http://ubuntutym.u-toyama.ac.jp/ubuntu/%g' /etc/apt/sources.list && \
    apt-get -y clean && apt-get -y update && \
    apt-get -qy install \
    sudo \
    tzdata \
    ca-certificates \
    gnupg \
    curl && \
    update-locale LANG=ja_JP.UTF-8 && \
    ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    : "create user" && \
    useradd -m -s /bin/bash ${USER} && \
    gpasswd -a ${USER} sudo && \
    echo "${USER}:${PASSWORD}" | chpasswd && \
    echo "Defaults visiblepw" >> /etc/sudoers && \
    echo "${USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# copy project
WORKDIR ${HOME}

COPY ${CONTENTS} ${HOME}/${PROJECT_NAME}
COPY unit.conf.json ${HOME}
COPY entrypoint.sh ${HOME}
COPY requirements.pip ${HOME}

RUN set -o pipefail && \
    sed \
        -e "s#%%HOME%%#$HOME#g" \
        -e "s#%%PJ%%#$PROJECT_NAME#g" \
        -e "s#%%PORT%%#$EXPOSE_PORT#g" \
        -e "s#%%PYVER%%#$PY_VER#g" \
        ${HOME}/unit.conf.json > ${HOME}/unit-conf.json && \
    chown -R ${USER}:${USER} ${PROJECT_DIR} && \
    chown ${USER}:${USER} ${HOME}/unit.conf.json && \
    chown ${USER}:${USER} ${HOME}/entrypoint.sh && \
    chown ${USER}:${USER} ${HOME}/requirements.pip && \
    ln -sf /dev/stdout ${HOME}/unit.log && \
    chmod 774 ${HOME}/entrypoint.sh

# install Project Package
WORKDIR /root
RUN set -o pipefail && \
    : "nginx unit repogitory" && \
    curl -fsSL "https://nginx.org/keys/nginx_signing.key" | apt-key add - && \
    echo "deb https://packages.nginx.org/unit/ubuntu/ ${UBUNTU_VER} unit" > /root/unit.list && \
    echo "deb-src https://packages.nginx.org/unit/ubuntu/ ${UBUNTU_VER} unit" >> /root/unit.list && \
    cp /root/unit.list /etc/apt/sources.list.d/unit.list && \
    : "project packages" && \
    apt-get -y update && apt-get -qy install \
    unit \
    unit-python${PY_VER} \
    python3 \
    python3-pip

# pip
USER ${USER}
WORKDIR ${HOME}
RUN pip3 install -r ${HOME}/requirements.pip

# start unitd
STOPSIGNAL SIGTERM
EXPOSE ${EXPOSE_PORT}

CMD ["sh", "./entrypoint.sh"]
```

### unit.conf.json

[公式ドキュメント][8]を参考にNGINX/Unitの設定を記述したjson形式のファイルを用意します。
"%%"で囲んでいる箇所はdockerfile内で置換しています。

```nginx
{
  "listeners": {
    "*:%%PORT%%": {
      "application": "%%PJ%%"
    }
  },  
  "applications": {
    "%%PJ%%": {
      "type": "python %%PYVER%%",
      "processes": 5,
      "working_directory": "%%HOME%%/%%PJ%%/",
      "path": "%%HOME%%/%%PJ%%/",
      "module": "%%PJ%%.wsgi",
      "user": "api",
      "group": "api",
      "limits": {
          "timeout": 10,
          "requests": 1000
      }
    }
  },
  "settings": {
      "http": {
          "header_read_timeout": 10,
          "body_read_timeout": 10,
          "send_timeout": 10,
          "idle_timeout": 120,
          "max_body_size": 6291456
      }
  },
  "access_log": "%%HOME%%/access.log"
}
```

### entrypoint.sh

コンテナ生成後に実行するシェルスクリプトです。Dockerfile末尾で実行しています。
NGINX Unitを実行する方法は他にもありますが、今回はシンプルに実行します。

```shell
#!/bin/sh

sudo unitd --control unix:./control.unit.sock --log ./unit.log
sudo curl -fsSL -X PUT -d @./unit-conf.json --unix-socket ./control.unit.sock "http://localhost/config/"
sudo tail -f ./access.log
```

### おまけ: requirements.pip

```
Django==2.1.5
pytz==2018.9
```

## ビルドとコンテナ起動

コンテナイメージのビルドをおこない、起動します。
curl等で疎通確認をおこないます。

~~~shell
$ docker build --no-cache ./ -t proj/api
$ docker run -d --name api -p 8080:8000 proj/api
$ curl http://localhost:8080/polls/
~~~

## 次の記事

| ベースイメージ | 用途 | |
|---|---|---|
| NGINX Unit | APIサーバ |○|
| mysql | MySQLサーバ |←ココ|
| redis | Redisサーバ | |
| まとめる | docker compose | |

* 上記イメージをDockerfileを記述してビルド
* Docker Composeで取りまとめる

[1]:https://qiita.com/arai-h/items/70f592f1ba7075727f38
[2]:https://www.nginx.com/products/nginx-unit/
[3]:https://ja.wikipedia.org/wiki/Web_Server_Gateway_Interface
[4]:https://www.djangoproject.com/
[5]:https://docs.docker.com/samples/library/nginx/
[6]:https://hub.docker.com/r/nginx/unit
[7]:https://github.com/nginx/unit/blob/master/pkg/docker/Dockerfile.python3.5
[8]:http://unit.nginx.org/configuration/
[9]:https://docs.djangoproject.com/ja/2.1/intro/tutorial01/
[10]:https://qiita.com/munisystem/items/b0f08b28e8cc26132212