Docker 容器化部署 Flask 项目 + Gunicorn + Nginx

蓟清野
2023-12-01

准备工作

本文使用环境:

  • Linux 发行版:Ubuntu Focal 20.04.4 (LTS)
  • Linux 内核:Linux 5.4.0-100-generic
  • Docker 版本:20.10.12
  • Python:3.7.9
  • Flask:2.0.2
  • Gunicorn:20.1.0
  • Nginx:1.21.4

搭建 Linux 环境可参考:


创建项目

项目结构如下:

flask_project/
├── docker-compose.yml
├── flaskapp
│   ├── Dockerfile
│   ├── app.py
│   ├── gunicorn.config.py
│   └── requirements.txt
└── nginx
    ├── Dockerfile
    └── nginx.conf

./flaskapp/app.py

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World'


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

项目依赖文件:./flaskapp/requirements.txt

Flask==2.0.2
gunicorn==20.1.0
gunicorn[gevent]

gunicorn 配置文件:./flaskapp/gunicorn.config.py

import multiprocessing

# 默认'127.0.0.1:8000',防火墙需开启对应端口
bind = '0.0.0.0:8000'

# 并发进程数,默认1
workers = multiprocessing.cpu_count() * 2 + 1

# 允许挂起的连接数最大值,默认2048
backlog = 2048

# 进程的工作方式,默认'sync'
worker_class = 'gevent'

# 最大客户客户端并发数量,对使用线程和协程的worker的工作有影响
#worker_connections = 1200

# 调试模式,默认False
#Debugging = True

# 进程名
proc_name = 'gunicorn.proc'


# pid文件,如果不设置将不会创建
pidfile = '/tmp/gunicorn.pid'

# 访问记录
accesslog = '-'
# 访问记录格式
# access_log_format = '%(h)s %(t)s %(U)s %(q)s'

# 日志文件
errorlog = '-'

# 错误日志输出等级,默认'info'
#loglevel = 'debug'

注:容器写入 stdoutstderr 的任何内容都将被捕获并存储为容器的日志。 出于这个原因,accesslogerrorlog 都配置为 -,容器部署时把日志发送到标准输出,以便由 Docker 作为日志存储。


容器部署

创建容器网络

创建 flask 和 nginx 通信使用的网络

// 创建网桥,连接方式是 bridge
$ docker network create -d bridge flask-nginx

// 显示所有 bridge
$ docker network ls
# NETWORK ID     NAME      DRIVER    SCOPE
# ddeb87894904   flask-nginx   bridge    local

创建 flask 镜像 + 容器

以 python:3.7.9-slim 为基础镜像,Dockerfile 如下:

FROM python:3.7.9-slim
WORKDIR /ubuntu/flaskapp
COPY ./requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

COPY . .
CMD ["gunicorn", "-c", "./gunicorn.config.py", "app:app"]

构建镜像并启动

# 当前工作目录:flaskapp
# 构建镜像
$ docker build -t="my_flaskapp:0.1" .

# 后台启动flask容器,加入网桥flask-nginx
$ docker run -itd \
--name flask \
--network flask-nginx \
-p 8000:8000 \
-e APP_NAME=flask_0.1 \
my_flaskapp:0.1 

# 查看容器ip
$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' flask
# 172.20.0.2

创建 nginx 镜像 + 容器

nginx 配置文件 nginx.conf,具体可参考 Nginx 配置详解

user nginx; # 配置用户名为nginx
worker_processes auto; # 允许生成的进程数,默认为1,auto表示和CPU内核有关,有几个内核,就会开启几个进程
error_log /var/log/nginx/error.log; # 指定日志路径,级别,这个设置可以放入全局块,http块,server块,级别依次为:debug|info|notice|warn|error|crit|alert|emerg
pid /run/nginx.pid; # 指定nginx进程运行文件存放地址

include /usr/share/nginx/modules/*.conf; # include 指定包含其他扩展配置文件

events {
    worker_connections 1024; # 最大连接数,默认为512
}

http {
    # 设置日志格式,默认为 conbined
    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; # nginx 访问日志存放位置

    sendfile            on; # 允许sendfile方式传输文件,默认为off,可以在http块,server块,location块。
    tcp_nopush          on; # 减少网络报文段的数量
    tcp_nodelay         on;
    keepalive_timeout   65; # 连接超时时间,默认为75s,可以在http块,server块,location块。
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types; # 文件扩展名与文件类型映射表
    default_type        application/octet-stream; # 默认文件类型,默认为 text/plain

    include /etc/nginx/conf.d/*.conf; # 包含的子配置项位置及文件

    server {
        listen       80 default_server; # 监听 80 端口,default_server 表示默认服务器
        listen       [::]:80 default_server; # 为 IPv6 设置
        server_name  _; # 配置域名
        access_log   /var/log/nginx/flaskapp_access.log;
        error_log    /var/log/nginx/flaskapp_error.log;
        root         /usr/share/nginx/html; # 服务器默认启动目录

        include /etc/nginx/default.d/*.conf;

        location / {  # 用于做URL匹配                                                               
            proxy_pass http://flask:8000;  # 将固定的请求路由到新的uri                                 
            proxy_redirect     off;        # 关闭重定向                                     
            proxy_set_header   Host                 $http_host;                 
            proxy_set_header   X-Real-IP            $remote_addr;               
            proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for; 
            proxy_set_header   X-Forwarded-Proto    $scheme;                    
        }                                                                           
                                                                                
        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|ico)$ {                            
             proxy_pass  http://flask:8000;                                   
             expires 30d; # 静态文件30天后在客户端浏览器的缓存到期                                                      
        }
                                                                           
        location ~ .*\.(js|css)?$ {                                                 
            proxy_pass http://flask:8000;                                     
            expires 15d;                                                        
        }

        error_page 404 /404.html; # 配置404页面
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html; # 错误状态码的显示页面,配置后需要重启
            location = /50x.html {
        }
    }
}

nginx 容器通过 --network 连接 flask 容器,因此配置文件中 location 的代理地址为 flask:8000

注:docker 官方已不推荐使用 docker run --link 来链接 2 个容器互相通信,随后的版本会删除 --link

以 nginx:1.21.4 为基础镜像,Dockerfile 如下:

FROM nginx:1.21.4
RUN rm /etc/nginx/conf.d/default.conf
COPY ./nginx.conf /etc/nginx

构建镜像并启动

# 当前工作目录:nginx
$ docker build -t "my_nginx:1.0" . 

# 后台启动nginx容器,加入网桥flask-nginx
$ docker run -itd --name nginx -p 80:80 --network flask-nginx my_nginx:1.0

# 查看容器ip
$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx
# 172.20.0.3

现在浏览器可以通过 http://主机地址 访问 flask 项目。主机地址可以通过 ifconfig 命令查看。

测试

# 访问项目地址
$ curl http://172.17.29.117
Hello World

容器编排

需要安装 Docker Compose
docker-compose.yml

version: '3'
networks:
  flask-nginx:
services:
  flaskapp:
    image: my_flaskapp:0.1
    build: ./flaskapp
    container_name: flask
    volumes:
      - "/etc/timezone:/etc/timezone:ro"
      - "/etc/localtime:/etc/localtime:ro"
    networks:
      - flask-nginx
    ports:
      - "8000"
    environment:
      - APP_NAME=flask_0.2
  nginx:
    image: my_nginx:1.0
    build: ./nginx
    container_name: nginx
    volumes:
      - "/etc/timezone:/etc/timezone:ro"
      - "/etc/localtime:/etc/localtime:ro"	
    networks:
      - flask-nginx
    ports:
      - "80:80"
    depends_on:
      - flaskapp

depends_on
设置依赖关系

  • docker-compose up :以依赖性顺序启动服务。在上面示例中,先启动 flaskapp ,才会启动 nginx。
  • docker-compose up SERVICE :自动包含 SERVICE 的依赖项。在上面示例中,docker-compose up nginx 还将创建并启动 flaskapp。
  • docker-compose stop :按依赖关系顺序停止服务。在上面示例中,nginx 在 flaskapp 之前停止。

运行项目

# 后台运行
docker-compose up -d

# 查看运行状态
docker-compose ps

参考

 类似资料: