こんばんは、SWX3人目の熊谷(悠)です。
docker composeやソケットファイル無しでソケット通信を使用した nginxコンテナ ⇔ uWSGIコンテナ(Flaskアプリ)環境を構築する方法です。
※本稿の設定例そのままの値だとIP直指定しており、疎結合とは到底言えない状態のため、最終的にはサービス毎にAPIエンドポイントを作成するなど設定値を置き換えて利用ください。
環境
Amazon ECS(別タスク定義)やDockerが起動しているマシン(RHEL7系Linux)にて動作を確認しました。
本稿では、例として公式AMIから起動したばかりで初期状態のAmazonLinux2上に構築します。
AmazonLinux2に構築するコマンド例
$ sudo su # yum update -y # yum -y install docker # systemctl start docker # systemctl enable docker # systemctl status docker # mkdir /opt/example-service # mkdir /opt/example-service/app # cd /opt/example-service/app/ # nano hello.py # nano uwsgi.ini # nano Dockerfile # docker build -t hello -f Dockerfile . # docker run -itd -p 3031:3031 hello # mkdir /opt/example-service/web # cd /opt/example-service/web/ # nano env_list # nano uwsgi.conf.template # nano Dockerfile # docker build -t hello/nginx -f Dockerfile . # docker run -itd --env-file=env_list -p 80:80 hello/nginx # curl http://172.17.0.3/hello
App
Flaskアプリのファイルを作成します。 ※色々書いてますが関数はhelloだけでも大丈夫です。
from flask import Flask, jsonify app = Flask(__name__) @app.route('/healthcheck') def healthcheck(): return "" @app.route("/hello") def hello(): return jsonify({ "message": "Hello World!" }) @app.route('/api/v1/status/200', methods=['GET']) def status_200(): app.logger.info('execute 200 status') return jsonify({'message': '200 status message'}), 200 @app.route('/api/v1/status/400', methods=['GET']) def status_400(): app.logger.info('execute 400 status') return jsonify({'message': '400 status message'}), 400 @app.route('/api/v1/status/500', methods=['GET']) def status_500(): app.logger.info('execute 500 status') return jsonify({'message': '500 status message'}), 500 # Flaskのみで動作するビルトインサーバーを起動する※ローカルで動かす時用 if __name__ == '__main__': PORT = 5000 app.run( threaded=True, debug=True, port=PORT, host='0.0.0.0', )
uWSGIの設定ファイルを作成します。
[uwsgi] env=TZ=UTC-9 socket=0.0.0.0:3031 # nginxとのソケット通信用 #http=0.0.0.0:9090 # 単体(Webサーバー無し)で動作するHTTPサーバーを起動する※uWSGIコンテナに直接HTTPリクエストしたい時用 wsgi-file=./hello.py master=true callable=app
アプリを動かすためのDockerfileを作成します。
1行目をFROM python:latest
にすれば3~9行目のパッケージインストールコマンド群は不要になりますが、コンテナイメージサイズは倍(421MB ⇒ 952MB)になります。
FROM python:alpine RUN apk --update-cache add \ gcc \ g++ \ build-base \ linux-headers \ python3-dev \ pcre-dev RUN pip install --upgrade pip \ && pip install --no-cache-dir \ Flask \ uwsgi COPY hello.py ./ COPY uwsgi.ini ./ EXPOSE 3031 CMD uwsgi uwsgi.ini
コンテナビルド&起動コマンド例
$ sudo docker build -t hello -f Dockerfile . $ sudo docker run -itd -p 3031:3031 hello
Web
設定変更するためだけに毎回ビルドしなくて済むようにenvsubst用設定ファイルを作成します。(ECSなら環境変数に指定する。)
- SERVER_NAME:nginxコンテナにアクセスするIPもしくはFQDN
- RESOLVER:HOST_NAMEを解決できるレコードを持ったDNSサーバーIP
- HOST_NAME:uWSGIコンテナのIPもしくはFQDN
- PORT:uWSGIコンテナ側で待ち受けているwsgiソケットポート
SERVER_NAME=127.0.0.1 RESOLVER=169.254.169.253 HOST_NAME=172.17.0.2 PORT=3031
nginxの設定ファイルを作成します。
server { listen 80 default_server; server_name ${SERVER_NAME}; location / { include uwsgi_params; resolver ${RESOLVER}; set $url ${HOST_NAME}; uwsgi_pass $url:${PORT}; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
Dockerfileを作成します。
FROM nginx:alpine RUN apk --no-cache add tzdata && \ cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \ apk del tzdata COPY ["uwsgi.conf.template","/tmp/"] EXPOSE 80 ENTRYPOINT ["/bin/sh","-c"] CMD ["envsubst '$$SERVER_NAME $$RESOLVER $$HOST_NAME $$PORT' < /tmp/uwsgi.conf.template > /etc/nginx/conf.d/uwsgi.conf && nginx -g 'daemon off;'"]
コンテナビルド&起動コマンド例
$ sudo docker build -t hello/nginx -f Dockerfile . $ sudo docker run -itd --env-file=env_list -p 80:80 hello/nginx
確認
nginxが起動しているコンテナにリクエストします。
$ curl http://172.17.0.3/healthcheck $ curl http://172.17.0.3/hello {"message":"Hello World!"} $ curl http://172.17.0.3/api/v1/status/200 {"message":"200 status message"} $ curl http://172.17.0.3/api/v1/status/400 {"message":"400 status message"} $ curl http://172.17.0.3/api/v1/status/500 {"message":"500 status message"}
(ECS以外)uWSGI, nginxそれぞれのログを確認したい場合
$ sudo tail -f /var/lib/docker/containers/[コンテナID]/[コンテナID]-json.log
おまけ:docker-composeを用いる場合
docker networkのデフォルトサブネットアドレス帯と重複するのを避けるために、異なるアドレス帯を指定しています。
本稿例通りにenv_listのHOST_NAME
を設定されている場合は、第二オクテットを 18 に変更してください。
HOST_NAME=172.17.0.2 ↓ HOST_NAME=172.18.0.2
AmazonLinux2に構築するコマンド例
$ sudo su # curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose # chmod +x /usr/local/bin/docker-compose # nano /opt/example-service/docker-compose.yml # cd /opt/example-service/ # /usr/local/bin/docker-compose up -d # curl http://172.18.0.3/hello
version: '3' services: app: image: hello networks: hello-app-net: ipv4_address: 172.18.0.2 ports: - "3031:3031" restart: always web: image: "hello/nginx" networks: hello-app-net: ipv4_address: 172.18.0.3 ports: - "80:80" env_file: ./web/env_list # depends_on: # - app restart: always networks: hello-app-net: driver: bridge ipam: driver: default config: - subnet: 172.18.0.0/24 # gateway: 172.18.0.1
参考
Flask
https://msiz07-flask-docs-ja.readthedocs.io/ja/latest/
https://flask.palletsprojects.com/en/2.2.x/quickstart/
uWSGI
https://www.python.ambitious-engineer.com/archives/1959
https://uwsgi-docs.readthedocs.io/en/latest/WSGIquickstart.html
Docker
https://mebee.info/2021/08/05/post-40153/
https://www.tohoho-web.com/docker/docker_logs.html
docker-compose
https://man.plustar.jp/docker/compose/install.html
https://docs.docker.com/compose/compose-file/compose-file-v3/