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 安装包。
代码语言:javascript复制tar -xvf mysql-5.7.27-1.el7.x86_64.rpm-bundle.tar
安装 MySQL5.7 。
代码语言:javascript复制yum install mysql-community-* -y
查询 root 用户默认的密码。
代码语言:javascript复制grep password /var/log/mysqld.log
使用 root 用户登录 MySQL ,然后修改 root 用户的密码(密码自己设,不用跟我一样)。
代码语言:javascript复制mysql -u root -p
# 修改root的密码
alter user 'root'@'localhost' identified by 'Mysql!123';
创建需要使用的用户 admin。
代码语言:javascript复制grant all privileges on *.* to 'admin'@'%' identified by 'Mysql!123';
退出 root 用户,使用 admin 用户登录 MySQL ,创建需要使用的数据库 MyDB_one 。
代码语言:javascript复制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 ,就不用担心兼容问题了。
代码语言:javascript复制yum install python36 -y
删除 /usr/bin/ 下的 python ,创建一个软连接指向 python3.6 ,这样默认的 Python 版本就是 python3.6 了。
代码语言:javascript复制rm /usr/bin/python
ln -s /usr/bin/python3.6 /usr/bin/python
3. 安装 Flask 和 Flask-SQLAlchemy
执行如下命令安装 Flask 和 Flask-SQLAlchemy ,如果是 Python2.7 就将 pip3 改成 pip。
代码语言:javascript复制pip3 install Flask
pip3 install flask-sqlalchemy
因为使用的是 MySQL,要使用 Flask-SQLAlchemy 连接数据库,还要安装 flask-mysqldb 。
代码语言:javascript复制pip3 install flask-mysqldb
安装时会报如下错误,是因为在 python3.6 中找不到 Python.h ,需要安装 python3-devel 。
代码语言:javascript复制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 。
代码语言:javascript复制yum install python3-devel -y
但是执行后会报如下错。
代码语言:javascript复制File "/usr/bin/yum", line 30
except KeyboardInterrupt, e:
^
SyntaxError: invalid syntax
因为 yum 是使用 Python2 实现的,默认的 python 已经被改成 python3.6 了(上面创建软链接时) ,需要将 yum 使用的 Python 版本改回 python2.7 。
代码语言:javascript复制vim /usr/bin/yum
将第一行的 /usr/bin/python 改为 /usr/bin/python2.7,重新运行还会报如下错误。
代码语言:javascript复制Downloading packages:
File "/usr/libexec/urlgrabber-ext-down", line 28
except OSError, e:
^
SyntaxError: invalid syntax
原因与上面相同,继续修改。
代码语言:javascript复制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 命令 ,可以先安装。
代码语言:javascript复制yum install tree -y
flask_project.py 中的代码如下:
代码语言:javascript复制# coding=utf-8
from flask import Flask, render_template, request, redirect, flash
from flask_sqlalchemy import SQLAlchemy
import random
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://admin:Mysql!123@127.0.0.1:3306/MyDB_one'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_ECHO'] = True
app.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 = person
add_data(phone)
return redirect('/')
def add_data(obj):
try:
db.session.add(obj)
db.session.commit()
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_name
try:
db.session.merge(person)
db.session.commit()
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_name
try:
db.session.merge(phone)
db.session.commit()
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)
db.session.commit()
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)
db.session.commit()
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])
# db.session.commit()
app.run(debug=True)
flask_project.html 中的代码如下:
代码语言:javascript复制<!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 很简单,使用如下命令即可。
代码语言:javascript复制yum install nginx -y
安装成功后,nginx 还没有启动,要先开启 nginx 服务。
代码语言:javascript复制systemctl start nginx
然后使用 ps 命令查看 nginx 服务是否成功开启。
代码语言:javascript复制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项目)。
代码语言:javascript复制 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 如下。
代码语言:javascript复制# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/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_nopush on;
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 http://nginx.org/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 ,使配置生效。
代码语言:javascript复制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 。
代码语言:javascript复制pip3 install gunicorn
然后使用如下命令运行 Flask 服务器。
代码语言:javascript复制gunicorn -w 1 -b 127.0.0.1:5000 flask_project:app
-w 表示启动的进程数量,-b 表示服务运行的 ip 和端口,与 nginx 配置文件中转发的地址保持一致,后面跟启动文件和 Flask 实例名称,启动文件不带后缀 .py,与实例名称中间用冒号连接。如果需要以守护进程运行项目的话,再加一个 -D 参数,关于 gunicorn 的更多参数,可以使用 -h 查看帮助信息。
运行之后,(如果需要的话)可以查看 gunicorn 是否开启成功,也可以查看服务器是否在监听 7777 和 5000 端口。
代码语言:javascript复制ps -ef | grep gunicorn
netstat -ntlp
现在,项目运行起来了,在 windows 上访问 http://120.77.235.113:7777/ ,功能正常,部署成功。