Python Build/Install 流程简化¶
- 日期:
2017-09-06 13:00
- 标签:
python, build, source, repo
当前的 python wheel/venv 构建流程不易理解,安装流程也变得复杂。此蓝图旨在简化部署、简化理解,并将当前强制所有部署者使用的许多功能变为可选。
Launchpad 蓝图: https://blueprints.launchpad.net/openstack-ansible/+spec/python-build-install-simplification
问题描述¶
构建¶
用于 OpenStack-Ansible 部署的 Python 仓库用于准备 Python wheels,用于来自 git 或 pypi 的任何软件包的环境。使用 wheel 可以加快软件包安装速度,并省去在安装时安装编译软件包所需的发行版软件包的麻烦。
仓库准备过程还会为所有 OSA 角色准备 Python virtualenvs,前缀为 os_(预期为 OpenStack 服务),以便通过下载完整的 virtualenv 而不是为需要该服务的每个主机单独安装软件包来加快服务部署速度。
py_pkgs lookup 用于汇总构建过程使用到的信息。它是一个黑盒,其工作原理不清楚,它所做的决策以及读取和输出的信息没有在任何地方记录,只有在代码中才能找到。代码不易修改,否则会破坏流程,因此通常很少修改,维护不足,导致技术债务不断增加。repo-build 角色中的后续 jinja 模板处理起来很困难,不易维护。虽然可以将两者调整为使用不同的插件或过滤器,但它们仍然是一组复杂的黑盒。
指定 git 仓库和向构建过程提供参数的方式扩展性较差。每个 git 仓库至少需要设置两个扁平变量(git_repo 和 git_install_branch),还可以选择设置更多变量。这种设置变量的方式很容易覆盖单个设置,但需要使用模式匹配机制来发现所有设置(这就是我们使用 lookup 的原因)。这些设置也放在不同的地方,很难找到——defaults/repo_packages、role/defaults。大多数新手很难知道如何更改它们,许多老手也不清楚许多设置的含义。通常需要大量的代码阅读才能理解某些设置的含义,例如 venvwithindex 和 ignorerequirements。
用于获取构建时使用的 git 源代码的 git 克隆过程是异步完成的,以提高完成时间。但是,Ansible 中的单个异步任务无法重试,并且 git 克隆经常失败。这是 Ansible 的限制,我们可以通过实现自己的 action 模块来解决,但这会增加技术债务,因为我们需要不断更新模块代码以适应 Ansible 的后续版本。
在构建 wheels 时,pip 无法预先解析所有依赖项。它拥有的唯一功能是解析当前软件包的要求。然后它依次处理每个软件包要求。为此,需要下载软件包并解包以读取要求。这是一个顺序过程,因此在处理具有大量要求的软件包(OpenStack 项目通常如此)时会花费很长时间。
在 Kilo 中,OpenStack 要求管理流程没有测试所有 OpenStack 软件包的可安装性的作业,也没有生成 upper-constraints.txt 文件作为可以一起使用的软件包版本的清单。因此,我们需要自己处理所有将由角色安装的 python 软件包,并为构建 wheels 和确保安装集一致性而编译一组要求和约束。当 OpenStack 要求仓库开始发布 upper-constraints 文件时,我们立即采用它以帮助保持构建的一致性。但是,我们仍然生成自己的 requirements_absolute_requirements.txt 文件,该文件用于所有 pip install 任务,以确保一致性并确保我们从 git 构建的软件包被使用(而不是让角色中的安装从 git 源代码进行安装,而是从 repo 服务器中的 wheel 安装)。但是,这不再实用,因为存在不同服务和需求的要求,无法降低到一组公共要求——我们需要能够允许安装任何版本的软件包,并在需要时仅应用约束。
我们构建的一些 venvs 不符合 OpenStack 要求流程,因此有时无法使用 upper constraints 文件构建。还有一些人对能够进行混合系列部署而不是同构部署感兴趣。这将涉及准备包含来自不同系列且具有不同约束的软件包的 venv。当前仓库构建流程中的约束是全局的——我们只有在构建 venvs 时才能启用/禁用它们的能力。最好能够指定全局回退约束,但也要允许每个 venv 的约束。
OpenStack 和 Ansible 对 Python 2.7 的使用正在减少,需要将所有内容迁移到使用 Python 3.5 的需求已经出现。工具需要进行调整,以在适用时使用 Python 3.5 实现 venvs,但如果服务尚未支持在 Python 3.5 环境中运行,则可能仍然需要准备使用 Python 2.7 的 venvs。
在 Newton 中,我们引入了多架构构建以适应多种架构,然后又由于 wheel/venvs 对 C 库的引用因每个发行版可用的库不同而不同,因此又不得不拆分多发行版构建。目前这可行,但它使仓库构建过程更加复杂,花费更多时间。同步每个发行版和每个架构构建的工件的过程容易出错,并且让项目的新手感到困惑。
为了便于使用 repo-server 响应 pip index 查询,使用了多个目录和符号链接来准备适当的结构,以便将正确的响应返回给 pip。设置所有符号链接的过程非常耗时,并且在某些地方该过程可能会导致死链接,尤其是在为特定发布标签重建时。
存储¶
为环境构建 wheels、venvs 和其他工件后,它们会使用 rsync 和 lsyncd 的组合存储并同步到仓库容器之间。虽然此同步过程通常没问题,但它通常是混淆的根源,需要一个复杂的故障排除过程来弄清楚为什么软件包不存在。
安装¶
准备好的 wheels 和 virtualenvs 的使用随着时间的推移而发生了变化。随着 developer_mode 引入到角色中,仓库构建过程和角色安装过程之间存在大量代码和功能重复。
需要适应 venvs 中各种插件/驱动程序的可选包含,无论是通过使用额外的 Python 软件包,还是通过将系统软件包链接到 venv(当软件包是专有的或无法通过 git 或 pypi 获取时),这进一步增加了流程的复杂性。
在执行 pip 安装时,pip 始终按以下顺序查找软件包:本地缓存、本地文件夹、默认索引、额外索引。Pip 将始终检查所有位置,然后决定使用哪个位置进行安装。这意味着如果使用了多个索引,它将查询所有索引。如果其中任何一个不在环境本地,则可能会很慢。
提议的变更¶
更改仓库构建过程,默认情况下,仅为给定的 git 源代码构建 wheels,而不构建依赖项。仍然可以构建所有 wheels,但不会是默认行为。这将减少在 CI、开发环境或小型在线环境中花费的时间,在这些环境中没有必要构建/存储所有 wheels。完整的构建仅在离线部署和部署者专门选择确保构建所有内容的环境中才需要。
将 wheels 的当前存储结构替换为扁平目录。该目录将通过非常简单的 pypiserver 应用程序提供的 pypi API 提供服务。如果我们需要继续为每个发行版或每个架构提供 wheels,则可以实现由单个文件夹提供的发行版/架构索引。但是,这可能是不必要的。
使用 nginx 作为反向代理,它通过首先尝试本地 pypiserver、然后尝试 tarballs.openstack.org,最后尝试 pypi 来响应来自 pip 的请求。这将允许 nginx 缓存所有下载的软件包(加快后续请求),而无需 repo 服务器构建它们。
实施对角色进行更改,以允许在构建 venvs 时应用特定于服务的约束。这允许 CI 流程构建服务 venvs 并发布该服务的已测试版本列表。然后,对于生产构建,可以发布列表作为 venv 的约束,以确保生产构建使用相同的版本。这解决了我们今天面临的一个问题,即某些项目(例如:tempest、rally、gnocchi)必须不受约束地构建,因为它们不符合全局要求流程。
实施对每个角色进行更改,以处理 wheel 构建和 venv 构建,但以只有通过使用标签、设置特定标志或 include_role 和 tasks_from 才能执行构建的方式进行。然后可以在角色中列出特定的依赖项,并且角色可以用于工件准备。
从主机中删除所有 pip install 活动,而是专门使用发行版软件包来满足主机上的任何 python 要求。我们应该避免在主机上实施尽可能多的 python 软件包,并将所有精力集中在实施我们所需的一切(包括针对目标主机的 Ansible 要求)到 venvs 中。然后,所有 Ansible 任务都应专门使用适当的 venv 来执行任务,避免使用主机上的任何 python 库。这可以防止系统软件包冲突,并减少主机软件包安装要求。
实施一个 playbook,该 playbook 可选地用于准备与今天相同的环境的预构建 venvs。如果部署者希望在构建过程中准备 venvs,则应在构建过程中执行该 playbook,并且应在构建主机的指定“构建主机”上执行,该主机将使用临时容器和/或虚拟机在构建主机上执行必要的发行版和架构组合的构建。
删除现有的复杂 git 缓存/暂存过程,并使 repo 服务器用于 git 缓存对于需要它的服务(例如:nova-console 使用 novnc/spice 从 git)完全是可选的。
实施一个 playbook,该 playbook 可用于通过将所有构建的工件(由 CI 作业完成)下载到部署主机来准备离线安装,然后适当分发它们。
简化约束管理,通过实施以下顺序使用 –constraints
–constraint user-specified-constraints.txt –constraint openstack-ansible-pins.txt –constraint openstack-upper-constraints.txt
这将取代当前将各种约束合并到一个文件中的方法,这需要大量的 jinja 魔法,因为单个文件无法同时包含两个约束并成功解析为单个结果,就像我们在当前机制中需要的那样。
实施对角色进行更改,以确保构建过程和仅在构建时才需要的软件包(开发头文件等)仅在执行构建时使用。构建软件包和运行时软件包将更改为单独的列表,以便运行时环境仅安装它需要的软件包。
确保“可选”pip 软件包在构建阶段安装到 venv 中,而不是在安装阶段安装。
备选方案¶
构建过程可以保持不变,继续让部署者感到困惑且难以维护。
构建过程可以更改为仅构建和存储为在主机上 pip 安装的软件包的 wheels,并且仅构建和存储 venvs 以供分发。
Playbook/Role 影响¶
将添加 playbook 来满足构建过程和暂存过程。将调整角色以正确分离构建任务和要安装的发行版软件包(与使用预构建 wheels 时的要求不同)。
升级影响¶
将小心确保升级像今天一样进行。
安全影响¶
通过减少在构建了完整工件集时安装到主机和容器上的软件包数量,可以提高安全性。
性能影响¶
由于减少了使用预构建软件包进行部署所需的时间,因此可以提高部署性能。
最终用户影响¶
对于 OpenStack 云的消费者而言,没有最终用户影响,除了升级可能更快地执行,从而减少了维护插槽要求。
部署者影响¶
由于部署和升级将更快地执行,部署者可以在更短的维护插槽中执行它们。
部署者需要了解如何更好地利用 CI 流程来准备加速部署所需的工件。
开发人员影响¶
由于构建过程将集成到角色中,因此更容易理解它的工作原理和作用。
依赖项¶
此规范将与 https://blueprints.launchpad.net/openstack-ansible/+spec/deployment-stages 合作实施。
实现¶
负责人¶
- 主要负责人
jesse-pretorius (odyssey4me)
工作项¶
默认 AIO 中实现的每个角色都将按顺序进行处理,以根据此工作流程重新安排和优化。 具体工作项在此未详细说明,但将在 gerrit 的蓝图主题中体现,并在 launchpad 中可见。
测试¶
随着此流程的成熟,使用集成构建进行所有角色测试可能比拥有两个独立的测试实现更简单。 这将减少项目的技术债务。
文档影响¶
这项工作需要包括文档更新,描述使用完整制品构建实现部署的新方式,以及如何实施离线安装。