为什么我在Docker中仍然使用Python虚拟环境

文摘   2024-09-19 08:30   美国  

每当我发布关于我的Python Docker工作流程的 内容[1] 时,我总是会被质疑在Docker容器中使用 虚拟环境[2] 是否有意义。和往常一样,这是一个权衡,而我倾向于选择标准和可预测性。

我很清楚,特别是对于像 python:.*[3] 这样的单一用途Python容器,通过全局安装所有内容并从Python安装的内部COPYsite-packages,或者将 PYTHONUSERBASE[4] 设置为类似/app的值,然后使用pip install --user而不是pip install,再COPY过来,来实现多阶段构建是很流行的做法。这些都是基于某种方式更简单且一层隔离就足够的前提。

我不否认一层隔离的充分性,但多年来,我发现在生产环境中,在谈论简单性时需要考虑不止一个方面。


作为一个总体主题,我的目标不是盲目地遵循一些增加复杂性但收益可疑的~最佳实践~,仅仅因为某个大型科技公司的开发者倡导者在会议上这么说。但我花了很多时间思考我所做事情的次要影响。

对我来说,复杂性不在于我需要按多少个键 - 而在于推理我所做事情的后果有多困难。

所以,这里是我不完整的仍然使用虚拟环境并计划在未来继续使用的原因列表:

可预测性和熟悉度

它们的结构定义明确,旨在容纳单个Python应用程序。 我喜欢它们是一个包含binlibshare目录的目录层次结构,这使得它们默认成为自包含应用程序的绝佳"容器"。它邀请在额外的etc目录中存储辅助文件,包括偶尔的配置文件。所有这些使它们非常适合放入/opt/app/app中。

是的, 与十年前不同[5] ,你不再需要将应用程序与系统Python隔离。但将你的代码保存在一个隔离的、定义明确的位置和结构中本身就有价值。

我负责数十个服务,所以我很欣赏知道我部署的所有内容都在/app中的一致性,如果它是一个Python应用程序,我知道它是一个虚拟环境,如果我运行/app/bin/python,我就会得到虚拟环境的Python,我的应用程序已准备好被导入和运行。

标准和沟通

所有这些一致性使得团队内部和跨团队的沟通更加容易。每个人都知道当我说/记录部署工件是一个虚拟环境时意味着什么。如果他们不知道,互联网上充满了文档 - 包括 官方文档[1] 。这就像 Black[6] 让我们停止思考和争论代码风格,并为更重要的事情释放了那些心理资源1。这就是标准的最终价值,即使人们不喜欢它们的一切。

而且虚拟环境已经成为 Python的核心功能[1] 12年了,自2000年代中期以来一直是Python社区的核心概念。这是我们在Python中最接近封闭的、标准化的、被充分理解的应用程序构建工件。这是一个牵强的类比,但我认为它们就像编译语言中链接动态二进制文件的结果。

你可以 在本地[7] 使用它们,你可以使用 发行版包[8] 部署它们,你也可以 使用Docker容器部署它们[0]在开发和生产中使用相同的工具和原语是很好的。 这意味着你了解你的工具,需要记住的东西更少。因此,使用不同的方法来部署你的应用程序应该带来切实的好处。

导入复杂性崩溃

虚拟环境的一个普遍优点 - 无论是在本地还是在Docker中 - 是你可以缩小Python寻找要导入的代码的相关搜索路径2。通过传递-I 以隔离模式运行Python[9] 将进一步缩小这个范围。

所以,如果你a)从不全局安装任何东西3,并且b)使用虚拟环境中的python二进制文件同时传递-I,你知道不在标准库中的所有东西必须在虚拟环境中。这使得Python的导入行为更加可预测,调试导入问题不再像一个谋杀之谜。

这就引出了...

一个可以安全忽略但我需要说出来的额外观点

地狱没有比我对pip install --user的感受更强烈的愤怒了。这是一个具有吸引力的麻烦,对Python糟糕的打包声誉造成了很大的损害。

它主要用于掩盖操纵系统site-packages目录的更糟糕的影响 - 这是破坏Python安装的最常见原因。所有这些都是为了启用用户本地安装4,这本身就是一个坏主意。从这个意义上说,_npm_生态系统以其项目目录本地优先的打包方式远远领先于我们,我们仍然停留在旧的、懒惰的方式中。

最终,它通过添加另一个移动部分使得推理Python安装变得更加复杂。如果每次有人因为他们用户本地Python包中的奇怪东西而在bug跟踪器上对我大喊大叫时我能得到一毛钱,我就不需要 乞求赞助[10] 了。尽管我很喜欢 PDM[11] ,但我每天都感谢克苏鲁 PEP 582 /[12] 被拒绝了,因为它会增加另一个混淆的向量。标准化项目本地.venv无疑是正确的举措,即使更喜欢将我的虚拟环境存储在一个中心位置。

最后:到底为什么呢?

你到底在试图解决什么问题? 这不是一个额外的工具或额外的概念 - 它要么就在 标准库[1] 中,要么你已经在使用 uv[13] 了。使用uv venv,创建虚拟环境所需的时间并不比mkdir明显更长。它们可能稍微大一些,但是它们的大小真的重要吗?通过避免使用这种将Python项目及其所有依赖项隔离到一个易于处理的目录中的标准方法,你并没有使任何事情变得更简单。

我想知道对虚拟环境的抵制有多少是没有技术原因的,而是源于 Homebrew在每次Python更新时破坏它们[14] 以及Debian通过他们烦人的Python安装解绑使它们难以使用。

我希望_uv_的便利性和速度将改变人们对它们的公众看法。

尾声

我在文章开头描述的捷径依赖于对site-packages位置和可移植性的假设。它们引入了一些复杂性,比如使用pip install --user(这在Docker中在语义上很奇怪,而且从未打算用于这样的事情),并且可能需要不熟悉的工具和范式,与你在开发中使用的不同。

但是我并不是试图说服你做任何事情。 我确实意识到我的一些理由属于无形的~氛围~方面。如果你在这些捷径的参数范围内,并且对权衡感到满意,你可以自由地做你想做的事。但我希望我已经向你展示了在Docker中使用虚拟环境确实是有意义的 - 至于它是否对你有意义,由你自己决定。我只是厌倦了每次都要为自己辩护,所以我在这里一劳永逸地阐述我的观点。

如果在所有这些之后,你仍然对我关于Python和Docker的看法感兴趣,我建议从** 使用uv构建生产就绪的Docker容器[0] **开始,这是一份关于我如何尽可能快地为Python应用程序构建Docker容器的活文档。


附:我简直不敢相信我发布这篇文章几乎正好是我那篇著名的 virtualenv Lives![4] 的十周年纪念日!很多事情都变得更好了,但虚拟环境仍在继续发挥作用。

参考链接

1. 内容: https://hynek.me/articles/docker-uv/
2. 虚拟环境: https://docs.python.org/3/library/venv.html
3. python:.*: https://hub.docker.com/_/python
4. PYTHONUSERBASE: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONUSERBASE
5. 与十年前不同: https://hynek.me/articles/virtualenv-lives/
6. Black: https://black.readthedocs.io/
7. 在本地: https://docs.astral.sh/uv/concepts/projects/
8. 发行版包: https://hynek.me/articles/python-app-deployment-with-native-packages/
9. 以隔离模式运行Python: https://docs.python.org/3/using/cmdline.html#cmdoption-I
10. 乞求赞助: https://hynek.me/say-thanks/
11. PDM: https://pdm-project.org/
12. PEP 582 /: https://peps.python.org/pep-0582/
13. uv: https://docs.astral.sh/uv/
14. Homebrew在每次Python更新时破坏它们: https://justinmayer.com/posts/homebrew-python-is-not-for-you/

幻想发生器
图解技术本质
 最新文章