使用Docker构建Nginx + uWSGI + Flask的松耦合Web应用服务器
在没有Docker Compose和套接字文件的情况下,构建使用套接字通信的nginx容器 ⇔ uWSGI容器(Flask应用程序)环境的方法。
注意:本文的配置示例直接指定了IP,这种状态不是松散耦合的,因此最终请根据需要替换配置值,例如为每个服务创建API端点。
为了将每个服务解耦,并实现微服务架构设计,通常会将容器细分为各个服务。然而,如果不使用负载均衡器、代理和API终端等,而是直接将容器保持独立通信,这不仅麻烦,而且无法实现松散耦合,所以除非有特殊情况,最好不要这样做(也很少见到这样的例子)。
例如,当托管启动设置的容器的主机重新启动时,容器的IP会发生变化,如果不进行配置更新,容器之间就无法通信,会出现问题。
环境
我在运行Amazon ECS(备用任务定义)和Docker的机器(RHEL7系Linux)上进行了测试。
本文将以从官方AMI启动,处于初始状态的AmazonLinux2上构建为例。
# 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
以下是在Amazon Linux 2上構建的命令示例:
$ 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
应用程序
我要创建一个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。
如果将第一行改成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 补充
目前构建这个容器映像时,安装uwsgi时会出现错误。(尚未调查原因和解决方案)
容器构建和启动命令示例
$ sudo docker build -t hello -f Dockerfile .
$ sudo docker run -itd -p 3031:3031 hello
网络
为了避免每次需要更改设置时都要进行构建,我们将创建一个适用于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网络的默认子网地址冲突,需要指定不同的地址范围。
如果按照本文示例设置了env_list的HOST_NAME,请将第二个八位改为18。
HOST_NAME=172.17.0.2
↓
HOST_NAME=172.18.0.2
$ 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
在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
请参考
(Translation: Please refer to)
瓶子
uWSGI -> uWSGI
Docker是一种容器化技术。
docker-compose (简称为”compose”) 是一种用于管理多个容器应用程序的工具。