supervisord+nginx部署fastapi应用
本地测试与线上生产环境部署的区别
通常在写一个fastapi的应用时,我们会直接使用uvicorn来运行,uvicorn可以很方便的开启一个本地的服务,并会在我们代码更新的时候重载,可以说是很方便了:
1 | uvicorn main:app --host=0.0.0.0 --port=8000 --reload |
其中main是我们项目的入口代码文件的名称,我的项目中这个文件叫做main.py,里面是fastapi的应用app,所以写成了main:app。
但是uvicorn用于线上部署会有一些缺点:
- 线上服务器如果部署了多个网站,使用多个域名来匹配,肯定会需要使用nginx之类的软件
- uvicorn可以让应用跑起来,但是应用如果因为一些原因挂了,或者出现了异常,需要有一个daemon进程来监控,uvicorn是不能胜任的
- 我们希望线上部署的时候,应用是在后台运行,不需要在控制台输出
因此,在线上部署的时候,我们往往会使用其他的软件来做这件事情。
生产环境部署的步骤
先说一句,我使用的系统是ubuntu20.04,其他linux系统可能会有所出入
部署nginx
在我的服务器上,有多个网站在运行,所以我使用了nginx来做所有网站的访问入口:
Nginx(“engine x”)是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的 Web和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。
nginx的安装我一直都是使用lnmp工具来进行的,具体安装过程就不赘述了,lnmp网站链接:https://lnmp.org/install.html
nginx监听了80和443端口,然后根据请求的具体domain,来将请求进一步转发到内部程序。
也就是说,fastapi应用其实监听的端口是其他端口,比如8000,nginx监听80和443端口,再将请求转发到8000端口。
nginx需要添加一个反代fastapi应用的配置:
1 | server { |
其中较为关键的是proxy_pass这一句,这里告诉了nginx要把请求转发到哪一个端口。
proxy_set_header下面的内容也比较关键,这里会让fastapi通过headers知道用户的真实ip地址、用户请求的域名、请求的方法(http还是https),否则在fastapi内部就会看到所有的请求都是来自127.0.0.1,都是http请求,这岂不是很糟糕?
这里还通过nginx为我们的访问添加了一层ssl,将http强制转换成https:
- return 301 https://$http_host$request_uri; 这一句就是把80端口接收到的http请求转发到443端口的https。
- listen 443 ssl http2; 这一句一是加上了ssl,二是指定了http版本为http2,http2版本与http1相比有一些改进
- ssl_certificate和ssl_certificate_key是ssl配置最关键的ssl证书私钥和公钥,我的网站大部分都是使用免费申请的ssl证书,下一篇再来讲如何使用工具申请免费的ssl证书
- ssl还有很多其他的参数可以配置,这里我也没有太细究,日后再进行学习
部署我们的项目代码
fastapi是一个python应用,python应用通常再python版本、各个应用的包的版本之间都会有区分,通常本地测试的时候用的是哪写版本,生产环境就也用同样的版本,避免版本不一致带来各种潜在的问题。在我的项目中都会有一个requirements.txt文件记录这些信息:
1 | fastapi~=0.77.1 |
线上部署的时候安装这些版本的软件包,如果有其他python项目也在,那么建议新建一个虚拟环境来部署:
1 | # 在项目文件夹下面建立一个虚拟环境,虚拟环境所处的文件夹名称与虚拟环境名称是一致的 |
部署gunicorn
gunicorn与fastapi的配合还是比较好的,gunicorn也有文档讲有哪些方式来部署:https://docs.gunicorn.org/en/latest/deploy.html
1 | /path/to/python -m pip install gunicorn |
现在我们就可以使用gunicorn来运行我们的项目了:
1 | /path/to/gunicorn main:app -k uvicorn.workers.UvicornWorker --bind 127.0.0.1:5082 |
这里的-k uvicorn.workers.UvicornWorker是fastapi官方说明中指定的,–bind则告诉gunicorn要监听哪一个端口,其实目前为止gunicorn和uvicorn基本是一样的角色。
当然,为了gunicorn日志等其他配置也都可以管理,不要每次都写这么长的参数,我们建立一个gunicorn.py
来把gunicorn的配置放在里面:
1 | debug = False |
这个配置文件中有几个地方也很重要:
- debug,生产环境部署我们一般不开启debug模式,防止把代码细节泄露出去
- bind,指定监听的主机和端口,这里指定127.0.0.1来避免其他机器也可以通过端口号直接访问服务,指定的端口5082一定要和nginx端口一致
- daemon,我们还要使用supervisor来管理进程的开启,包括实现开机自启等,就不需要gunicorn再来开启一个监控进程了,让supervisor来做这件事情
- work_class,fastapi官方指定的参数,这是为了让fastapi和gunicorn兼容的做法,一定不要省略
- workers,按照你服务器的cpu数量来确定,通常是2*cpu数量+1,当然也可以是别的值,我的应用比较小,只是自己用用,2个就足够了,开启太多了也浪费资源
- pidfile,我们使用supervisor来管理的时候,一定不能指定pidfile,否则supervisor监控会失效!!!不指定就不会创建pid文件
- chdir,在启动之间切换到项目文件夹,避免找不到main这样的事情发生
有了配置文件,我们开启gunicorn就更简单一些了:
1 | /path/to/gunicorn -c /path/to/gunicorn.py main:app |
部署supervisor
这里之所以选择supervisor而不是systemctl主要是因为对systemctl用起来老出错,一会儿报安全错误,一会儿找不到路径之类的,太烦了,后来选择了supervisor,总的还是比较省心的,也比较简单。
Supervisor is a client/server system that allows its users to control a number of processes on UNIX-like operating systems. It was inspired by the following:
Convenience
It is often inconvenient to need to write
rc.d
scripts for every single process instance.rc.d
scripts are a great lowest-common-denominator form of process initialization/autostart/management, but they can be painful to write and maintain. Additionally,rc.d
scripts cannot automatically restart a crashed process and many programs do not restart themselves properly on a crash. Supervisord starts processes as its subprocesses, and can be configured to automatically restart them on a crash. It can also automatically be configured to start processes on its own invocation.
首先安装:
1 | apt intall supervisor |
虽然官方文档也可以使用pip安装,但是pip安装之后感觉只是一个残次的,supervisor不能开机启动,因此建议不要用pip安装!用pip安装的也将其卸载了吧:
1 | pip3 uninstall supervisor |
用apt或者yum安装之后,就会出现一个文件夹/etc/supervisor
,下面包含这些内容:
1 | supervisord.conf |
supervisord.conf是supervisord的配置文件,我们需要对这个文件做一下修改,或者直接粘贴这些内容:
1 | [unix_http_server] |
我这里粘贴出来的内容是必须要有的!
其他还有一些配置,可以导出sample看看添加:
1 | echo_supervisord_conf > /etc/supervisor/supervisord.sample.conf |
然后要告诉supervisor我们的gunicorn程序,因此在conf.d文件夹里面新建一个文件项目名称.conf
加入以下内容:
1 | [program:gunicorn] # 这里的gunicorn就是告诉supervisor我们的应用名称叫做什么 |
其中的路径都推荐使用绝对路径!
现在就可以开启supervisor了:
1 | systemctl enable supervisor.service |
不出意外的话,我们的supervisor就已经运行起来了,gunicorn也已经运行起来了
然后可以通过supervisorctl来查看我们的应用状态:
1 | supervisorctl status |
应该会看到以下内容:
1 | gunicorn RUNNING pid 5188, uptime 0:01:13 |
gunicorn已经在运行了。
supervisorctl还有一些其他用法:
1 | supervisorctl reread && supervisorctl update # 如果修改了配置文件要重新读取并更新 |
大功告成了
完成了以上工作,fastapi总算是部署完成了,挺费劲吧
在这个过程中其实我一开始摸索的时候有很多地方走了弯路,可以说都是经验教训,比如说:
- systemctl start supervisor提示找不到service,因为是用了pip安装supervisor
- supervisor总是提示找不到socket文件(unix:///tmp/supervisor.sock no such file)或者access denied,也是因为用了pip安装supervisor,需要pip卸载后,再apt安装,再重新进入shell环境(这一步也很重要),才能使用
- supervisorctl start找不到应用:ERROR (no such process) ,是因为我把配置文件放在
/etc/supervisord.conf
而不是/etc/supervisor/supervisord.conf
还有很多一开始尝试systemctl来让gunicorn开启自启的,配置太麻烦了。
希望我的经验可以给后来人一些启示吧,少走弯路。
还有再网上看到一些人在mian.py中有这样的内容:
1 | if __name__ == "__main__": |
这样也会导致出错(端口占用),把这些内容删了吧。
supervisord+nginx部署fastapi应用