Nixpkgs 中用 Packer 构建跨平台镜像:packer.withPlugins 与插件体系的完整解析
2026/9/18 23:56:01 网站建设 项目流程

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 行,核心逻辑值得逐行看清:

  1. 插件选择(L26):plugins = selector packerPlugins;—— 你在withPlugins (ps: ...)里写的函数就是对packerPlugins(全部已打包插件)的调用,返回一个插件 derivation 列表。
  2. 插件“农场”(L27-L38):用linkFarm "packer-plugins"为每个插件创建两组符号链接:
    • 以插件的pluginPath为名,链接${p}/bin/${p.meta.mainProgram}(真正的插件二进制);
    • ${pluginPath}_SHA256SUM为名,链接同名的校验和文件。 这正是 Packer 发现本地插件所需的目录形态:二进制 + 配套 SHA256 校验文件。
  3. 包装器(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-前缀)
公有云amazonazuregooglecomputealicloudjdcloudtencentcloudyandexoraclencloudoneandoneprofitbricksopenstacktriton
本地虚拟化qemudockerlxclxdhypervvirtualboxvagrantproxmoxkubevirtcloudstackhashicups(测试用)
配置/合规工具ansiblechefpuppetsaltconvergeinspec
SDK/脚手架sdkscaffolding

nix-env -qaP输出的第二列同时给出了带版本的完整包名(如packer-plugin-docker-1.1.3packer-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里“看到的”全是可安装包。关键结论:属性名(如dockerqemu)就是传给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_amd64apiVersion默认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-linuxlinux_amd64
aarch64-linuxlinux_arm64
aarch64-darwindarwin_arm64

在其他系统上调用会直接throw "Unsupported system: ..."。也就是说,当前插件打包链路仅支持这三个系统,这是使用该体系时必须知道的前提。

(6)fetcher 限制:L50-L52 用lib.assertMsg强制src必须携带repoownergithubBase字段——即目前只支持fetchFromGitHub。这与手册 Notes 一节(“mkPackerPlugincurrently only supportsfetchFromGitHubas the fetcher”)完全对应。若你的插件发布在 GitLab 或其他平台,需要自行处理命名与校验和逻辑,不能直接套用该构建器。

4.1 一个具体插件包长什么样

以 packer-plugin-docker 的 package.nix 为例,一个插件定义非常短:mkPackerPlugin提供pnameversionsrcfetchFromGitHub拉取 hashicorp/packer-plugin-docker 的 tag 并附 SRI 哈希)、vendorHashbuildGoModule的 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、按上述模式填写fetchFromGitHubowner/repo/tag/hashvendorHashplugins.nixpackagesFromDirectoryRecursive会自动收录它,无需改动任何注册表。

5. 实战建议与已知限制小结

结合文档与源码,使用 nixpkgs 的 Packer 插件体系时注意以下几点:

  1. 优先使用包装器而非packer plugins installwithPlugins产物的插件路径在构建期即确定(linkFarm 内容静态可复现),这是 Nix 可复现性要求下的推荐做法;packer plugins install会在运行时发起网络下载,与这一原则相悖。
  2. 插件名以作用域属性名为准。执行nix eval nixpkgs#packer.plugins --apply builtins.attrNames查询你手上 nixpkgs 版本实际可用的插件,不要凭手册旧示例臆测。
  3. 目标平台受支持。插件构建器目前仅覆盖x86_64-linuxaarch64-linuxaarch64-darwin(见 mk-packer-plugin.nix 的 platformSuffix)。
  4. fetcher 受限于 GitHub。所有已收录插件均通过fetchFromGitHub获取源码并用 Go 模块方式构建,插件的source声明在 Packer 模板中需写成github.com/$OWNER/$TYPE形式。
  5. 版本固定、可审计。每个插件都是独立 derivation,版本与哈希固化在各自的 package.nix 中;packer.plugins.dockerpacker.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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询