现代化环境管理

聊聊一种可能是环境管理的现代化方案
在讨论环境管理之前,先看看环境搭建这个从古至今的问题。
这里讨论的环境,主要是指 sdk 的环境、多版本 sdk 动态选择的环境,也可以是指一套容器化的环境。
环境搭建的传统方式
在以前,搭建环境的一般步骤就是手动从官方网站下载具体的 sdk ,然后安装 sdk 、配置环境变量,最后验证环境的可用性。这样看来,搭建环境似乎不是一件很麻烦的事情,每个步骤都很清晰,搭建完环境后就可以专心投入新项目的开发。
后来,有了更多的新项目,并且这些项目对 sdk 的要求各不相同,于是按照这些项目的要求手动搭建环境。起初还好,每套 sdk 互不干扰,基本一套环境搭建完,后续可以一直使用。
不过,随着时间发展,sdk 也在不断迭代升级。出于对新特性的好奇,你再次从官方网站上手动重复了一遍环境搭建工作。有一部分 sdk 似乎变得“智能”了一些,环境变量配置的工作被内置在特定格式 sdk 文件中,但是升级 sdk 还是需要手动从官方网站上下载。
另一方面,云服务器的流行,使得你开始接触不同的操作系统,你可能会需要在不同的新机器中反复搭建环境。当你在新的操作系统上搭建环境时,可能会发现过去的经验并不能很好的工作。
脚本化搭建与包管理器搭建
在学习了终端 cli 安装之后,你可能意识到之前环境搭建的很多过程是可以自动化的,并且注意到一些新兴的 sdk 并非是以安装包的形式直接提供,而是给出了一串奇怪的命令。在终端执行这串命令之后,环境便完成了搭建。
你尝试理解了这串命令的含义,知道它包含了一个脚本,不过是通过网络的形式下载执行的。为了了解具体的脚本是如何做到自动化搭建环境的,你访问了这个脚本实际的内容,发现它是以代码的形式记录了一个环境搭建的行为。
因此,通过编写脚本记录环境搭建的步骤,你实现了自动化环境搭建,但是脚本的编写与维护工作可能是一件更耗费精力的事情:你可能希望这个脚本能实现安装、更新、卸载以及更加高级的功能。
脚本并非完全包含了环境搭建的所有可能性。一个脚本可能只在特定的操作系统上工作,针对不同的系统你可能还需要编写不同格式的脚本,且考虑环境配置的幂等性等情况,一部分配置工作还是设计为用户手动处理。
脚本虽然解决了一部分手动操作的问题,但是引入了新的问题。这些问题很大程度上受脚本编写质量的影响:如果一个脚本编写的非常出色,那么几乎不需要用户操劳。如果一个脚本编写的质量不好,甚至可能造成损失的,那么用户可能得自行为执行这个脚本的过失负责。因此,除非用户审阅并正确理解脚本的行为,不然无法估计执行脚本所带来的变数。(然而这和仔细阅读用户须知等协议是类似的,大部分用户并没有这么多的耐心)
你希望有一个相对规范统一的平台来处理这些事情,于是你接触了专属于操作系统的包管理器。包管理器相对于脚本提供了更好的保障和统一性,虽然这种保障并不是绝对的,但是这比纯粹的脚本管理带来了更多的高级功能和体验。尽管包管理器的运作需要特定的权限,但是目前你拥有最高权限。你使用包管理器在几条命令之后就完成了环境搭建,环境搭建基本上由包管理器统一承担。
但正因为包管理器是系统专属的,它的运作只适用于它所对应的操作系统,无法在多台系统各异的机器上重复相同的操作。此外,包管理器由于总是将环境置于一个全局的位置,导致系统可能会变得不太稳定。你或许需要一个具有隔离性的环境管理方案。
容器化搭建与跨平台 sdk 管理器搭建
云原生时代,容器化部署开始流行,环境的搭建可以转移到容器中实现。基于容器仓库可以快速获取定制好的各种环境,并且定制自己的环境使用。
容器化主要有应用容器和系统容器两个方向。
应用容器主要是以单个应用进程为中心。89luca89/distrobox 是一个容器包装层,主要是提供不同的容器化 linux 版本,依赖 docker 或 podman 运作。本质上是一系列脚本,自动化处理复杂的容器集成逻辑,实现高度系统集成的容器环境。此外,还有类似的 containers/toolbox。
系统容器旨在模拟完整的操作系统环境,能同时运行多个进程与服务。lxc 是比较典型的系统级容器虚拟化工具。lxc 本身比较底层,基于 lxc 开发的高级管理工具有 lxd 和 incus(lxd 的社区分支)。
如果使用过基于 systemd 的操作系统,可能会知道 systemd-nspawn 这个工具,它是由 systemd 生态所提供的系统级容器。
容器化引入新的学习成本,定制符合个人需求的环境需要熟练掌握容器化技术的使用方法,似乎对于个人环境搭建来说有些沉重了。
在另外一条道路上,一些新兴的跨平台 sdk 管理器出现,它们不同于传统的包管理器,不绑定单一的系统。这些 sdk 管理器基本涵盖了常见的 sdk 包,能够爬取各种 sdk 的版本号,具有下载、安装、卸载、特定版本控制等一系列功能,可以快速地搭建环境,适应复杂多变的环境需求。
我在过去的文章中有提到过一些 sdk 管理器,但是它们局限于各自的生态。
以下几个工具包含了相对全面的 sdk 管理:
直到多版本 sdk 管理器出现之前开发环境都是基于固定的环境变量 PATH 来识别。
多版本环境管理问题
前面说到,出于对新版本 sdk 的需求,你可能已经在系统中积累了多个版本的 sdk 或者是只留下了最新版本的 sdk。sdk 的迭代升级并不总是一帆风顺的,有时候迁移到新的 sdk 可能会付出较大的代价,因为新的 sdk 无法再兼容旧 sdk 的产物。
同时,多次重复的 sdk 配置工作,使得环境变量变得杂乱不堪。你可能需要一种能管理多版本 sdk 且不会污染环境变量的方案,例如 vfox.
在多版本 sdk 管理器的时代,为了实现多版本切换,引入了 shims 机制。最初是通过 shims 脚本动态切换软链接的指向实现 sdk 版本的切换,不过 shims 中间层的存在,造成了一定的性能损失。于是,后续出现了读取目录下特定文件动态改变环境变量的方案 direnv、mise,通过维护动态的环境变量来实现高效的 sdk 版本切换。
声明式的环境管理
在大部分多版本 sdk 管理方案还是基于动态选择思想设计时,一些不走寻常路的工具采取了基于声明式配置的静态解析。
声明式配置的一个例子就是 nix。nix 的设计受到了 haskell 函数式编程范式的影响,使用 nix 需要一定的函数式经验。不过基于 nix 已经出现了相对容易使用的工具:jetify-com/devbox 和 flox/flox.
devbox 可以在 Nixhub.io | A Nix Packages Registry 中搜索包,并支持导出 devcontainers 供其他容器使用。
现状
传统的环境搭建方式,在简单场景下依然是最符合直觉的不二选择。但是环境的变化总是会趋向于更复杂的情况,越发庞大的环境,越发混乱的环境。于是开始使用容器管理环境,但是容器的初衷并不是提供开发用途的环境,更多的是提供长期的服务。
当然在容器发展过程中也出现了 Developing inside a Container 这种在容器内开发的形式。
codesandbox 就是一个基于容器化环境开发的在线平台,并且很多开发工具提供了对容器化开发的支持。
My Favorite Way To Handle Dev Environments | VS Code Devcontainers - YouTube 介绍了如何在 vscode 中使用 devcontainers 插件来实现通过容器管理开发环境。
但是环境并不总是单一地服务于开发、测试。很多程序也需要一个稳定运行的环境,而一些程序与容器化的相性不合,更多时候还是需要一个在本地可用的环境。于是又回到本地,但是这时候有了 sdk 管理器的选择。
而 sdk 管理器则从原本的单一 sdk 管理趋向于更加全面的 sdk 管理,并且通过与 direnv 等工具配合实现了更灵活的开发环境管理。sdk 管理器的出现,算是很好地解决了大部分问题。但是某天你需要在另一台新的机器上准备这些环境,你会发现你又需要重复做一篇历史工作。容器化一定程度上能解决这个问题,但是容器化并不适用所有的场景。你可能希望有一种方案,能像脚本一样记录、能像 sdk 管理器一样控制版本、能像容器一样迁移。于是声明式环境管理可能是一种较好的方案。
声明式环境管理使得环境可以被记录和控制,基于这些配置元信息,能便捷地将环境转移到其他地方。
那么最终的环境管理形态可能有两种:一种是基于声明式配置 + 容器化开发(devcontainers) + 容器化生产的组合,适合大规模的环境共享以及对环境有较高的一致性要求;另一种是基于 sdk 管理器,适合个人灵活使用和临时测试。
参考文章
使用 distrobox 和 nix 加速 UOS 开发 | 小竹’s blog
SDK 版本管理器之 vmr 和 vfox 对比 - V2EX
利用 direnv 管理项目环境变量,提高开发效率 | 小赵的技术手记
Devbox vs Docker: light & repeatable dev envs w/o container
- 标题: 现代化环境管理
- 作者: Entropy Tree
- 创建于 : 2025-04-17 13:48:22
- 更新于 : 2025-04-17 16:15:17
- 链接: https://www.entropy-tree.top/2025/04/17/modern-multi-env-manager/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。