使用 Nginx + Gunicorn 部署 Flask 项目
Flask Web 项目开发完成后,开发人员只是在开发环境运行,只有本地可以访问到项目。如果要让用户访问到项目,需要将项目部署到生产环境上,在服务器运行项目。
本文就使用阿里云服务器(CentOS 7.7 64位)来演示部署一个简单的 Flask 项目。
一、阿里云服务器配置
要将项目部署到服务器上,首先要有服务器。阿里云需要实名认证登录,刚注册时可以领一台免费使用的服务器,试用一个月,如果认证用户是24周岁以下可以买学生服务器,比较便宜,实在不行可以只买一个星期来试用。
拥有服务器后,需要在服务器上配置一些规则,才能完成项目的部署。
1. 登录阿里云,点击用户名旁边的“控制台”,然后在控制台点击左上角的菜单,展开菜单后,再点击“云服务器 ECS”(Elastic Compute Service,简称ECS),进入自己的云服务器界面。当前用户有哪些服务器都会在这里显示。
2. 点击“实例”,然后点击服务器信息右边的“管理”,进入服务器管理界面。
3. 在服务器管理界面点击“本实例安全组”,然后点击右边的“配置规则”,就会进入配置安全组规则的界面(页面的按钮是随阿里云的前端界面变化的,仅供参考)。
4. 如果之前配置过安全组规则,在此页面会保留着,如果没有配置过,就点击右上角的“添加安全组规则”按钮,弹出如下的配置框,然后按照下图的方式配置。
下面是按照 Flask 的默认服务器端口5000配置的,也可以配置多个端口,方法完全一样。
5. 我配置了三个端口,5000,7777,8888,配置成功后,结果如下。
配置完成后,后面部署项目时可以设置配置好的端口作为 Flask Web 项目的访问端口。
二、环境搭建和代码部署
配置好阿里云服务器的访问端口后,服务器上还没有安装项目需要使用的软件和库,也没有项目代码,所以需要搭建好项目运行的环境和部署代码。
1. 安装 MySQL
最好先下载好 MySQL 的安装包,然后使用远程连接工具将安装包上传到服务器,这样会快一点。
可以使用 WinSCP 或FileZilla,都非常方便,将下载好的安装包(如 mysql-5.7.27-1.el7.x86_64.rpm-bundle.tar)上传到服务器。
然后使用 ssh 连接阿里云服务器,如使用 Xshell ,按以下步骤安装 MySQL 和创建需要的用户和数据库。
解压 MySQL 安装包。
tar -xvf mysql-5.7.27-1.el7.x86_64.rpm-bundle.tar
安装 MySQL5.7 。
yum install mysql-community-* -y
查询 root 用户默认的密码。
grep password /var/log/mysqld.log
使用 root 用户登录 MySQL ,然后修改 root 用户的密码(密码自己设,不用跟我一样)。
mysql -u root -p# 修改root的密码alter user 'root'@'localhost' identified by 'Mysql!123';
创建需要使用的用户 admin。
grant all privileges on *.* to 'admin'@'%' identified by 'Mysql!123';
退出 root 用户,使用 admin 用户登录 MySQL ,创建需要使用的数据库MyDB_one 。
mysql -u admin -p# 创建 MyDB_one 数据库create database MyDB_one character set utf8;
安装好 MySQL,并创建好用户和数据库,代码中可以正常连接和使用数据库,数据库就准备好了。
2. 安装 Python3.6
正常项目部署时,最好先安装一个虚拟环境,好让当前项目与其他项目隔离开,运行环境不会相互干扰。不过当前的服务器只有一个项目,后续也不会再部署其他的项目,所以不安装虚拟环境了。
在CentOS 中默认的 Python 版本是2.7 ,如果后面用 Python2.7 运行代码,最新版本的 gunicorn 是不支持 Python2.7 的,可以指定较旧的版本安装,如 gunicorn==19.9.0 ,新版本的 Flask 使用时也有兼容问题,可以指定较旧的版本安装,如 flask==0.10.1 。
当然,安装 Python3 ,就不用担心兼容问题了。
yum install python36 -y
删除/usr/bin/ 下的 python ,创建一个软连接指向 python3.6 ,这样默认的 Python 版本就是 python3.6 了。
rm /usr/bin/pythonln -s /usr/bin/python3.6 /usr/bin/python
3. 安装 Flask 和 Flask-SQLAlchemy
执行如下命令安装 Flask 和 Flask-SQLAlchemy ,如果是 Python2.7 就将 pip3 改成 pip。
pip3 install Flaskpip3 install flask-sqlalchemy
因为使用的是 MySQL,要使用 Flask-SQLAlchemy 连接数据库,还要安装flask-mysqldb 。
pip3 install flask-mysqldb
安装时会报如下错误,是因为在 python3.6 中找不到 Python.h ,需要安装python3-devel 。
MySQLdb/_mysql.c:38:20: fatal error: Python.h: No such file or directory#include "Python.h"^compilation terminated.error: command 'gcc' failed with exit status 1
所以执行命令安装python3-devel 。
yum install python3-devel -y
但是执行后会报如下错。
File "/usr/bin/yum", line 30except KeyboardInterrupt, e:^SyntaxError: invalid syntax
因为 yum 是使用 Python2 实现的,默认的python 已经被改成 python3.6 了(上面创建软链接时) ,需要将yum 使用的 Python 版本改回 python2.7 。
vim /usr/bin/yum
将第一行的 /usr/bin/python 改为 /usr/bin/python2.7,重新运行还会报如下错误。
Downloading packages:File "/usr/libexec/urlgrabber-ext-down", line 28except OSError, e:^SyntaxError: invalid syntax
原因与上面相同,继续修改。
vim /usr/libexec/urlgrabber-ext-down
将第一行的 /usr/bin/python 改为 /usr/bin/python2.7 。
然后重新执行yum install python3-devel -y 和pip3 install flask-mysqldb 就可以安装成功了,如果还有其他问题,可以使用类似方法解决。
4. 将项目代码部署到服务器上
我准备了一个 ProjectOne 的项目文件夹,项目中包含了一个 Flask 代码文件 flask_project.py ,一个 templates 模板文件夹,在模板文件夹中有一个 flask_project.html 模板文件。
使用WinSCP 或FileZilla 将项目代码上传到服务器,使用 tree 命令查看,项目的目录结构如下。
如果没有 tree 命令 ,可以先安装。
yum install tree -y
flask_project.py 中的代码如下:
# coding=utf-8from flask import Flask, render_template, request, redirect, flashfrom flask_sqlalchemy import SQLAlchemyimport randomapp = Flask(__name__)app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://admin:Mysql!123@127.0.0.1:3306/MyDB_one'app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Trueapp.config['SQLALCHEMY_ECHO'] = Trueapp.config['SECRET_KEY'] = 'NFAIOSDFHASOGHAOSPIGAOWE'db = SQLAlchemy(app)class Phone(db.Model):__tablename__ = 'Phone_tb'pid = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(32))person_id = db.Column(db.Integer, db.ForeignKey('Person_tb.mid'))def __repr__(self):return 'Phone_name: {}'.format(self.name)class Person(db.Model):__tablename__ = 'Person_tb'mid = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(64), unique=True)age = db.Column(db.Integer)phones = db.relationship('Phone', backref='person', lazy='dynamic')def __repr__(self):return 'Person_name: {}'.format(self.name)# db.drop_all()# db.create_all()@app.route('/', methods=['GET', 'POST'])def index():if request.method == 'GET':persons = Person.query.all()return render_template('flask_project.html', persons=persons)if request.method == 'POST':person_name = request.form.get('person')if not person_name:return redirect('/')submit = request.form.get('search')if submit == '查询':persons = Person.query.filter_by(name=person_name)return render_template('flask_project.html', persons=persons)submit = request.form.get('add')if submit == '添加':phone_name = request.form.get('phone')person = Person.query.filter_by(name=person_name).first()if person:if not phone_name:return redirect('/')phone = Phone.query.filter(Phone.name == phone_name, Phone.person_id == person.mid).first()if phone:flash('数据已存在!')else:phone = Phone(name=phone_name, person_id=person.mid)add_data(phone)else:person = Person(name=person_name, age=random.randint(18, 25))add_data(person)if not phone_name:return redirect('/')phone = Phone(name=phone_name)phone.person = personadd_data(phone)return redirect('/')def add_data(obj):try:db.session.add(obj)mit()except Exception as e:print(e)db.session.rollback()flash("添加失败")@app.route("/update_person/<id>", methods=['GET', 'POST'])def update_person(id):person_name = request.form.get('person_{}'.format(id))if not person_name:flash("请输入修改后的人名")return redirect('/')person = Person.query.get(id)if not person:flash("人名不存在")else:person.name = person_nametry:db.session.merge(person)mit()except Exception as e:print(e)db.session.rollback()return redirect('/')@app.route("/update_phone/<id>", methods=['GET', 'POST'])def update_phone(id):phone_name = request.form.get('phone_{}'.format(id))if not phone_name:flash("请输入修改后的手机")return redirect('/')phone = Phone.query.get(id)if not phone:flash("手机不存在")else:phone.name = phone_nametry:db.session.merge(phone)mit()except Exception as e:print(e)db.session.rollback()return redirect('/')@app.route("/delete_person/<id>")def delete_person(id):person = Person.query.get(id)if not person:flash("人名不存在")else:try:Phone.query.filter(Phone.person_id == person.mid).delete()db.session.delete(person)mit()except Exception as e:print(e)db.session.rollback()return redirect('/')@app.route("/delete_phone/<id>")def delete_phone(id):phone = Phone.query.get(id)if not phone:flash("手机不存在")else:try:db.session.delete(phone)mit()except Exception as e:print(e)db.session.rollback()return redirect('/')if __name__ == '__main__':# per_one = Person(name='You', age=18)# per_two = Person(name='Me', age=81)# per_three = Person(name='JackMa', age=60)# per_four = Person(name='Panshiyi', age=50)# per_five = Person(name='DingLei', age=40)# db.session.add_all([per_one, per_two, per_three, per_four, per_five])## phone_one = Phone(name='IPhone', person_id=1)# phone_two = Phone(name='Mi', person_id=3)# phone_three = Phone(name='NOKIA', person_id=2)# phone_four = Phone(name='HUAWEI', person_id=4)# phone_five = Phone(name='OPPO', person_id=5)# phone_six = Phone(name='VIVO', person_id=1)# db.session.add_all([phone_one, phone_two, phone_three, phone_four, phone_five, phone_six])# mit()app.run(debug=True)
flask_project.html 中的代码如下:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Person</title></head><body><form method="post"><label>人名:</label> <input id="person" name="person" type="text" value=""><br/><label>手机:</label> <input id="phone" name="phone" type="text" value=""><br/><br/><input id="search" name="search" type="submit" value="查询"><input id="add" name="add" type="submit" value="添加"><br/></form><br/>{% for message in get_flashed_messages() %}{{ message }}{% endfor %}<ul>{% for person in persons %}<li>{{ person.name }} <a href="/delete_person/{{ person.mid }}">删除</a><form method="post" action="/update_person/{{ person.mid }}"><input id="person_{{ person.mid }}" name="person_{{ person.mid }}" type="text" value=""> <input id="update_{{ person.mid }}" name="update_{{ person.mid }}" type="submit" value="修改"></form></li><ul>{% for phone in person.phones %}<li>{{ phone.name }} <a href="/delete_phone/{{ phone.pid }}">删除</a><form method="post" action="/update_phone/{{ phone.pid }}"><input id="phone_{{ phone.pid }}" name="phone_{{ phone.pid }}" type="text" value=""> <input id="update_{{ phone.pid }}" name="update_{{ phone.pid }}" type="submit" value="修改"></form></li>{% else %}<li>无</li>{% endfor %}</ul>{% endfor %}</ul></body></html>
5. 创建数据表和添加数据
将代码中db.drop_all() 和 db.create_all() 的注释取消,将添加数据的代码注释也取消,然后 python flask_project.py 运行代码,会在数据库中创建两张数据表 Person_tb 和 Phone_tb ,并分别在两张表中添加几条数据。
同时,程序会自动运行在127.0.0.1:5000 上,开启监听。 先将程序停掉。
停掉之后,如果 5000 端口还被占用着,可以 kill -9 thread id 将进程关掉。
添加成功数据后,注释掉代码中的 db.drop_all() 和 db.create_all() ,将添加数据的代码也全部注释掉。
三、Nginx 安装和配置
1. 安装 Nginx
在CentOS 中安装 Nginx 很简单,使用如下命令即可。
yum install nginx -y
安装成功后,nginx 还没有启动,要先开启 nginx 服务。
systemctl start nginx
然后使用 ps 命令查看 nginx 服务是否成功开启。
ps -ef | grep nginx
开启 nginx 成功后,在 windows 浏览器上访问服务器的 80 端口(阿里云上已经配置好80端口了,访问 ip:port,ip是服务器ip,port默认就是80),页面如下,说明 nginx 安装和开启成功。
2. 修改 Nginx 配置
nginx 已经安装成功了,但 nginx 默认监听的是80端口,要部署自己的项目,还需要修改配置,增加监听端口和路由转发规则。
nginx 的配置文件是/etc/nginx/ 下的 nginx.conf ,vim nginx.conf 修改配置文件。
默认 user 是 nginx 用户(需要先创建这个用户才行),先将 user 修改为 root (实际项目中一般不会使用 root),然后参照默认监听80端口的配置增加一份 server 配置。这份配置是监听7777端口,这个端口在阿里云上配置好了,当服务器监听到7777端口的请求时,会将请求转发到 127.0.0.1:5000/ (服务器本地运行的Flask项目)。
server {listen 7777;listen [::]:7777 default_server;server_name flask_project;# Load configuration files for the default server block.include /etc/nginx/default.d/*.conf;location / {proxy_pass http://127.0.0.1:5000/;}}
增加后的完整nginx.conf 如下。
# For more information on configuration, see:# * Official English Documentation: /en/docs/# * Official Russian Documentation: /ru/docs/user root;worker_processes auto;error_log /var/log/nginx/error.log;pid /run/nginx.pid;# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.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_nopushon;tcp_nodelay on;keepalive_timeout 65;types_hash_max_size 2048;include /etc/nginx/mime.types;default_type application/octet-stream;# Load modular configuration files from the /etc/nginx/conf.d directory.# See /en/docs/ngx_core_module.html#include# for more information.include /etc/nginx/conf.d/*.conf;server {listen 80 default_server;listen [::]:80 default_server;server_name _;root /usr/share/nginx/html;# Load configuration files for the default server block.include /etc/nginx/default.d/*.conf;location / {}error_page 404 /404.html;location = /40x.html {}error_page 500 502 503 504 /50x.html;location = /50x.html {}}server {listen 7777;listen [::]:7777 default_server;server_name flask_project;# Load configuration files for the default server block.include /etc/nginx/default.d/*.conf;location / {proxy_pass http://127.0.0.1:5000/;}}# Settings for a TLS enabled server.## server {# listen 443 ssl http2 default_server;# listen [::]:443 ssl http2 default_server;# server_name _;# root /usr/share/nginx/html;## ssl_certificate "/etc/pki/nginx/server.crt";# ssl_certificate_key "/etc/pki/nginx/private/server.key";# ssl_session_cache shared:SSL:1m;# ssl_session_timeout 10m;# ssl_ciphers HIGH:!aNULL:!MD5;# ssl_prefer_server_ciphers on;## # Load configuration files for the default server block.# include /etc/nginx/default.d/*.conf;## location / {# }## error_page 404 /404.html;# location = /40x.html {# }## error_page 500 502 503 504 /50x.html;# location = /50x.html {# }# }}
修改完成配置文件后需要重启 nginx ,使配置生效。
systemctl restart nginx
四、Gunicorn 安装和配置
在运行 Flask 程序时,默认使用的是 Flask 的 runserver 服务器,现在直接 python flask_project.py 运行 Flask 项目,在 windows 上用浏览器访问http://120.77.235.113:7777/(我使用的阿里云服务器ip是120.77.235.113,记得换成自己部署的ip)就可以正常访问到 Flask 项目了。
但是,runserver 只是一个供开发者调试的微型服务器,实际部署时不会这样使用。
通常使用的 HTTP 服务器有 Gunicorn 或 uWsgi ,两个都是满足 Python WSGI 协议的HTTP服务器。使用uWsgi 需要再配置一份uWsgi 的配置文件,使用Gunicorn 会简单些,直接用命令运行代码就可以了,接下来就介绍 Gunicorn 的部署方法。
先安装Gunicorn 。
pip3 install gunicorn
然后使用如下命令运行 Flask 服务器。
gunicorn -w 1 -b 127.0.0.1:5000 flask_project:app
-w 表示启动的进程数量,-b 表示服务运行的 ip 和端口,与 nginx 配置文件中转发的地址保持一致,后面跟启动文件和 Flask 实例名称,启动文件不带后缀 .py,与实例名称中间用冒号连接。如果需要以守护进程运行项目的话,再加一个 -D 参数,关于 gunicorn 的更多参数,可以使用 -h 查看帮助信息。
运行之后,(如果需要的话)可以查看 gunicorn 是否开启成功,也可以查看服务器是否在监听 7777 和 5000 端口。
ps -ef | grep gunicornnetstat -ntlp
现在,项目运行起来了,在 windows 上访问http://120.77.235.113:7777/,功能正常,部署成功。