单一文件的Python/Django部署

397 阅读7分钟

这篇文章涵盖了我在DjangoCon US 2018的演讲《Containerless Django》中的部分内容。

自从十年前我开始接触Python以来,部署Python已经有了很大的改进。我们有virtualenv、pip、wheel、软件包哈希验证和 文件。尽管有了这些改进,它仍然感觉比它需要的更难。安装一个典型的大型项目有很多步骤,每一个步骤都很容易被绊倒。

  1. 安装Python
  2. 安装构建工具(pip/virtualenv、pipenv、poetry等)。
  3. 安装构建依赖(C语言编译器、开发库等)。
  4. 下载代码
  5. 运行构建工具
  6. 如果你使用Node来构建网站的客户端文件,请重复步骤1-5

对于运营团队来说,重复这个过程作为自动化测试的一部分是很正常的,然后在项目部署到的每台服务器上再重复一次。这也难怪Docker会如此受欢迎,因为你可以轻松地一次性构建并在任何地方进行部署。

但Docker是一个笨重的解决方案,并不适合每个项目。我很羡慕Go等语言的简单性,在那里你可以把你的项目编译成一个单一的二进制文件,在没有任何外部依赖的情况下运行。即使是Java的JAR文件格式,也需要预装Java,但除此之外只需要下载一个文件,这将是一个巨大的进步。

用于 Python 的 JAR 文件

事实证明,已经有一些项目在解决这个问题了。Twitter的PEX,Facebook的XAR,以及像PyOxidizer这样更有野心的项目,但LinkedIn的shiv找到了我们的甜蜜点。它简单、稳定,不需要特殊的工具,而且不会产生运行时的性能问题。它创建了一个包含所有代码和依赖项的ZIP文件,只需用Python解释器就可以执行。在未来的文章中,我们将深入探讨shiv在内部是如何工作的,但为了简洁起见,我们将在这里把它当作一个黑盒子。

在Django中使用Shiv

Shiv可以与任何适当的Python包一起使用。由于大多数Django开发者没有考虑过打包他们的项目,我们将在这里给你一个速成课程。

打包你的项目

以前,唯一可行的打包工具是setuptools,但自从PEP-517之后,我们现在有了许多其他的选择,包括flitpoetry。目前,setuptools仍然是事实上的标准,因此,尽管它比其他选项更残酷一些,我们将在我们的例子中使用它。

你可以使用我们的文章《在你的(Django)项目中使用setup.py》作为起点,但我们还需要采取一些步骤来确保非Python文件(静态文件、模板等)被包含在内。

最简单的方法是用一个 MANIFEST.in文件。它可能看起来像这样。

graft your_project/collected_static
graft your_project/templates

注意,这些目录需要在顶层 Python 模块才能被包含。还要注意,静态文件目录应该是你的STATIC_ROOT ,而不是 STATICFILES_DIRS 。如果你在你的个人应用程序中定义了模板,你也需要包括这些目录。

处理依赖关系

现在,每个项目都应该包括某种锁文件,它是由机器生成的,并定义了每个依赖的确切版本和它们的哈希验证。你可以通过poetrypip-tools的pip-compilepipenv来完成。

我通常让这些工具中的一个处理安装,然后通过--site-packages 选项将其site-packages 目录传递给shiv。在这种情况下,你也会传递 pip 标志--no-deps . 来安装你的本地项目,但不包括任何定义的依赖。

包括一个入口点

我们需要为shiv提供一个Python函数,当zipapp被执行时,它将运行该函数。最合理的是等价物,manage.py 。你可以直接使用django.core.management.execute_from_command_line ,但我建议写一个小的封装器,同时设置默认的DJANGO_SETTINGS_MODULE 环境变量。你可以在你的项目中创建一个__main__.py ,并包括以下内容。

import os
from django.core.management import execute_from_command_line

def main():
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_project.settings")
    execute_from_command_line()

if __name__ == "__main__":
    main()

把它放在__main__.py ,这是一个Python惯例,也可以通过python -m your_project ... 执行管理命令。

Shebang

虽然不是必须的,但你可以自定义 zipapp 的shebang,让它在特定的 Python 版本或特定位置的 Python 下执行。我通常使用/usr/bin/env python3.7 (或项目期望的任何版本)。

把这些放在一起,你的CI脚本中可能会有这样的东西。

pipenv install --deploy
pipenv run manage.py collectstatic --noinput
shiv --output-file=your_project.pyz \
     --site-packages=$(pipenv --venv)/lib/python3.7/site-packages \
     --python="/usr/bin/env python3.7" \
     --entry-point=your_project.__main__.main \
     --no-deps .

生产网络服务器

精明的读者可能已经注意到,当我们想要部署时,没有一个简单的方法来运行uwsgigunicorn 。通常情况下,你执行你的网络服务器,然后把它指向你的项目,而不是反过来。我们创建了 django-webserver所以你可以访问你喜欢的WSGI服务器作为管理命令。我们还赞助了uWSGI的工作,把它打包成一个轮子,使它在这个设置中快速和容易使用。🎺

你还需要确保你的zipapp能够提供自己的静态文件,可以通过 whitenoise或让uwsgi 处理你的静态文件(默认包含在django-webserver )。

设置

人们对Django设置做了各种奇怪的事情。你仍然可以使用DJANGO_SETTINGS_MODULE 环境变量来选择在运行时使用的设置。你也可以使用环境变量在你的设置文件中设置不同的值,但这可能很繁琐,而且有可能是一个安全问题。

相反,我更喜欢使用一个容易被机器读取的文件(JSON或YAML),也容易从配置管理或密室Hashicorp Vault等秘密管理器中生成。

我们建立了另一个包。 goodconf它让你使用静态配置文件(或环境变量)来调整不同环境下的设置。这让我们把我们的zipapp更像一个标准的应用程序,而不像一个特殊情况下的Django应用程序。处理你的部署的人应该很欣赏这一点。

部署

一旦你有了你的zipapp,部署几乎是微不足道的。

  1. 安装Python
  2. 创建配置文件
  3. 下载zipapp(我们把我们的存储在S3中)。
  4. 启动服务器 -./myproject.pyz gunicorn./myproject.pyz pyuwsgi

注意事项

用shiv创建的Zipapp并不是一个完美的解决方案。在你开始使用它们之前,你应该注意到一些事情。

提取

在第一次执行时,shiv会将压缩文件的内容缓存到~/.shiv (路径可在运行时配置)的一个独特目录中。这在第一次运行时产生一个小的延迟。这也意味着,如果你正在做大量的部署,你可能需要定期清理该目录。

系统依赖性

如果你使用依赖系统库的库,它们也需要安装在部署目标上。例如,mysqlclient 将需要MySQL库。幸运的是,轮子格式的扩散允许作者将这些库与他们的包捆绑在一起,就像Pillowpsycopg2-binarylxml 和许多其他的情况一样。

灵活性

你的zipapp将定义一个单一的入口点。虽然有可能在运行时覆盖它,甚至丢到Python解释器中,但我想把这些选项只留给调试用。如果你习惯于从你的项目中运行任意的脚本或从你的git仓库加载任意的文件,你将需要使用一些更多的纪律来为你想在部署后运行的东西制定管理命令。

可移植性

纯 Python 项目应该在不同的操作系统和可能的不同 Python 版本之间具有很好的可移植性。然而,一旦你开始编译依赖项,你最好的选择是在与你打算运行项目相同的操作系统和 Python 上进行构建。

隔离

你的项目将与系统的站点包一起在sys.path 中运行,这与用--system-site-packages 创建一个虚拟环境是一样的。为了获得更好的隔离效果,在Python中使用-S 标志,例如:python3.7 -S ./your_project.pyz 。更多细节请参见这个GitHub问题

我们使用shiv生成的zipapps成功运行这个网站和许多客户的网站已经有几个月了。我们对它的简单性和推出新软件的速度非常满意。如果你正在使用shiv或类似的技术来捆绑你的应用程序,请在下面的评论中告诉我们。