Docker + Flask + uWSGI + Nginxでwebアプリを作成する

flaskでwebアプリケーションを作成した場合に、pythonを実行したものをそのまま本番運用するのは良くない。そこで今回はユーザーとFlaskの間にnginx, uwsgiを経由させる様な構成を構築する。

## イメージ
![S__2637830.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2738475/3f0ecd8f-d2cb-f44d-d88b-e98862f20507.jpeg)


## カレントディレクトリ
本当は整理した方が良い
```
root@ubuntu-19:/home/tmcit/app# ls
app.py    deploy.sh           Dockerfile        static     tmp        web
build.sh  docker-compose.yml  requirements.txt  templates  uwsgi.ini  wsgi.py
```

## Docker-compose.yml
flaskコンテナは、docker-networkを通して、nginxとやりとりをするため、ポート開放をする必要はない。（nginx、flask間のやりとりが上手く行かない場合、障害原因を切り分けるために用いよう。）

```
version: '3'
services:

  flask:
    container_name: flask
    build:
      context: .
      dockerfile: Dockerfile
#    ports:
#      - 80:80
#      - 3031:3031
    restart: always
    tty: true
    volumes:
    - ./flask/requirements.txt /usr/src/app/
    - ./flask/app.py /usr/src/app/app.py
    - ./flask/templates/head.html /usr/src/app/templates/head.html
    - ./flask/static /usr/src/app/static
    environment:
      TZ: Asia/Tokyo


  web:
    container_name: web-server
    hostname: web-server
    build: ./web
    volumes:
    - "./web/nginx.conf:/etc/nginx/nginx.conf"
    - "./tmp/nginx_log:/var/log/nginx"
    ports:
      - "4231:80"
    environment:
      TZ: Asia/Tokyo
```

## flask用dockerfile
flaskとuwsgiは同じコンテナ内で動かす。flaskとuwsgiのインストールは、requirements.txtに書き込む。


```
FROM shomaigu/flask-base:latest

#RUN apt -y install python3 python3-pip curl wget
#RUN apt autoremove
#RUN pip3 install --upgrade pip setuptools
#RUN mkdir -p /usr/src/app/templates
RUN mkdir -p /var/www/

ADD ./requirements.txt /usr/src/app/
ADD ./app.py /usr/src/app/
ADD ./wsgi.py /usr/src/app/
ADD ./templates /usr/src/app/templates
ADD ./static /usr/src/app/static
ADD ./uwsgi.ini /usr/src/app/
RUN pip3 install --no-cache-dir -r /usr/src/app/requirements.txt


WORKDIR /usr/src/app/
#CMD ["python3", "/usr/src/app/app.py"]

CMD ["uwsgi","--ini","/usr/src/app/uwsgi.ini"]
```

### requirements.txt
```
flask
mysql-connector-python
uwsgi
```

## uwsgi.ini
wsgi-file=によって、pythonファイルがどこにあるか指定してあげよう。また、この時、指定するpythonファイルは、flask本体のpythonファイルではなく、後述するflask本体を呼び出すためのpythonファイルである。
また、socketで定義しているのは、pythonコンテナがuwsgiを3031でlistenさせるという設定。

```
[uwsgi]
# pythonモジュールのインポート
app = app:create_app()
module = %(app)

callable = app
master = true
processes = 1

# pythonコンテナはuwsgiを3031でlistenさせる
socket = :3031

# pythonファイル
wsgi-file = /usr/src/app/wsgi.py

logto = /var/log/app-server.log

master = true
processes = 10
vaccum = true
die-on-term = true
max-requests = 100
harakiri = 60

reload-mercy = 5
worker-reload-mercy = 5

ignore-sigpipe=true
ignore-write-errors=true
disable-write-exception=true
```
## wsgi.py
flask本体を呼びだすためのpythonファイル。app.pyと同じ階層に配置する事。
また、この構成にする場合、app.pyのapp.runの記述は削除しておくこと。
```
from app import app

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=80)
```

## app.py
flaskの本体のファイル
wsgi.pyを作る場合は、app.runをコメントアウト

```
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, flash, render_template, request, session
import mysql.connector, re
from datetime import timedelta



app = Flask(__name__)


@app.route("/")
def index():
    return render_template('index.html')

@app.route("/about")
def about():
    return render_template('about.html')

@app.route("/event")
def event():
    return render_template('event.html')

@app.route("/event01")
def event01():
    return render_template('event01.html')


#if __name__ == "__main__":
#    app.run(host="0.0.0.0", port=80)
#    app.run(host="0.0.0.0")
```

## web/Dockerfile
```
FROM nginx:latest

CMD ["nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"]
```

## web/nginx.conf
wsgiが3031で待機しているため、
「server flask（コンテナ名）:3031」
この設定を入れる事によって、nginxが受け取ったリクエストをuwsgiへプロキシする。

また、「listen 80;」によってnginxコンテナが80番でlistenする。

```
# 実行ユーザー
user  nginx;
# 使用可能process数
worker_processes  1;

# エラーログの設定
error_log  /var/log/nginx/error.log warn;
# processidの格納先
pid        /var/run/nginx.pid;

# イベント処理モジュール
events {
    # 最大接続数
    worker_connections  1024;
}

# http関連のモジュール
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    #sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    upstream uwsgi {
        server flask:3031;
    }

    server {
        listen 80;
        charset utf-8;

        location / {
            include uwsgi_params;
            uwsgi_pass uwsgi;
        }

    }
}
```

## 起動
```
docker-compose build
docker-compose up
```

## アクセス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2738475/e62dc6dd-4de9-2eb2-5422-2ff96dce983a.png)