使用Docker构建Nginx + uWSGI + Flask的松耦合Web应用服务器

在没有Docker Compose和套接字文件的情况下,构建使用套接字通信的nginx容器 ⇔ uWSGI容器(Flask应用程序)环境的方法。
注意:本文的配置示例直接指定了IP,这种状态不是松散耦合的,因此最终请根据需要替换配置值,例如为每个服务创建API端点。

为了将每个服务解耦,并实现微服务架构设计,通常会将容器细分为各个服务。然而,如果不使用负载均衡器、代理和API终端等,而是直接将容器保持独立通信,这不仅麻烦,而且无法实现松散耦合,所以除非有特殊情况,最好不要这样做(也很少见到这样的例子)。
例如,当托管启动设置的容器的主机重新启动时,容器的IP会发生变化,如果不进行配置更新,容器之间就无法通信,会出现问题。

环境

我在运行Amazon ECS(备用任务定义)和Docker的机器(RHEL7系Linux)上进行了测试。
本文将以从官方AMI启动,处于初始状态的AmazonLinux2上构建为例。

以下是在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

以下是在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
在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

在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”) 是一种用于管理多个容器应用程序的工具。

 

bannerAds