本文环境:
- Linux 发行版:Ubuntu Focal 20.04.3 (LTS)
- Linux 内核:Linux 5.4.0-90-generic
- Docker 版本:20.10.11, build dea9396
- MYSQL:5.7.36
- Python:3.7.9
- Flask:2.0.2
- Gunicorn:20.1.0
- Nginx:1.21.4
部署前提
已启动数据库。本文数据库使用 mysql 5.7,数据库 IP 地址为 192.168.1.11 。
登录 mysql 创建测试数据库及用户:
-- 创建应用数据库
mysql> CREATE DATABASE demo;
-- 创建数据库用户
mysql> CREATE USER 'demouser'@'%' IDENTIFIED BY 'demo123';
-- 授权
mysql> GRANT ALL PRIVILEGES ON demo.* TO 'demouser'@'%';
创建测试项目
目录结构
└── flask_project
├── docker-compose.yml
├── flaskapp
│ ├── app.py
│ ├── config.py
│ ├── Dockerfile
│ ├── gunicorn.config.py
│ └── requirements.txt
├── mysql
│ └── test.env
└── nginx
├── Dockerfile
└── nginx.conf
./flaskapp/app.py
import os
from datetime import datetime
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
import config
# 实例化一个web应用
app = Flask(__name__)
app.config.from_object(config)
# 初始化一个数据库连接对象
db = SQLAlchemy(app)
# 测试数据库连接
# 建表
class User(db.Model):
__tablename__ = 'user'
uid = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(50), nullable=False)
email = db.Column(db.String(50), nullable=False, unique=True)
update_time = db.Column(db.DateTime, default=datetime.now)
def to_json(self):
return {
'uid': self.uid,
'username': self.username,
'email': self.email,
'update_time': self.update_time,
}
db.create_all()
@app.route("/")
def index():
app_name = os.getenv("APP_NAME")
return f"Hello from {app_name} running Flask inside Docker."
@app.route("/query")
def query():
result = User.query.all()
return jsonify(list(map(User.to_json, result)))
@app.route("/add/<username>")
def add(username):
result = User.query.filter_by(username=username).first()
if result:
return f"user {username} already exists."
user1 = User(username=username, email=username + '@456.com')
db.session.add(user1)
db.session.commit()
return f"add {username}."
if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0')
数据库配置文件 ./flaskapp/config.py
DB_USERNAME = 'demouser'
DB_PASSWORD = 'demo123'
DB_HOST = 'mysql57'
DB_PORT = '3306'
DB_NAME = 'demo'
DB_URI = 'mysql+pymysql://%s:%s@%s:%s/%s?charset=utf8mb4' % \
(DB_USERNAME, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME)
SQLALCHEMY_DATABASE_URI = DB_URI
SQLALCHEMY_TRACK_MODIFICATIONS = False
项目依赖文件:./flaskapp/requirements.txt
PyMySQL==1.0.2
Flask==2.0.2
Flask-SQLAlchemy==2.5.1
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'
注:容器写入 stdout
或 stderr
的任何内容都将被捕获并存储为容器的日志。 出于这个原因,accesslog
和 errorlog
都配置为 -
,容器部署时把日志发送到标准输出,以便由 Docker 作为日志存储。
容器部署
创建容器网络
创建 flask 和 mysql,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 /www/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容器,加入网桥front
$ docker run -itd \
--name flask \
--network flask-nginx \
--add-host=mysql57:192.168.1.11 \
-p 8000:8000 \
-e APP_NAME=flask_0.1 \
my_flaskapp:0.1
# 查看容器ip
$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' flask
# 172.18.0.3
--add-host
:添加自定义hosts,这里是 mysql 数据库的 IP 地址
创建 nginx 镜像+容器
nginx 配置文件 nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
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;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
listen [::]:80 default_server;
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 / {
proxy_pass http://flask:8000;
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;
}
location ~ .*\.(js|css)?$ {
proxy_pass http://flask:8000;
expires 15d;
}
error_page 404 /404.html;
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容器,加入网桥front
$ 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.18.0.4
现在浏览器可以通过 http://主机地址
访问 flask 项目。
测试
# 访问项目地址
$ curl http://127.0.0.1
Hello from flask_0.1 running Flask inside Docker
# 添加记录到数据库
$ curl http://127.0.0.1/add/lisi
add lisi.
# 查询添加结果
$ curl http://127.0.0.1/query
{"email":"lisi@456.com","uid":1,"update_time":"Wed, 15 Dec 2021 16:46:10 GMT","username":"lisi"}]
容器编排
前提:安装 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
extra_hosts:
- "mysql57:192.168.1.11"
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
上面配置中,连接数据库使用了外部 hosts,也可使用外部容器网络:
networks:
mysql57:
external: true
services:
flaskapp:
networks:
- mysql57
运行项目
# 后台运行
docker-compose up -d
# 查看运行状态
docker-compose ps
– END –
参考
-
使用docker部署nginx+flask+gunicorn+mysql项目 - 简书 (jianshu.com) 2020-06-17
-
docker 的 link 和 network 网络互连问题 - 小寒的故事 - 博客园 (cnblogs.com) 2018-12-18
-
docker link 过时不再用了?那容器互联、服务发现怎么办? - YatHo - 博客园 (cnblogs.com) 2017-11-20