1 概述
本示例详述了使用Docker部署Nginx作为反向代理,基于MySQL数据库的Flask项目的基本实现过程。实际生产环境部署,一般使用docker compose,这里演示分步实现项目部署,只是学习的一个过程,示例中的代码、配置文件,应根据具体的项目需求和环境配置,某些步骤也应该做相应的调整。
2 项目准备
2.1 基础环境和工具
-
Linux系统:虚拟机或云服务器都可以,这里使用的是基于Windows WSL的Ubuntu 24.04
-
MobaXterm远程终端工具
这里使用MobaXterm连接虚拟机,便于项目部署的操作。MobaXterm极易上手,免费易用,MobaXterm 链接直达。
-
Docker工具
安装及使用方法不赘述,Docker Docs。
2.2 Docker基础镜像
国内连接Docker镜像仓库网络的原因,我这里速度很慢且不稳定,所以这里推荐在项目部署前,把镜像先安排上,以提升项目部署速度,降低部署过程中出错中断概率。这里会用到MySQL、Python和Nginx相关基础镜像,可以从镜像仓库拉取或者本地加载。
# 仓库拉取
docker pull mysql:8.0.33
docker pull python:3.9-slim
docker pull nginx:1.27.1
# 本地加载
docker load -i ./tar/mysql-8.0.33.tar
docker load -i ./tar/python-3.9-slim.tar
docker load -i ./tar/nginx-1.27.1.tar
2.3 项目代码及配置文件
2.3.1 项目布局树形图
# mysql和nginx的docker容器配置文件
/nginx
|-- Dockerfile # 构建Nginx镜像的Dockerfile
|-- nginx.conf # Nginx配置文件
/mysql
|-- conf
|-- mysql.conf # MySQL配置文件
|-- data
|-- init
|-- init.sql # MySQL初始化脚本
# flask项目及配置文件
/flaskDockerDemo
|-- templates
|-- index.html
|-- show.html
|-- static
|-- styles.css
|-- sun.png
|-- baiduai.png
|-- uploads
|-- BaiduAI.jpeg
|-- app.py # Flask项目主程序
|-- requirements.txt # 示例项目比较简单,这里不要
|-- Dockerfile # 构建flaskdemo镜像的Dockerfile
|-- run.sh # 示例项目比较简单,可以不要
|-- .dockerignore
|-- README.md
2.3.2 flaskDockerDemo代码及其配置文件
示例代码及配置文件就不推到GitHub了,完整贴出来如下,需要自行复制
-
templates
- index.html 代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>FlaskDemo</title> <link rel="icon" type="image/png" href="{{ url_for('static', filename='sun.png') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> </head> <body> <nav class="navbar"> <ul> <li><a href="{{ url_for('hello_world') }}">Home</a></li> </ul> </nav> <main class=""> <h1>{{ greetings[0][1] }} × {{ greetings|length }}</h1> <img src="{{ url_for('static', filename='baiduai.png') }}" height="600"> </main> <footer class="footer"> <hr> <small> © 2024 <a href="{{ url_for('hello_world') }}">HelloFlask</a></small> </footer> </body> </html>
- show.html 代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>FlaskDemo</title> <link rel="icon" type="image/png" href="{{ url_for('static', filename='sun.png') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> </head> <body> <nav class="navbar"> <ul> <li><a href="{{ url_for('hello_world') }}">Home</a></li> </ul> </nav> <main class=""> <h1>你像花一样灿烂</h1> <img src="{{ url_for('bloom', filename='BaiduAI.jpeg') }}" height="600"> </main> <footer class="footer"> <hr> <small> © 2024 <a href="{{ url_for('hello_world') }}">HelloFlask</a></small> </footer> </body> </html>
-
static
- styles.css 代码:
/* 导航条样式 */ .navbar { background-color: #333; /* 导航条背景颜色 */ overflow: hidden; /* 清除浮动 */ position: sticky; /* 固定定位,根据需要可改为absolute、fixed或static */ top: 0; /* 底部对齐 */ width: 100%; /* 宽度100% */ } .navbar ul { list-style-type: none; /* 移除列表前的标记 */ margin: 0; padding: 0; display: flex; /* 使用Flexbox使导航项水平排列 */ justify-content: left; /* 均匀分布导航项space-around */ } .navbar li { float: left; /* 浮动列表项,但对于Flexbox不是必需的 */ } .navbar li a { display: block; /* 将链接变成块级元素,便于调整大小 */ color: white; /* 链接文字颜色 */ text-align: center; /* 文本居中 */ padding: 14px 16px; /* 内边距 */ text-decoration: none; /* 移除下划线 */ } .navbar li a:hover { background-color: #ddd; /* 鼠标悬停时的背景颜色 */ color: black; /* 鼠标悬停时的文字颜色 */ } /* 页脚样式 */ .footer { background-color: #333; /* 页脚背景颜色 */ color: white; /* 页脚文字颜色 */ text-align: center; /* 文本居中 */ padding: 10px 0; /* 上下内边距,左右为0 */ position: fixed; /* 固定定位,根据需要可改为absolute、fixed或static */ bottom: 0; /* 底部对齐 */ width: 100%; /* 宽度100% */ } /* 内容样式 */ main { text-align: center; /* 文本居中 */ }
- sun.png 图片:
- baiduai.png 图片:
-
uploads
- BaiduAI.jpeg
-
app.py
import os import pymysql from flask import Flask, render_template, send_from_directory app = Flask(__name__) # 配置上传文件的目录 UPLOAD_FOLDER = os.path.join(app.root_path, 'uploads') app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER db_config = { 'host': 'mysql', 'port': 3306, 'user': 'root', 'password': '123456', 'database': 'demo', } @app.route('/') def hello_world(): # put application's code here conn = pymysql.connect(**db_config) cursor = conn.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS hello( id INT AUTO_INCREMENT PRIMARY KEY, greeting VARCHAR(16) NOT NULL ) DEFAULT CHARSET=utf8;''') cursor.execute('''INSERT INTO hello(greeting) VALUES ('Hello, Flask!')''') conn.commit() cursor.execute("SELECT * FROM hello") greetings = cursor.fetchall() cursor.close() conn.close() return render_template('index.html', greetings=greetings) @app.route('/show') def show(): return render_template('show.html') @app.route('/flower/<path:filename>') def bloom(filename): return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
-
Dockerfile
# 使用Python 3.9的slim版本作为基础镜像,slim版本较为轻量,适合生产环境 FROM python:3.9-slim # 将当前目录下的所有文件复制到容器内的/app/目录 COPY . /app/ # 将当前目录下的run.sh文件复制到容器内的/run.sh目录 COPY ./run.sh /run.sh # 设置容器内的工作目录为/app/ WORKDIR /app/ # 设置环境变量TIME_ZONE为Asia/Shanghai,用于设置容器内的时区 ENV TIME_ZONE=Asia/Shanghai # 设置环境变量PIPURL,这里设置为清华大学的pypi镜像 ENV PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple/ #或 RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/ # 更新系统时区设置,确保容器内的时间与上海时区一致 RUN echo "${TIME_ZONE}" > /etc/timezone && \ ln -sf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime # 更新pip版本 RUN /usr/local/bin/python -m pip install --upgrade pip #或 RUN python -m pip install --upgrade pip # 安装依赖包 RUN pip install python-dotenv flask flask-wtf flask-sqlalchemy flask-migrate flask-ckeditor pymysql bleach gunicorn cryptography #或 RUN pip install -r requirements.txt # 给予run.sh脚本执行权限 RUN chmod +x /run.sh # 暴露端口,可以不写 EXPOSE 5000 # 设置容器启动时执行的命令 CMD ["/run.sh"]
-
run.sh
#!/bin/sh # 这里可以写一些项目初始化的脚本,比如数据库初始化 #echo "Initializing Flask database..." #flask db init #flask db migrate #flask db upgrade echo "Starting Gunicorn." exec gunicorn -b :'5000' app:app
2.3.3 mysql
-
mysql.conf
[client] default_character_set=utf8mb4 [mysql] default_character_set=utf8mb4 [mysqld] character_set_server=utf8mb4 collation_server=utf8mb4_unicode_ci init_connect='SET NAMES utf8mb4'
-
init.sql
DROP DATABASE IF EXISTS `demo`; CREATE DATABASE IF NOT EXISTS `demo` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
2.3.4 nginx
-
Dockerfile
FROM nginx:1.27.1 COPY nginx.conf /etc/nginx/nginx.conf
-
nginx.conf
user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; 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; sendfile on; keepalive_timeout 65; # 代理设置 upstream flask_app { server flaskdemo:5000; # Flask容器的名称或IP } server { listen 80; server_name _; location /static { alias /app/static; # 静态资源路径 } location /uploads { alias /app/uploads; # 用户上传文件路径 } # 转发请求到Flask应用 location / { proxy_pass http://flaskdemo:5000; proxy_set_header Host $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; proxy_redirect off; } # 其他可能的配置... } # 其他server配置... }
2.4 将2.3中创建的相关文件丢进虚拟机
这里是以conderchi用户登录的虚拟机,将文件丢进用户根目录即可,如果是以root权限登录,需要将文件拖到root根目录下。
3 项目分步部署
3.1 构建Docker网络
为了实现后续要部署的MySQL、Flask 项目和Nginx等各容器通过容器名无缝直连,首先构建一个Docker网络flasknet,代码如下:
# 创建flasknet网络
docker network create flasknet
# 查看已存在的网络
docker network ls
代码执行后查看创建的网络如下图:
flasknet网络构建完成,还可以通过以下指令查看网络具体信息:
# 通过网络名查看
docker network inspect flasknet
# 或通过网络id查看
docker network inspect 8ab
3.2 MySQL容器部署
部署MySQL容器配置项目相对来说比较简单,基于MySQL官方基础镜像直接运行MySQL容器就足够了,这里没有必要再单独创建自己的MySQL镜像。
部署运行指令如下,注意需要在根目录下运行该指令。
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123456 \
-v ./mysql/data:/var/lib/mysql \
-v ./mysql/conf:/etc/mysql/conf.d \
-v ./mysql/init:/docker-entrypoint-initdb.d \
--network flasknet \
mysql:8.0.33
代码执行后mysql容器即部署完成,可以看到mysql的挂载目录data已有数据被创建出来,如下图:
也可以在其他目录下去构建运行容器,但挂载数据卷时路径要做相应调整,比如在mysql目录下,挂载数据卷路径应修改如下。
-v ./data:/var/lib/mysql \
-v ./conf:/etc/mysql/conf.d \
-v ./init:/docker-entrypoint-initdb.d \
执行以下命令,查看mysql容器是否已运行:
docker ps
进入容器内部,查看数据库demo是否已创建:
docker exec -it mysql mysql -uroot -p123456
show databases;
exit; # 或Ctrl+D,退出mysql容器
3.3 Flask项目部署
3.3.1 构建flaskdemo镜像
注意:在构建flaskdemo镜像时,应根据Dockerfile路径及配置信息,选择在哪个目录下执行镜像构建命令,这里可以先进入flaskDockerDemo目录下,然后执行构建镜像,或者在构建镜像时明确指定Dockerfile路径。
# 方式一:进入flaskDockerDemo目录
cd ./flaskDockerDemo
docker build -t flaskdemo .
# 方式二:在根目录下
docker build -t flaskdemo ./flaskDockerDemo
查看刚构建的flaskdemo镜像
docker images
3.3.2 部署运行flaskdemo容器
- 不使用Nginx反向代理静态文件,可以使用以下命令直接部署
docker run -d --name flask -p 5000:5000 -v ./flaskDockerDemo:/app --network flasknet flaskdemo
# 使用下面命令查看正在运行的容器
docker ps
以下命令可以查看容器运行日志
docker logs flask # 或docker logs 7fa
查看虚拟机ip
ip addr
浏览器打开网页
- 使用Nginx反向代理静态文件,使用以下命令部署
docker run -d --name flaskdemo -v ./flaskDockerDemo:/app --network flasknet flaskdemo
3.4 Nginx容器部署
3.4.1 构建nginx镜像
方法和3.3.1构建flaskdemo镜像相同。
# 方式一:进入nginx目录
cd ./nginx
docker build -t nginx .
# 方式二:在根目录下
docker build -t nginx ./nginx
3.4.2 部署运行nginx容器
执行以下命令部署运行nginx容器
docker run --name nginx -d \
--network flasknet \
-p 80:80 \
-v ./nginx/nginx.conf:/etc/nginx/nginx.conf \
-v ./flaskDockerDemo/static:/app/static \
-v ./flaskDockerDemo/uploads:/app/uploads \
nginx
打开网页
4 设置开机自动启动
常用有以下3中方法,对于分步部署,个人更推荐前两种方式
4.1 容器已经部署
当容器已经部署到系统,可以通过以下命令,设置容器开机自动启动
docker update --restart=always mysql flaskdemo nginx
4.2 部署容器时设置
也可以通过以下命令,在容器部署时设置容器开机自动启动
# 在执行docker run时,增加 --restart always参数
# mysql
docker run -d \
--restart always \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123456 \
-v ./mysql/data:/var/lib/mysql \
-v ./mysql/conf:/etc/mysql/conf.d \
-v ./mysql/init:/docker-entrypoint-initdb.d \
--network flasknet \
mysql:8.0.33
# flaskdemo
docker run -d \
--restart always \
--name flaskdemo \
-v ./flaskDockerDemo:/app \
--network flasknet \
flaskdemo
# nginx
docker run -d \
--name nginx
--restart always \
--network flasknet \
-p 80:80 \
-v ./nginx/nginx.conf:/etc/nginx/nginx.conf \
-v ./flaskDockerDemo/static:/app/static \
-v ./flaskDockerDemo/uploads:/app/uploads \
nginx
4.3 使用systemd服务
systemd是现代Linux系统的标准初始化系统,通过创建一个服务单元文件(.service),可以定义容器启动的参数和重启策略,并将其设置为开机自启动。步骤如下:
-
创建一个名为
.service
的文件- mysql.service
[Unit] Description=mysql container service Requires=docker.service After=docker.service [Service] RemainAfterExit=yes ExecStart=/usr/bin/docker start mysql Restart=always [Install] WantedBy=multi-user.target
- flaskdemo.service
[Unit] Description=flaskdemo container service Requires=docker.service After=docker.service [Service] RemainAfterExit=yes ExecStart=/usr/bin/docker start flaskdemo Restart=always [Install] WantedBy=multi-user.target
- nginx.service
[Unit] Description=nginx container service Requires=docker.service After=docker.service [Service] RemainAfterExit=yes ExecStart=/usr/bin/docker start nginx Restart=always [Install] WantedBy=multi-user.target
注意:在
ExecStart
中,因前面已经存在,所以直接使用docker start
命令;如果容器不存在,需要使用docker run
命令,这里不赘述。另外,在
ExecStart
中,要指定docker
路径,你的可能和示例/usr/bin/docker
不同,可以使用以下命令查看docker
路径which docker # 查看docker路径
-
将
.service
文件放到虚拟机/etc/systemd/system/
目录下因这里使用的是user账户连接的虚拟机系统,可以采取以下方式将
.service
文件放到/etc/systemd/system/
目录下,以flaskdemo.service
文件为例如下:-
将文件拖到用户根目录下
-
执行以下指令,将文件复制到
/etc/systemd/system/
目录sudo cp flaskdemo.service /etc/systemd/system/ # 第一次sudo需要输入系统密码
-
进入
/etc/systemd/system/
目录查看文件是否到位cd /etc/systemd/system/
如果是root权限登录的系统,直接进入
/etc/systemd/system/
目录,将文件拖进去就好了cd /etc/systemd/system/
-
-
启用并启动服务
sudo systemctl daemon-reload sudo systemctl enable flaskdemo.service sudo systemctl start flaskdemo.service # 可以重启系统使用 docker ps 看服务是否正常激活
如果重启系统后,容器没有正常启动,大概率是
.service
文件配置有问题。可以使用以下指令查看服务日志systemctl list-units --type=service # 查看已启动的服务 sudo journalctl -u flaskdemo.service # 查看服务日志
5 总结
- 项目配置文件路径及构建和部署时路径应特别注意,避免部署时出现意外
- 项目部署存在依赖关系,需要按照网络→MySQL→Flask→Nginx顺序