Nixpkgs 中用 Packer 构建跨平台镜像:packer.withPlugins 与插件体系的完整解析
【免费下载链接】nixpkgsNix Packages collection & NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs
本文以 nixpkgs 官方手册中的 Packer 章节(doc/packages/packer.section.md)为核心,结合仓库中 packer 包定义、withPlugins 实现 和 插件构建器 的源码,系统讲解如何在 Nixpkgs 环境下免运行时下载地使用 Packer 插件:读完你将掌握packer.withPlugins的用法与原理、如何枚举当前版本可用的全部插件、插件二进制命名与校验和机制,以及如何用mkPackerPlugin自行打包一个新插件。
1. Packer 与 nixpkgs 中的打包形态
Packer 是一个从单一源配置为多个平台创建一致机器镜像的工具。在 nixpkgs 中,它由 pkgs/by-name/pa/packer/package.nix 通过buildGoModule构建,当前版本为1.15.4,采用 BSL 1.1 许可(见该文件meta.license = lib.licenses.bsl11),并在postInstall阶段安装 zsh 补全(installShellCompletion --zsh contrib/zsh-completion/_packer)。
Packer 的核心功能通过插件扩展:builder(如 docker、qemu)、provisioner(如 shell、ansible)、post-processor 等都独立发布为插件。Nixpkgs 的差异化之处在于:不让 Packer 在运行时自行下载插件,而是把所需插件一并静态打包进一个包装后的 Packer 可执行文件,保证构建可复现、离线可用。这一能力的入口就是packer.withPlugins。
2. 用 packer.withPlugins 组装“带插件的 Packer”
packer.withPlugins接受一个函数:它接收可用插件集作为参数,返回要包含的插件列表:
packer.withPlugins (ps: [ ps.docker ])其产物是一个经过包装的packer可执行文件,运行时自动带上PACKER_PLUGIN_PATH环境变量,因此选中的插件无需再执行packer plugins install即可直接使用。
官方手册给出的典型场景是搭建一个包含 Packer 与 Docker 插件的开发 shell:
{ pkgs ? import <nixpkgs> { }, }: pkgs.mkShell { packages = [ (pkgs.packer.withPlugins (ps: [ ps.docker ])) ]; }也可以一次选择多个插件,例如同时带上 Docker 与 QEMU 的 builder:
packer.withPlugins (ps: [ ps.docker ps.qemu ])2.1 源码实现:linkFarm 汇总插件 + makeBinaryWrapper 注入环境变量
上述行为在 pkgs/by-name/pa/packer/with-plugins.nix 中实现,整份文件仅 54 行,核心逻辑值得逐行看清:
- 插件选择(L26):
plugins = selector packerPlugins;—— 你在withPlugins (ps: ...)里写的函数就是对packerPlugins(全部已打包插件)的调用,返回一个插件 derivation 列表。 - 插件“农场”(L27-L38):用
linkFarm "packer-plugins"为每个插件创建两组符号链接:- 以插件的
pluginPath为名,链接${p}/bin/${p.meta.mainProgram}(真正的插件二进制); - 以
${pluginPath}_SHA256SUM为名,链接同名的校验和文件。 这正是 Packer 发现本地插件所需的目录形态:二进制 + 配套 SHA256 校验文件。
- 以插件的
- 包装器(L49-L52):
buildCommand中用makeWrapper把原生packer复制到$out/bin/packer,并--set PACKER_PLUGIN_PATH "${pluginFarm}"。至此,运行这个包装版packer时,Packer 就能在PACKER_PLUGIN_PATH下找到全部选定插件,完全跳过运行时下载。
withPlugins本身在 package.nix 的 passthru 中定义:它先callPackage ./plugins.nix得到完整的插件作用域(scope),再把它连同packer = finalAttrs.finalPackage一起传给with-plugins.nix,最后以{ selector = f; }注入你的选择函数——这就是为什么参数集ps只包含 nixpkgs 中“已打包”的插件。
2.2 测试验证:withPlugins 的端到端测试
package.nix 的passthru.tests.withPlugins提供了权威验证路径:它先用finalAttrs.passthru.withPlugins (ps: [ ps.docker ])构造出带 docker 插件的 Packer,在runCommand中执行packer plugins installed,再断言输出中包含 docker 插件的pluginPath;若缺失则以非零状态退出。这印证了第 2.1 节的结论:包装后的 Packer 确实能让packer plugins installed直接列出所选插件。
3. 列出当前版本可用的全部插件
npxkgs 手册给出的两条命令在此完整保留(启用 flakes 与否各一条):
$ nix eval nixpkgs#packer.plugins --apply builtins.attrNames [ "docker" "qemu" ]不使用 flakes 时:
$ nix-env -f '<nixpkgs>' -qaP -A packer.plugins packer.plugins.docker packer-plugin-docker-1.1.2 packer.plugins.qemu packer-plugin-qemu-1.1.4两条命令的输出内容取决于你使用的 nixpkgs 版本——手册示例出自收录插件较少的早期版本;在当前仓库中,pkgs/by-name/pa/packer/plugins/ 目录下已收录 33 个插件,涵盖主流云厂商与本地虚拟化方案:
| 类别 | 插件示例(属性名即插件目录去掉packer-plugin-前缀) |
|---|---|
| 公有云 | amazon、azure、googlecompute、alicloud、jdcloud、tencentcloud、yandex、oracle、ncloud、oneandone、profitbricks、openstack、triton |
| 本地虚拟化 | qemu、docker、lxc、lxd、hyperv、virtualbox、vagrant、proxmox、kubevirt、cloudstack、hashicups(测试用) |
| 配置/合规工具 | ansible、chef、puppet、salt、converge、inspec |
| SDK/脚手架 | sdk、scaffolding |
nix-env -qaP输出的第二列同时给出了带版本的完整包名(如packer-plugin-docker-1.1.3、packer-plugin-qemu-1.1.5),可用于核对具体版本。
3.1 属性名从哪里来:插件作用域的构建
pkgs/by-name/pa/packer/plugins.nix 解释了上述属性集的来源:
- 用
lib.packagesFromDirectoryRecursive扫描./plugins/目录,自动收集所有插件包; - 用
lib.mapAttrs'将每个包名去掉packer-plugin-前缀后作为作用域属性名(lib.removePrefix "packer-plugin-" name); - 同时向作用域注入
mkPackerPlugin,供各插件包复用。
而 package.nix 中plugins = lib.filterAttrs (_: lib.isDerivation) pluginScope会过滤掉非 derivation 项(如mkPackerPlugin函数本身),因此packer.plugins里“看到的”全是可安装包。关键结论:属性名(如docker、qemu)就是传给packer.withPlugins的键,与手册 Notes 的说明一致。
4. 插件是如何被构建的:mkPackerPlugin 深入解析
理解 pkgs/by-name/pa/packer/extra/mk-packer-plugin.nix(99 行)是理解整个插件体系的最佳入口,它封装了 Packer 插件发布结构的硬性约定:
(1)Packer 对插件目录结构的期望。文件 L39-L49 的注释说明:Packer 假设插件托管在 GitHub 上并遵循特定发布结构,由此产生两条要求——
- 二进制必须位于
$PACKER_PLUGIN_PATH/github.com/$OWNER/$TYPE/,其中$TYPE是仓库名去掉packer-plugin-前缀后的名字; - 在 Packer 模板中声明插件时,
source属性必须写成同样的github.com/$OWNER/$TYPE形式。
with-plugins.nix中 linkFarm 的命名(name = p.pluginPath)正是为了满足第一条要求,使离线插件与在线下载的插件在目录布局上完全等价。
(2)二进制命名规则(L53-L56):
suffix = platformSuffix."${stdenv.hostPlatform.system}" ...; binName = "${finalAttrs.src.repo}_v${finalAttrs.version}_${apiVersion}_${suffix}";例如 docker 插件在 x86_64-linux 上产出packer-plugin-docker_v1.1.3_x5.0_linux_amd64(apiVersion默认x5.0)。
(3)校验和文件生成(L86-L90):postFixup阶段在二进制被 fixup 定型之后,将$out/bin/${repo}重命名为带版本后缀的binName,并计算sha256sum写入同名的_SHA256SUM文件。这就是 Packer 校验本地插件完整性所依赖的文件。
(4)版本注入(L68-L80):通过ldflags的-X将${githubBase}/${owner}/${repo}/version包中的Version变量写入插件版本,-X ...VersionPrerelease=置空预发布标识,保证packer plugins installed显示干净的版本号。
(5)平台限制(L1-L6, L53-L55):platformSuffix只映射了三个系统:
| Nix 系统 | Packer 发布后缀 |
|---|---|
x86_64-linux | linux_amd64 |
aarch64-linux | linux_arm64 |
aarch64-darwin | darwin_arm64 |
在其他系统上调用会直接throw "Unsupported system: ..."。也就是说,当前插件打包链路仅支持这三个系统,这是使用该体系时必须知道的前提。
(6)fetcher 限制:L50-L52 用lib.assertMsg强制src必须携带repo、owner、githubBase字段——即目前只支持fetchFromGitHub。这与手册 Notes 一节(“mkPackerPlugincurrently only supportsfetchFromGitHubas the fetcher”)完全对应。若你的插件发布在 GitLab 或其他平台,需要自行处理命名与校验和逻辑,不能直接套用该构建器。
4.1 一个具体插件包长什么样
以 packer-plugin-docker 的 package.nix 为例,一个插件定义非常短:mkPackerPlugin提供pname、version、src(fetchFromGitHub拉取 hashicorp/packer-plugin-docker 的 tag 并附 SRI 哈希)、vendorHash(buildGoModule的 vendor 依赖哈希)和meta。packer-plugin-qemu 的 package.nix 结构相同,仅版本与哈希不同(当前仓库中 docker 1.1.3、qemu 1.1.5,许可证均为 MPL-2.0)。
由此可以推断:为 nixpkgs 新增一个插件,本质上就是在plugins/下新建一个packer-plugin-<name>/package.nix、按上述模式填写fetchFromGitHub的owner/repo/tag/hash与vendorHash;plugins.nix的packagesFromDirectoryRecursive会自动收录它,无需改动任何注册表。
5. 实战建议与已知限制小结
结合文档与源码,使用 nixpkgs 的 Packer 插件体系时注意以下几点:
- 优先使用包装器而非
packer plugins install。withPlugins产物的插件路径在构建期即确定(linkFarm 内容静态可复现),这是 Nix 可复现性要求下的推荐做法;packer plugins install会在运行时发起网络下载,与这一原则相悖。 - 插件名以作用域属性名为准。执行
nix eval nixpkgs#packer.plugins --apply builtins.attrNames查询你手上 nixpkgs 版本实际可用的插件,不要凭手册旧示例臆测。 - 目标平台受支持。插件构建器目前仅覆盖
x86_64-linux、aarch64-linux、aarch64-darwin(见 mk-packer-plugin.nix 的 platformSuffix)。 - fetcher 受限于 GitHub。所有已收录插件均通过
fetchFromGitHub获取源码并用 Go 模块方式构建,插件的source声明在 Packer 模板中需写成github.com/$OWNER/$TYPE形式。 - 版本固定、可审计。每个插件都是独立 derivation,版本与哈希固化在各自的 package.nix 中;
packer.plugins.docker与packer.withPlugins的端到端行为由 tests.withPlugins 守护。
6. 延伸阅读
- 手册原始章节:doc/packages/packer.section.md
- Packer 主包定义(含 passthru 与测试):pkgs/by-name/pa/packer/package.nix
- withPlugins 包装实现:pkgs/by-name/pa/packer/with-plugins.nix
- 插件作用域与目录自动收录:pkgs/by-name/pa/packer/plugins.nix
- 插件构建器(命名/校验和/平台规则):pkgs/by-name/pa/packer/extra/mk-packer-plugin.nix
- 已收录插件全集(33 个):pkgs/by-name/pa/packer/plugins/
【免费下载链接】nixpkgsNix Packages collection & NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考