AI画家第五弹——从0到1部署你的RESTful API

2,496

上一篇文章中我们已经利用Flask制作了一个RESTful API,然而文中末尾我们我们将代码运行起来的时候确发现是localhost:xxxx,这也就意味着我们没法通过外网访问我们提供的API,所以这样做出来的程序即使部署到服务器上也没有任何用处。这一篇我们就来详细的说下怎么部署到服务器上然后实现外网访问吧。

环境准备

工欲善其事,必先利其器。还是老样子,既然要选择部署到服务器上,我们首先肯定得要有一个服务器,这里我选择XX云的学生机(别问为什么,问就是便宜)。系统选择Ubuntu18.04,需要安装的东西有:

  • Python 3.6
  • Nginx
  • pyenv
  • pipenv
  • Flask + Gunicorn

这里我们用pyenv管理不同的python版本,这里可能就有人要问了,Ubuntu不是自带python2和python3吗,为什么还要用pyenv来管理,之前的文章我们也说过,用pyenv管理python会非常的方便,方便到只需要一行代码就可以安装切换系统的Python版本,废话不多说,接下来就开始我们的操作吧。

连接到远程服务器

如何购买XX云的云主机我就不多说了,当你买好之后我们在后台查看到我们购买的云主机的外网IP,比如我的服务器的外网IP地址就是94.191.9.43(希望各位大佬手下留情,别弄他):

接下来就是连接到服务器了,工具有很多,xshell、putty或者terminal都可以,这里我推荐一个工具Termius,推荐他的原因有两个:颜值,作为一个颜控,Termius是我见过的最好看的SSH客户端,没有之一。同时还是跨平台的,win/mac/linux/android/ios都支持,这点不知道比xshell高到哪里去了。

5c7eb7590c28be6ee5c8afdb_termius

连接也很简单,我们在termius中添加需要连接的host,然后输入用户名和密码即可登录了,出现如下字样说明登录成功。

利用pyenv安装制定版本的python

之前的文章介绍过如何在mac下安装pyenv,虽然linux下的安装方法类似,但毕竟本文的题目是是从0到1部署,还是再详细说一下吧。

  • 下载安装pyenv
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash 
  • 先查看是否存在~/.bash_profile文件
cat ~/.bash_profile
  • 如果没有就新建该文件
touch ~/.bash_profile
  • 然后执行以下命令将pyenv添加到系统变量中
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile  
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile 
echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
source ~/.bash_profile

接下来执行pyenv命令,如果出现下图则说明安装成功。

接下来我们利用pyenv来安装不同版本的python,只需要一行命令:

pyenv install xxx(版本号)

比如说我们想安装python 3.6.7,只需要执行pyenv install 3.6.7即可。这一步可能有点慢,主要看服务器的网速和性能了。

如果出现安装失败的话,可以尝试执行下面代码安装一些依赖库后再重新安装python:

sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev

出现如下字样说明安装成功。

image-20190527155431084

接下来我们执行pyenv versions查看我们系统中目前已有的python

* system (set by /home/ubuntu/.pyenv/version)
  3.6.7

执行pyenv global 3.6.7将3.6.7作为系统默认的python环境,执行下python命令查看系统python版本,如下图所示我们已经将系统的默认python版本切换为3.6.7了。

image-20190527155707337

安装pipenv管理项目的虚拟环境

  • 先升级pip
pip install --upgrade pip
  • 然后安装pipenv
pip install pipenv

最后执行pipenv查看是否安装成功。

他的具体用法参考我之前的一篇文章👉《Python 管理哪家强?》

将项目上传至服务器

其实到这里项目环境基本算是搭建完成了,不过也只是刚刚能用,离我们最终需要搭建的环境还差点东西,这个后期再说,先来把项目上传到我们的服务器上,这里我选择的工具是FileZilla,一个开源免费的FTP客户端(而且颜值也不错),新建站点后添加服务器IP,输入用户名和密码后点击连接即可连接到远程服务器,如下图所示。

连接成功后我们可以在工具右侧看到服务器的文件列表,我个人比较喜欢在/home/ubuntu下新建一个dev文件夹专门用来存放项目的代码,方便管理,如下所示。

image-20190527162745456

我们在工具左侧选择要上传的文件,拖到指定文件夹中即可实现文件上传。这里我讲代码存放到/home/ubuntu/dev/python/stylize路径下了,我们也可以通过terminal查看到文件。

这个代码在上篇文章中有提到,可在微信公众号「01二进制」后台回复「风格迁移API」获得

实现外网访问

最原始的办法

我们已经将项目代码上传至服务器了,命令行进入该文件夹,然后执行pipenv install创建虚拟环境并下载所需依赖。接下来再执行pipenv shell进入虚拟环境,这时我们可以看见路径前会有一个括号,里面是虚拟环境的名字,如下所示。

(stylize) ubuntu@VM-0-10-ubuntu:~/dev/python/stylize$ 

对上一节提供的main.py代码,我们需要对main函数进行一些修改:

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=8080, debug=app.config['DEBUG']

相对于上一节内容,此次修改添加了host='0.0.0.0'这一参数,这一参数的的含义就是实现WSGI服务器的外网访问,如果不添加这个host,默认只能内网访问,这时我们再执行python main.py启动整个项目,出现如下效果说明服务已经启动了。

image-20190527191041654

这时候我们在浏览器中输入ip:8080即可看到效果,例如我这的输入的是http://94.191.9.43:8080,出现的结果如下:

⭐️需要注意的是,你需要在你购买的云服务的控制台设置安全组,以便给你的服务器开放相应的端口

至此我们就实现了flask的应用在服务端的部署以及实现外网访问了。

这样就结束了吗?

但是这样就结束了吗?当然不是,如果我们把连接断掉,这个服务就没办法执行。这时可能就有人会说了,那在执行python main.py这一命令前加一个nohup不就可以实现后台运行了吗?没错,这样确实可以实现后台运行,但是在实际的生产环境中是不会有人使用这样的方式部署的,原因很简单,Flask 是一个web框架,而非web server,直接用Flask拉起的web服务仅限于开发环境使用,生产环境不够稳定,也无法承受大量请求的并发,在生产环境下需要使用专业的服务器软件来处理各种请求。既然flask自带的服务器性能很差,那我们自然要清楚专业的服务器,这里的可选项有Gunicorn、 Nginx或Apache,通常情况下,我们选择Gunicorn+Nginx这样的组合。

Gunicorn是什么?

这里简单回答两个问题,Gunicorn是什么以及为什么是Gunicorn。

第一个问题,Gunicorn是什么?

Gunicorn中文名"独角兽",一个开源Python WSGI UNIX的HTTP服务器。

第二个问题,为什么是Gunicorn?

效率高,使用方便。

为什么是Gunicorn+Nginx?

《AI画家第三弹——毕业设计大杀器之Flask》这篇文章中我们曾提过web服务器应用服务器这两种服务器,其中应用服务器既可以解析静态资源,也可以解析动态资源,但是解析静态资源的能力不如web服务器。而Nginx就是web服务器,Gunicorn是应用服务器,采用这样的组合,一方面基于Nginx转发Gunicorn服务,在生产环境下能补充Gunicorn服务在某些情况下的不足,另一方面,如果做一个Web网站,除了服务外,还有很多静态文件需要被托管,这是Nginx的强项,也是Gunicorn不适合做的事情。所以,基于Flask开发的网站,部署时用Gunicorn和Nginx,是一个很好的选择。

那为什么需要Nginx转发Gunicorn服务?

按照上面的解释,其实直接使用Gunicorn就完全可以实现外网访问了,为什么非要加一层Nginx呢?

原因其实也很简单,Nginx功能强大,用Nginx转发Gunicorn服务,重点是解决“慢客户端行为”给服务器带来的性能降低问题;另外,在互联网上部署HTTP服务时,还要考虑的“快客户端响应”、SSL处理和高并发等问题,而这些问题在Nginx上一并能搞定,所以在Gunicorn服务之上加一层Nginx反向代理,是个一举多得的部署方案。

如果想深入了解Nginx/Gunnicorn在项目中扮演的角色,推荐阅读👇

Nginx、Gunicorn在服务器中分别起什么作用? - 知乎

www.zhihu.com/question/38…

开始实现

启动Gunicorn

上面说了那么多基础知识和原理,赶紧来实际操作下吧,首先安装Gunicorn,安装方法很简单,在pipenv虚拟环境中,我们直接运行以下代码就可以了。

pipenv install gunicorn

然后运行

gunicorn -D -w 3 -b 127.0.0.1:8080 main:app

这样就可以启动一个web服务,这里我们来解释下这些参数。

  1. gunicorn:命令的名称,不多解释
  2. -D 表示后台运行
  3. -w 表示线程
  4. -b 指定ip和端口(建议使用本地端口,方便nginx进行代理。)
  5. main是项目的启动文件(main.py)
  6. app 是全局变量 (app = Flask(__name__)

虽然说上述命令也不是很难,但是每次都输这么长的命令难免会出错,有没有什么简单的方法呢?答案自然是有的,我们可以将这些命令的参数以配置文件的形式保存到本地。

首先在项目根目录下新建一个gunicorn.conf,然后写入下面的信息:

# 并行工作线程数
workers = 4
# 监听内网端口8080【按需要更改】
bind = '127.0.0.1:8080'
# 设置守护进程【关闭连接时,程序仍在运行】
daemon = True
# 设置超时时间120s,默认为30s。按自己的需求进行设置
timeout = 120
# 设置访问日志和错误信息日志路径
accesslog = './logs/acess.log'
errorlog = './logs/error.log'

作用就如注释所言。这时我们再启动项目只需要执行下面的命令即可

gunicorn -c gunicorn.conf main:app

记得提前创建好logs这个文件夹哦😊

关闭Gunicorn

经历过上述操作后我们就可以启动一个项目了,那该如何关闭呢?

很简单,分两步

step1:

pstree -ap|grep gunicorn

显示结果:

(stylize) ubuntu@VM-0-10-ubuntu:~/dev/python/stylize$ pstree -ap|grep gunicorn
  |-gunicorn,9349/home/ubuntu/.local/share/virtualenvs/stylize-rKP1K2-f/bin/gun
  |   |-gunicorn,9352/home/ubuntu/.local/share/virtualenvs/stylize-rKP1K2-f/bin/gun
  |   |-gunicorn,9353/home/ubuntu/.local/share/virtualenvs/stylize-rKP1K2-f/bin/gun
  |   `-gunicorn,9354/home/ubuntu/.local/share/virtualenvs/stylize-rKP1K2-f/bin/gun
  |                       |-grep,9365 --color=auto gunicorn

step2:

kill -9 9349

-9表示强制关闭,9349表示运行的进程号(根据你自己的服务器情况来定)

安装配置Nginx

在Ubuntu上安装Nginx很简单,一条命令就可以了:

sudo apt-get install nginx

接下来就是配置了,nginx的默认配置文件在/etc/nginx/sites-enabled/default中,执行sudo vim default,修改下面部分的代码

server {
    listen 80;
    server_name localhost;
    location /{
        proxy_pass http://127.0.0.1:8080;
    }
}

这里我们解释下,修改上述内容的作用是:将外部通过80端口发来的请求,代理给127.0.0.1:8080端口。还记得我们在gunicorn.conf中设置了flask的启动地址为127.0.0.1:8080,到这里终于派上了用场。

然后执行以下命令重启nginx

sudo nginx -s reload

这时我们再访问http://94.191.9.43,就不用添加端口号了。

image-20190528014523423

如果你有域名的话,在后台把域名解析到这个ip上就可以实现域名访问了,是不是很有趣呢?

当然在Gunicorn上再加一层Nginx服务,除了实现反向代理,另一个原因就是用它配置HTTPS非常方便,这里我就不多说了,如果读者有兴趣的话可以查阅相关资料,或者在评论区/后台留言我,如果想了解的人数多的话我就找个机会挑出来单独写下。

日志!日志!日志!

除了上述HTTPS的问题,不知道你们有没有发觉还少了点东西。没错,就是日志!重要的事情说三遍!作为一个后台程序,怎么可以少了日志呢?既然我们已经使用Gunicorn接管了Flask项目,那怎么才能获得Flask的日志信息呢?别急,Gunicorn早就帮你想好了,很简单,只需要在启动文件也就是main.py中添加一段代码即可。

import logging
if __name__ != '__main__':
    gunicorn_logger = logging.getLogger('gunicorn.error')
    app.logger.handlers = gunicorn_logger.handlers
    app.logger.setLevel(gunicorn_logger.level)

这样的写法主要做了4件事:

  1. if __name__ != '__main__'这种写法很少见,意思是仅当该文件作为模块被导入时内部的代码才会被执行,有个好处,避免了硬编码,当执行gunicorn main:app跑的时候main.py会被当成模块导入到gunicorn内部, 因为gunicorn实际上就是纯pyhton写的
  2. 获取gunicorn的logger
  3. 将gunicorn的logger和flask app的logger绑定在一起
  4. 将绑定的logger 的level设置成gunicorn logger的level, 因为最终输出的log level是在gunicorn中配置的

然后重新启动gunicorn就可以了,日志存放的路径我们之前也已经在gunicorn.conf中定义过了,用cat命令查看就OK了。

最后

不知不觉已经码了将近4000字,这次写这么多一方面是因为最近用这东西的时候发现可以扩展的东西太多了,另一方面也是希望可以和其他博客的内容有所区别,不仅仅把实现过程表现出来,还可以把自己使用过程中的一些习惯、心得体会分享出来和大家交流。当然部署这里能挖的还有很多,比如如何获取来访者的IP地址并加以分析,如何用Nginx配置HTTPS等等等等,有兴趣的可以相关资料,或者在留言区/后台和我讨论,你们的支持才是我前进的最大动力!