docker composeやソケットファイル無しでソケット通信を使用した nginxコンテナ ⇔ uWSGIコンテナ(Flaskアプリ)環境を構築する方法です。
※本稿の設定例そのままの値だとIP直指定しており、疎結合とは到底言えない状態のため、最終的にはサービス毎にAPIエンドポイントを作成するなど設定値を置き換えて利用ください。
それぞれのサービスを疎結合にして、マイクロサービスなアーキテクチャ設計をするために、サービス毎にコンテナを細かく分割するケースは多いと思います。
ただ、ロードバランサーやプロキシ、APIエンドポイントなどを用いず、コンテナを独立させながら直接疎通させるのは手間で疎結合にも出来ないので、特別な事情が無ければ止めた方が良さそうです。(あまり例も見かけないですし)
例えば、自動起動設定のコンテナを起動しているホストマシンが再起動された場合、コンテナのIPが変わってしまい、設定に反映しないとコンテナ間で疎通が出来なくなる、などの問題が発生するためです。
環境
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
2023/10/06 追記
現在このコンテナイメージをbuildしようとするとuwsgiのインストール時にエラーが出ます。(原因及び解決策未調査)
コンテナビルド&起動コマンド例
$ 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
uWSGI
Docker
docker-compose