docker-compose+nginx+uwsgi | 使用容器编排技术部署一个flask项目

前言

为什么需要大规模容器编排?

容器解决了开发人员的生产力问题,使 DevOps 工作流变得异常流畅。开发人员可以创建容器镜像,运行容器并在该容器中开发代码,再将其部署在本地数据中心或公共云环境中。这种开发人员的工作流非常流畅,但无法自动应用于生产环境。

生产环境通常与开发人员电脑上的本地环境大不相同。无论是运行大规模的传统三层架构(three-tier)应用程序,还是基于微服务的应用程序,管理大量的容器和节点集群都不是一件容易的事。业务流程是实现规模化的必需组件,想要形成规模我们就必须要实现自动化。

容器编排有什么优势?

容器为基于微服务的应用程序提供了一个理想的「应用程序部署单元」和「自包含的执行环境」。这使应用可以在同一硬件上,以微服务的形式运行多个独立的模块,对每个模块获得更好的控制和生命周期管理。

🤔 想象一下,当一个项目包含的前端后端多个应用在部署的时候都需要单独安装并配置,那么项目执行环境的快捷部署就成了一个亟待解决的问题。而容器及容器编排提供的「隔离机制」和「编排能力」,即可以很好解决这个问题。整套部署方案由项目文件、yaml文件、镜像文件组成,有了这些,即可通过一行命令,启动多个应用环境,并运行项目代码文件,实现整套项目的部署和运行工作。

简而言之,容器编排的方式,使得我们的部署过程极度地快捷化、简单化了。一次构建,持续受益。

docker compose 的介绍

Docker-Compose 项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排。

Docker-Compose 项目由 Python 编写,调用 Docker 服务提供的API来对容器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用Compose 来进行编排管理。

通过 Docker-Compose ,不需要使用shell脚本来启动容器,而使用 YAML 文件来配置应用程序需要的所有服务,然后使用一个命令,根据 YAML 的文件配置创建并启动所有服务。

👌🏻 关于docker compose的更多内容,请参考:「docker」学习笔记 | 容器编排之Docker Compose

👌🏻 关于docker容器构建的项目部署过程,请参考:docker+nginx+https | 实现通过域名访问服务器资源

此前写的这篇文章, 以nginx应用为基础构建容器,并实现了通过域名去访问服务器资源。有了上一篇文章完成的工作,就可以很好的衔接本篇文章的实现操作啦。

🤫 整理一下目前的进度,等于我们现在已经拥有了一份完整的nginx项目文件、一份编写好的nginx.conf配置文件,以及一个nginx镜像。

🪵 本篇的目标,基于一个简单的flask项目去构建容器,并使用docker-compose进行两个容器的编排工作。

构建flask应用

/server/server_script/web_flask/app.py

创建一个最简单的flask程序: app.py

需要注意的是,程序中名为app的变量,以及名为app.py的文件名,分别跟uwsgi.ini文件中的wsgi-filecallable两个变量所对应

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

构建uwsgi应用

/server/server_script/web_flask/uwsgi.ini

uwsgi.ini文件编写

[uwsgi]

# uwsgi直接作为web服务器时,使用的ip和端口
http = :1110

# uwsgi与nginx连接时,使用的ip和端口
socket = :1100

master = true          // 允许存在主进程

;vhost = true          // 多站模式
;no-site = true        // 多站模式时不设置入口模块和文件

#虚拟环境目录
;home = /usr/local/flasky/venv

#指向网站根目录
chdir = /server_script/web_flask/

stats = uwsgi.status
pidfile = uwsgi.pid

#python启动程序文件
wsgi-file = app.py

#python程序内用于启动的application变量名
callable = app

#处理器数
processes = 8

#线程数
threads = 2

#状态监测地址
;stats = 127.0.0.1:9191

#设置uwsgi包解析的内部缓存区大小。默认4k
buffer-size = 32768

构建nginx应用

/server/server_nginx/config/nginx.conf

nginx.conf文件编写:

user  nginx;
worker_processes  auto;
 
error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

 
events {
    worker_connections  1024;
}


http {
    client_max_body_size 100m;
	include mime.types;
 
	server {
            listen 80;
            server_name api.chenxuefan.cn;
            rewrite ^(.*) https://$server_name$1 permanent;  # 强制跳转至https
        }

    # 负载均衡
	upstream blog {
        server server_script:1100;
#             server 127.0.0.1:1110;
    }

	server {
		    listen 443 ssl;  # 1.1版本后这样写
            server_name api.chenxuefan.cn; #填写绑定证书的域名
            ssl_certificate /etc/nginx/ssl/api.chenxuefan.cn.pem;  # 指定证书的位置,绝对路径
        	ssl_certificate_key /etc/nginx/ssl/api.chenxuefan.cn.key;  # 绝对路径,同上
		
	        ssl_session_timeout 5m;
        	ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置
       	 	ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照这个套件配置
        	ssl_prefer_server_ciphers on;
		    ssl_session_cache shared:SSL:1m;
 
        	fastcgi_param  HTTPS        on;
        	fastcgi_param  HTTP_SCHEME     https;

            location / {
                # 设置请求头信息,防止出现跨域报错
                add_header 'Access-Control-Allow-Origin' "$http_origin" always;
                add_header 'Access-Control-Allow-Credentials' 'true' always;
                add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
                add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-  Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;

                proxy_set_header   Host              $http_host;
                proxy_set_header   X-Real-IP         $remote_addr;
                proxy_set_header   X-Real-PORT       $remote_port;
                proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;

                # 静态文件模式。切换成转发uwsgi的信号时,需注释这两行
#                 root   /usr/share/nginx/html;
#                 try_files $uri $uri/ /index.html;

                # uwsgi模式。
                include uwsgi_params;  # 包含uwsgi的请求参数
                uwsgi_pass blog;  # 转交给uwsgi

            }

            #location ~ ^/facicon\.iso$ {
            location /favicon.ico {
                root  /usr/share/nginx/html;
            }
        }

        # 引入扩展配置(可以细分服务nginx)
        include /etc/nginx/conf.d/*.conf;
}

构建镜像

1. server_nginx

/server/server_nginx/Dockerfile

此镜像包含nginx相关配置文件,使用nginx:1.23.2作为基础镜像进行构建。Dockerfile文件编写:

FROM nginx:1.23.2

MAINTAINER Billy_Chen

RUN rm /etc/nginx/conf.d/default.conf

COPY ./config/nginx.conf /etc/nginx/conf.d/

EXPOSE 80 443

2. server_script

/server/server_script/Dockerfile

此镜像包含flask项目在内的所有py项目文件,使用python:3.10作为基础镜像进行构建。Dockerfile文件编写:

FROM python:3.10

ENV TZ=Asia/Shanghai

MAINTAINER Billy_Chen

WORKDIR /server_script

VOLUME ["/server_script/dsapi/images", "/server_script/unsplash/images"]

COPY . .

RUN pip install -r requirements.txt -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

EXPOSE 1100 1110

CMD ["uwsgi", "--ini", "./web_flask/uwsgi.ini"]

使用docker build打包镜像,打包命令参考:

docker build -t chenxuefan/server_script:v3 --platform linux/amd64 .

👌🏻 关于构建操作的具体教程,以及每个指令的含义解释,请参考:使用 Dockerfile 构建项目

👌🏻 关于发布到私有镜像仓库的具体教程,请参考:私有镜像仓库

因为我使用的是m2芯片的MacBook进行打包,并且后续会在Linux服务器和本机上使用镜像,因此涉及到多架构镜像的构建,这里建议使用buildx打包镜像。

👌🏻 关于buildx打包镜像的具体教程,请参考:用Docker Buildx制作多架构的镜像

使用docker buildx build打包镜像,打包命令参考:

docker buildx build -t chenxuefan/server_script:v3 --platform linux/amd64,linux/arm64 . --push

编写 Docker Compose 模板文件

/server/docker-compose.yml

目前经过上一步的操作,我们已经得到了server_nginxserver_script两个镜像,并已上传到私有仓库

此时有了镜像,就可以着手容器编排的工作了

docker-compose.yml文件编写:

version: "3"

services:
  server_nginx:
    image: chenxuefan/server_nginx:v3
    container_name: server_nginx
    restart: always
    volumes:
      - ./server_nginx/html:/usr/share/nginx/html
      - ./server_nginx/config/nginx.conf:/etc/nginx/nginx.conf
      - ./server_nginx/logs:/var/log/nginx
      - ./server_nginx/ssl:/etc/nginx/ssl
      - ./server_nginx/conf.d:/etc/nginx/conf.d
    ports:
      - 80:80
      - 443:443
    depends_on:
      - server_script

  server_script:
    image: chenxuefan/server_script:v3
#    build: ./server_script
    container_name: server_script
    restart: always
    volumes:
      - ./server_script/dsapi:/server_script/dsapi
      - ./server_script/dytt:/server_script/dytt
      - ./server_script/unsplash:/server_script/unsplash
      - ./server_script/web_flask:/server_script/web_flask
    ports:
      - 1100:1100
      - 1110:1110
    command:
      - sh
      - -c
      -
        uwsgi --ini ./web_flask/uwsgi.ini &
        python3 run.py


networks:
  server:
    driver: bridge

需要注意的是,yaml文件中的很多映射路径需要对应项目文件中的路径,比如./server_script就代表跟yml文件同级的server_script文件夹。

👌🏻 关于yaml文件中的各个命令的解释,请参考:编写-docker-compose-模板文件

image-20230221181208003

部署工作

👌🏻 关于更多的操作命令,请参考:docker-compose 操作命令

所有的准备工作都做好之后,就来到最后一步的部署工作啦!

第一步,把上图中的server文件夹(也就是包含docker-compose.yml文件那一个文件夹)拷贝至服务器中的目录

第二步,切入到docker-compose.yml所在的目录,执行代码:docker-compose up -d

image-20230221183935240

部署完成 ✅ 😎 💆🏻

查看此时的启动状态:docker-compose ps,可以看到两个容器都在运行的状态,即表示启动成功

image-20230221184911782

查看此时的端口监听状态

image-20230221185406444

网页端看看此次部署的成果:https://api.chenxuefan.cn/api/https://api.chenxuefan.cn/api/dsapi

可以访问到!本次部署圆满成功!🍔 🍻

后记


3004 字