前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Nginx+Gunicorn部署Flask项目

使用Nginx+Gunicorn部署Flask项目

作者头像
Python碎片公众号
发布2021-02-26 15:42:59
1.9K0
发布2021-02-26 15:42:59
举报

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/ ,功能正常,部署成功。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python 碎片 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档