使用Docker部署Python应用的一些经验总结

ticklishbear 发布于1年前 阅读46066次
0 条评论

本篇文章源自作者团队在长期开发过程中总结的宝贵经验,其中Supervisor、Gunicorn以及Nginx更是在使用Python开发Web应用时最常用的软件,因此对于打算使用Docker部署Python应用的读者而言,这些最佳实践是很有参考价值。同时希望各位在日常实践过程中,也能将各自踩到过的“坑”以及宝贵的经验分享出来,大家共同进步!

我们可以使用Docker简单而高效的部署Python应用,同时,也有一些最佳实践来帮助我们愉快的完成部署。当然,也不是说这些最佳实践就是完成部署的唯一方式,只不过我们团队发现它们具有高可用性,并且容易维护。注意本文中大多数内容都只是代表我的立场,基于Docker的实现方式有很多,你可以随便选择。本文中我不会过多的介绍Volume,因为它可能需要一个单独的话题来解释。我们通常会使用Volume将源代码复制到容器中,而不是在每次运行时都重新构建。

DEBIAN_FRONTEND

Docker用户应该都很熟悉这个环境变量,它告知操作系统应该从哪儿获得用户输入。如果设置为”noninteractive”,你就可以直接运行命令,而无需向用户请求输入(译者注:所有操作都是非交互式的)。这在运行apt-get命令的时候格外有用,因为它会不停的提示用户进行到了哪步并且需要不断确认。非交互模式会选择默认的选项并以最快的速度完成构建。

请确保你只在Dockerfile中调用的RUN命令中设置了该选项,而不是使用ENV命令进行全局的设置,因为ENV命令在整个容器运行过程中都会生效,所以当你通过BASH和容器进行交互时,如果进行了全局设置那就会出问题。例子如下:

# 正确的做法 - 只为这个命令设置ENV变量
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python3
# 错误地做法 - 为接下来的任何命令都设置ENV变量,包括正在运行地容器
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get install -y python3

requirements.txt

相比于基本的代码(codebase),应用的依赖很少发生变化,因此我们可以在Dockerfile中安装项目依赖,这也可以加快之后的构建速度(之后的构建只需要构建变更的代码)。Docker容器的层级构建可以缓存依赖安装的过程,所以之后的的构建速度会非常快,因为它不需要重新下载和构建依赖。

文件顺序

按照上面的思路(利用缓存)来推断,文件添加到容器的顺序至关重要。我们应该把频繁变更的文件放置到Dockerfile的下方,以便充分使用缓存来加速Docker的构建过程。例如,应用配置、系统配置和依赖都很少改变,我们就可以把它们放到Dockerfile的顶部。而源文件,比如路由文件、视图(views)和数据库代码会经常发生改变,所以我们就可以把它们放在Dockerfile的下方,注意是Docker配置命令(EXPOSE、ENV等)的下方。

另外,不要考虑如何将你的文件拷贝到Docker,它不会加快你的构建速度,因为大多数的文件根本不会用到,比如应用源文件。

应用密钥

之前我们一直不知道如何把应用密钥安全的传递给应用,后来我们发现可以使用docker run命令中的env-file参数。我们会把所有的密钥和配置都放在app_config.list文件中,然后通过这个文件交付给应用。具体如下:

docker run -d -t -—env-file app_config.list <image:tag>

这个方法允许我们简单地改变应用设置和密钥,而无需重建一个容器。

注意:请务必确保app_config.list在.gitignore文件的记录中,不然它不会被检录到源文件中。

Gunicorn

我们使用Gunicorn作为容器内部的应用服务器,Gunicorn非常的稳定并且性能很好,它有非常多的配置选项,比如指定worker数量和类型(green threads、gevent等)的能力,你可以根据负载来调整应用,以获得最佳性能。

启动Gunicorn很简单:

# 安装
pip3 install gunicorn

# 运行服务器
gunicorn api:app -w 4 -b 127.0.0.1:5000

最后是在Nginx后面运行你的应用服务器,这样你可以进行负载均衡

supervisord

你是不是想过在容器中运行多个进程?我想Supervisord绝对是你的最佳辅助工具。假设我们想部署这样一个容器,它包含Nginx反向代理以及Gunicorn应用。你通过BASH脚本可能就能实现,但是让我们想更加简洁一点。

Supevisor是“一个进程控制系统,它支持用户在类UNIX操作系统上,监视并控制一些进程”。听起来很完美!你需要先在你的Docker容器内安装Supervisor。

RUN DEBIAN_FRONTEND=noninteractive apt-get install -y
supervisor

为了让Supervisor知道该运行什么以及如何管理进程,我们接下来需要为它写一个配置文件。

[supervisord]
nodaemon = true  # 这个会让supervisord运行在前端

[program:nginx]  # 你想运行的第一个程序的命名
command = /usr/sbin/nginx  # nginx可执行程序的路径
startsecs = 5  # 如果nginx保持开启5s,我们视为启动成功

[program:app-gunicorn]
command = gunicorn api -w 4 -b 127.0.0.1:5000
startsecs = 5

这是非常基本的配置,它还有很多的配置项,比如控制日志、stdout/stderr重定向、重启策略等。这个工具真不错。

一旦你完成了配置,请确保Docker将其复制到了容器中。

ADD supervisord.conf /etc/supervisord.conf

让Supervisor作为容器的自启动命令。

CMD supervisord -c /etc/supervisord.conf

它将会在容器启动的时候,运行Gunicorn和Nginx。如果已经配置过了,那将会按需重启它们。

学到的东西以及未来的目标

我们已经花了很长时间在Docker中部署代码,并且接下来会投入更多的时间。在使用Docker的过程中,我们学到的最重要经验就是如何最小化思考(think minimally)。在一个容器中运行你的整个系统真的很诱人,但是在应用各自的容器中运行应用进程却更容易维护。一般情况下,我们会在容器中运行Nignx和Web服务器,并且在一些场景中,使用单独的容器来运行Nginx却没有任何优势,它可能只会增加复杂度。我们发现对于大多数情况,它在容器中的开销是可接受的。

我希望这些信息对各位有价值!当我们团队学到更多最佳实践时,我会更新这篇文章。

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。