PAYATHON 2026

如何管理开发和生产环境的软件依赖,并自动安装与更新?

技术老齐

这个 Ubuntu、Go、Python 混合项目,可以先用 Ansible 管理机器上的软件安装和配置,继续用 Go Modules、pip 或 Conda 管理项目依赖,用 Docker Compose 启动数据库、MLflow 等服务。

把安装清单、版本要求和执行入口提交到 Git,供本地开发和容器开发共用。更新时,先修改声明并验证,再同步到指定版本,避免每次运行都直接安装上游最新版。

如果团队经常切换项目,而各项目需要不同版本的 Go、Python 或命令行工具,可以再考虑用 Nix 管理项目工具环境。Docker daemon 等宿主机服务仍需单独配置。

先区分几类依赖

依赖类型项目中的例子建议的管理方式
语言库Go modules、Python packagesGo Modules、pip 或 Conda
编译器和命令行工具Go、Python、jq、psql、Helm、minikubeAnsible 安装;需要项目隔离时考虑 Nix
宿主机服务Docker EngineAnsible 配置软件源、软件包和服务
开发配套服务数据库、MLflow serverDocker Compose,固定镜像版本
生产应用Go 程序、Python 服务CI 构建产物或镜像,再部署经过验证的版本

Ansible 可以提供统一的执行入口,具体安装仍由 APT、发行包、pip、Conda 等工具完成。已有的 Go Modules 和 Python 依赖管理流程可以保留。

用 Ansible 声明 Ubuntu 环境

Ansible 适合安装系统包、下载指定版本的工具、配置路径和管理宿主机服务。使用具备幂等性的模块后,重复执行时,已经满足要求的任务通常不会再次修改系统。

安装任务可以先分为两组:

  • common:本地机器和开发容器都需要的软件。
  • host:Docker Engine 等宿主机组件。

简化的 bootstrap.yml 可以这样写:

---
- name: Prepare development environment
  hosts: all
  become: true

  tasks:
    - name: Install common Ubuntu packages
      ansible.builtin.apt:
        name:
          - ca-certificates
          - python3
          - python3-venv
          - jq
          - postgresql-client
        state: present
        update_cache: true
        cache_valid_time: 3600
      tags:
        - common

    - name: Install Docker packages on the host
      ansible.builtin.apt:
        name:
          - docker-ce
          - docker-ce-cli
          - containerd.io
          - docker-buildx-plugin
          - docker-compose-plugin
        state: present
      tags:
        - host

运行前,需要准备好 Ansible 及其所需的 Python。执行 Docker 任务前,还要按 Docker 的 Ubuntu 安装文档配置官方 APT 软件源,并处理可能冲突的软件包。这个示例只包含基础包和任务划分,完整的软件源配置及其他工具的安装逻辑需要另行补充。

在本地 Ubuntu 上执行:

ansible-playbook -i localhost, -c local bootstrap.yml --ask-become-pass

在已经具备 Python 和 Ansible 的开发容器中,以 root 执行公共任务:

ansible-playbook -i localhost, -c local bootstrap.yml --tags common

本地和容器由此可以共用一份任务定义。安装逻辑增多后,再把 Go、Conda、Docker、Helm 等拆成独立的 Ansible role。任务选择机制见 Ansible tags 文档。

区分“已安装”和“版本一致”

上面的 state: present 保证软件已安装,但不锁定版本。update_cache: true 只更新 APT 索引,不会因此升级已安装的软件。

要固定 APT 软件版本,可以使用 软件包名=完整版本号,例如将 name 条目写成:

- "jq={{ jq_apt_version }}"

jq_apt_version 应由项目变量文件提供,必须填写目标 Ubuntu 软件源实际提供的版本。可以用 apt-cache policy jq 检查候选版本。参数行为见 Ansible apt 模块文档。

如果 Go、Helm、minikube 需要的版本不在发行版仓库中,可以下载官方发行包,并在项目配置中记录版本、目标架构和 SHA-256。例如,Go 下载任务可以写成:

- name: Download the selected Go archive
  ansible.builtin.get_url:
    url: "{{ go_archive_url }}"
    dest: "/tmp/go{{ go_version }}.tar.gz"
    checksum: "sha256:{{ go_archive_sha256 }}"
    mode: "0644"
  tags:
    - common

这些变量都要填写团队选定版本对应的真实值。get_url 支持下载校验,具体行为见其官方文档。

后续任务还需完成解压、路径配置和版本检查。建议将不同版本安装到不同目录,通过切换当前使用的路径来方便回退。下载地址和校验值也要区分 amd64、arm64 等架构。

固定版本后,还要保证安装包长期可获取。要求较高时,可以保存经过批准的发行包,或使用受控的软件包仓库。

保留 Go 和 Python 各自的依赖管理方式

Go 项目应提交 go.mod 和 go.sum,同时管理工具链版本。go.mod 中的 go 指令表示最低 Go 版本要求,toolchain 指令提供建议工具链;实际选择还受 GOTOOLCHAIN 等配置影响,不能直接将它们当作严格的版本锁。相关规则见 Go toolchain 文档。

Python 项目需要选定环境管理方式:

  • 使用 pip 时,在独立虚拟环境中安装,并固定直接依赖及传递依赖。
  • 使用 Conda 时,提交环境定义;要求严格复现时,还需保存针对目标平台的锁定结果。

如果 pip requirements 文件已经完整固定版本并包含哈希,可以执行:

python3 -m venv .venv
.venv/bin/python -m pip install --require-hashes -r requirements.txt

--require-hashes 要求文件满足相应规则,不能直接用于只有包名或版本范围的文件。详见 pip 可重复安装文档。

如果继续混用 Conda 和 pip,建议先安装 Conda 依赖,再安装必须通过 pip 获取的包。环境经过 pip 修改后,不宜再反复用 Conda 调整;从声明文件重建通常更容易排查问题。Conda 文档也说明了这种混用方式的限制。

同一个项目的 Python 解释器应明确由哪套工具管理。让 APT、Conda 和另一套工具同时负责,很容易造成终端、IDE 和脚本各自调用不同 Python 的情况。

为更新设置明确的触发方式

Ansible 只有在执行时才会检查和修改环境。在 Git 中增加 Helm 或修改 Go 版本,并不会自动更新所有开发者的电脑。

可以在仓库中自行实现 make bootstrap 和 make doctor,作为统一入口:

  • bootstrap 安装或同步项目声明的依赖。
  • doctor 检查实际版本、命令路径和必要服务,发现不一致时给出具体修复提示。

开发者首次加入项目,或拉取依赖配置变更后,都执行同一个同步入口。项目启动脚本也可以先做轻量检查,提前报告工具缺失或版本不符的问题。

需要无人值守同步时,可以用定时任务或受管理的配置系统触发。个人开发机上的 Docker 升级可能涉及服务重启,适合安排在明确的维护时间。

新增工具或升级版本时,建议按以下流程处理:

  1. 修改版本声明、校验值或锁定文件。
  2. 在干净的 Ubuntu 环境和开发容器中验证安装。
  3. 运行项目测试,通过后合并配置。
  4. 开发者执行同步;容器用户重建开发镜像。

CI 应从声明文件重建环境,并检查构建过程是否意外改写锁定文件。只在某位开发者已经配置好的电脑上运行成功,还无法证明安装流程完整。

本地与容器共用配置时,区分宿主机能力

VS Code Dev Containers支持将容器作为开发环境,可以作为团队成员的可选开发方式。具体的编辑和调试体验取决于 IDE 及其配置。

共用安装定义时,需要注意以下限制:

  • 容器安装 Docker CLI 和 Compose 插件后,仍需连接可用的 Docker daemon。
  • 挂载宿主机 Docker socket 会赋予容器很高的宿主机操作权限,需要明确这一选择的影响。参见 Docker daemon 安全说明。
  • 安装 minikube 二进制后,还需准备可用的驱动及相应权限,才能运行集群。minikube Docker driver依赖现有的 Docker 环境。

共用的软件安装任务适合放进开发镜像,Docker daemon、虚拟化和集群启动任务则应由宿主机配置负责,不能默认把宿主机服务管理任务全部搬进 Dockerfile。

什么时候值得考虑 Nix?

如果两个项目分别要求不同版本的 Go 或 Python,修改全局安装可能让它们相互影响。Nix 可以通过项目声明提供对应的工具环境,通过固定 Nixpkgs 修订来减少环境漂移。相关方法见 Nix 声明式开发环境和固定 Nixpkgs。

同一份 Nix 环境定义可供本地和装有 Nix 的容器使用,但需要额外的学习和维护投入。包含原生扩展的 Python 包,以及现有 Conda 环境,都需要验证兼容性。Ubuntu 上的 Nix 包管理器也不能替代 Docker daemon 等宿主机服务的配置。

当前可以先完成 Ansible 安装流程,等项目间出现实际的版本冲突后,再引入 Nix。

生产环境应部署经过验证的产物

开发机需要编译器、调试工具、minikube 和客户端程序,生产环境只需保留运行应用所必需的组件。

对于已容器化的服务,建议由 CI 使用固定的构建工具和依赖生成镜像。测试通过后,将同一个镜像逐步部署到测试和生产环境。镜像应使用明确版本;需要严格确定内容时,用 digest 固定镜像。Docker 构建建议说明了标签可变与 digest 固定的区别。

Go 可以在构建阶段编译,再将程序及必要的运行库放入运行镜像。Python 服务则在构建阶段安装锁定的运行依赖。生产宿主机的基础软件仍可用 Ansible 管理,但应采用独立的配置和发布流程。

固定版本后仍需定期处理安全更新:修改依赖声明,重建并测试,再部署新产物。应用回退时使用上一份已验证的产物;涉及数据库结构变化时,还需另行设计兼容和回退方案。

备注:内容仅供参考。