概要
Docker上での、1つのuWSGI+Flaskコンテナで複数アプリを運用するスタイルは、Blueprintなどを駆使すれば可能です。
しかし、Blueprintの仕様のせいでディレクトリ構造が複雑になってしまったり、ルーティングの際に他のサービスに影響を与えてしまう可能性があります。また、Flaskには得体の知れないのハマりどころ・バグが所々にあり*1*2、無駄な時間を費やさないために複雑な構成をなるべく避けるべきだと感じていました。
今回は、今後の本格的なWebアプリ開発を見据えて、保守性の向上やプロトタイピングの高速化のためにも Flask+uWSGIコンテナをアプリ・サービスごとに作る構成*3を作ってみました。
イメージとしては以下のような感じです。
Nginxコンテナ ├ (http://example.com) - website ├ (http://sub-1.example.com) - uWSGI - Flask app_1コンテナ - … ├ (http://sub-2.example.com) - uWSGI - Flask app_2コンテナ - … … └ (http://sub-n.example.com) - uWSGI - Flask app_n - …
ベース:NginxコンテナとuWSGI+Flaskコンテナ
https://qiita.com/trrrrrys/items/a905f1382733dfb9c8c1qiita.com
こちらを参考に組めば、Nginxコンテナと、uWSGI+Flaskコンテナ1つを立ち上げるところまでは簡単にできます。
今回はこれをベースに、uWSGI+Flaskコンテナを複数用意することで、複数アプリの開発・運用が簡単にできるようになります。
独自ドメインのサブドメイン登録
全てのドメイン、サブドメインで同じようにDNSレコード(A)を設定します。
※Let's EncryptはDNS にサブドメインのレコードを問い合わせるため、SSL 対応より先に実施してください。
SSL 対応(Let's Encrypt)
Step 1を実行します。example.com
と"YOUR_EMAIL"
は書き換えてください。
docker run --rm \ -p 443:443 -p 80:80 --name letsencrypt \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ certbot/certbot certonly -n \ -m "YOUR_EMAIL" \ -d example.com \ --standalone --agree-tos
実行後、/etc/letsencrypt/live/
の中を確認し、(サブ)ドメインのディレクトリ名が存在するかどうかを確認するとよいと思います。
各設定ファイルの編集
方針
Flask アプリとして、app1 と app2を作ります。
NginxとのTCPソケット通信のため、それぞれポートを割り振ります。
- app1: 3031
- app2: 3032
また、subdomainは
- app1: app1.example.com
- app2: app2.example.com
とします。
ディレクトリ構造
flaskapp/ ├ app1/ │ ├ Dockerfile │ ├ requirements.txt │ ├ uwsgi.ini │ └ src/ │ └ run.py │ ├ app2/ │ ├ Dockerfile │ ├ requirements.txt │ ├ uwsgi.ini │ └ src/ │ └ run.py │ └ nginx/ ├ Dockerfile └ nginx.conf
docker-compose.yml
nginx
、uwsgi_app1
、uwsgi_app2
の3つのコンテナを立ち上げます。
version: "3.2" services: uwsgi_app1: build: ./app1 volumes: - ./app1:/var/www/ ports: - "3031:3031" uwsgi_app2: build: ./app2 volumes: - ./app2:/var/www/ ports: - "3032:3032" nginx: image: nginx volumes: - /etc/letsencrypt:/etc/letsencrypt - /usr/share/nginx/html:/usr/share/nginx/html - ./nginx/nginx.conf:/etc/nginx/nginx.conf links: - uwsgi_app1 - uwsgi_app2 ports: - 80:80 - 443:443
uwsgi.ini
app1/uqsgi.ini では、socket = :3031
となっています。
app2/uqsgi.iniでは、socket = :3032
となることに注意してください。
[uwsgi] wsgi-file = /var/www/src/run.py callable = app master = true processes = 1 socket = :3031 chmod-socket = 666 vacuum = true die-on-term = true py-autoreload = 1
nginx.conf
SSLへのリダイレクトもルーティングを書いています。
upstream
ssl_certificate
,ssl_certificate_key
localion
のuwsgi_pass
user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } 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; keepalive_timeout 65; upstream uwsgi_app1 { server uwsgi_app1:3031; } upstream uwsgi_app2 { server uwsgi_app2:3032; } server { listen 80; server_name ~^(?<subhost>.+)\.example\.com$; return 301 https://example.com/$subhost$request_uri; } server { listen 443 ssl; server_name app1.example.com; ssl_certificate /etc/letsencrypt/live/app1.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/app1.example.com/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; location / { include uwsgi_params; uwsgi_pass uwsgi_app1; } } server { listen 443 ssl; server_name app2.example.com; ssl_certificate /etc/letsencrypt/live/app2.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/app2.example.com/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; location / { include uwsgi_params; uwsgi_pass uwsgi_app2; } } }
起動
docker-compose build
docker-compose up -d
app{1,2}フォルダに、ローカルで開発したflaskアプリのファイル一式を放り込めばアプリを動かせるので楽です。
補足
Let's Encrypt で複数のSSL証明書を一括取得
SSL証明書あたりの準備や記述が冗長なので、一つの証明書でいけるようにしたいところです(時間がある時にでも)。
サーバ再起動時の手順
サーバメンテナンス時の対応方法(コンテナの終了・起動)のメモです。
>make stop
でコンテナを終了docker ps -a
で起動しているコンテナや、マウントされているイメージの確認docker rm xxxxxx yyyyy zzzzz
でイメージ削除
- サーバ再起動
>lsof -i:443
,>lsof -i:80
等で、dockerで使うポートがすでに使われていないか確認- Nginxが使われていたら、
nginx -s stop
などで止める
- Nginxが使われていたら、
>make
でコンテナ起動
*1:https://methane.hatenablog.jp/entry/2012/12/24/214950
*2:サンプルコードやチュートリアルの先は暗闇の地雷原が広がっています
*3:Flaskがmicroframework なので、きっとそうするべきなのだと思います