用 Nix 构建 systemd Portable Services 镜像:pkgs.portableService 完整指南

用 Nix 构建 systemd Portable Services 镜像:pkgs.portableService 完整指南 用 Nix 构建 systemd Portable Services 镜像pkgs.portableService 完整指南【免费下载链接】nixpkgsNix Packages collection NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgspkgs.portableService是 Nixpkgs 提供的一个构建函数它把软件包及其全部依赖打包成一个只读、不可变的squashfs原始磁盘镜像.raw文件该镜像完全符合 systemd Portable Services 为主体结合 实现源码 与 NixOS 集成测试讲解全部输入参数、完整构建示例以及底层实现原理读完你可以独立用 Nix 构建、装载并调试自己的 Portable Service 镜像。什么是 Portable Services为什么用 Nix 构建Portable Services 是 systemd 2392018-06-22 发布起引入的一种服务分发机制。与容器不同Portable Service 镜像内的文件系统结构遵循规范约定如/usr/lib/systemd/system、/etc/systemd/system等目录被装载attach后其 unit 文件直接暴露给宿主 systemd由宿主直接管理进程无需独立的容器运行时。镜像以.raw后缀的磁盘镜像形式存在具备只读、不可变特性天然适合分发与审计。Nix 构建 Portable Service 的独特优势在于构建产物天然可复现。Nixpkgs 通过closureInfo机制收集目标包及其所有依赖的完整 store 闭包一次性写入镜像避免手工拼装依赖时常见的遗漏与冲突同时 Nix 的 store 路径哈希确保相同输入必然产出相同镜像。需要注意的是本文讨论的是 Nixpkgs 自带的pkgs.portableService构建函数官方手册将其归类于 镜像构建助手它专门产出面向 systemdportablectl的镜像区别于通用的磁盘镜像制作工具。构建产物形态portableService生成的镜像会出现在 Nix store 中文件扩展名为.raw这是 Portable Services 规范强制要求的后缀。镜像内部包含规范要求的文件系统骨架以及传给portableService的包和它们的全部依赖。从 实现源码 可以看出最终的 derivation 名为${pname}-img产出文件遵循$pname_$version.raw的命名模板该模板同样被 Portable Services 规范支持。例如构建 hello 服务的镜像后会在/nix/store/...-hello-img-2.12.1/hello_2.12.1.raw得到产物。输入参数详解portableService接受单个包含以下属性的参数集合必选参数pnameStringPortable Service 的名称镜像将按$pname_$version.raw模板命名。在源码中它还承担双重职责——用作生成的/etc/os-release中PORTABLE_ID的值并要求所有 unit 文件名必须以它为前缀见下文units与 命名约束。versionStringPortable Service 的版本参与镜像文件名模板$pname_$version.raw。unitsList of Attribute Setsystemd unit 文件 derivation 的列表。每个 derivation 必须且只能产出一个文件文件名必须以pname开头、以 unit 类型后缀结尾如.service、.socket、.timer等。这些 unit 会被复制到镜像内的/etc/systemd/system/目录——实现中通过lib.concatMapStringsSep \n (u: cp ${u} $out/etc/systemd/system/${u.name};) units逐条复制u.name正是 derivation 输出文件的名字这也是该命名约束的底层原因实现源码。可选参数descriptionString 或 Null默认null若指定其值会作为PORTABLE_PRETTY_NAME写入镜像内的/etc/os-release文件供任何人检查镜像时获取更多信息。homepageString 或 Null默认null若指定其值会作为HOME_URL写入镜像内的/etc/os-release文件。symlinksList of Attribute Set默认[]元素格式为{ object, symlink }。对列表中的每一项portableService会在镜像根文件系统中symlink指定的路径相对镜像根创建一个指向object的符号链接。object所依赖的所有包及其依赖会被自动复制进镜像。这适用于那些假设某些文件全局存在的应用例如/etc/ssl或/bin/bash。contentsList of Attribute Set默认[]附加 derivation 列表这些 derivation 会被原样放入镜像内的/nix/store目录。实现上它们与根文件系统骨架一起作为closureInfo的rootPaths参与闭包收集实现源码。squashfsToolsAttribute Set默认pkgs.squashfs-tools覆盖内部使用的mksquashfs(1)提供者。squash-compressionString默认xz -Xdict-size 100%作为压缩选项传给内部调用的mksquashfs(1)。squash-block-sizeString默认1M作为块大小选项传给内部调用的mksquashfs(1)。实现中这些压缩参数最终被拼接到mksquashfs命令的-b ${squash-block-size} -comp ${squash-compression}部分实现源码覆盖它们即可按需调整镜像大小与压缩比。镜像内部结构从源码看生成过程portableService的实现分两个阶段完整逻辑见 pkgs/build-support/portable-service/default.nix第一阶段构建根文件系统骨架rootFsScaffold。这是一个独立的stdenv.mkDerivation负责创建/etc/systemd/system、/proc、/sys、/dev、/run、/tmp、/var/tmp、/var/lib、/var/cache、/var/log等规范要求的目录创建空的/etc/resolv.conf与/etc/machine-id装载时由宿主版本覆盖挂载将生成的/etc/os-release复制进骨架——os-release由lib.generators.toKeyValue生成包含PORTABLE_IDpname、PORTABLE_PRETTY_NAMEdescription、HOME_URLhomepage、IDnixos、PRETTY_NAMENixOS、BUILD_IDrolling等键值实现源码null 值通过filterNull自动剔除将各 unit 复制到/etc/systemd/system/根据symlinks逐条创建符号链接。第二阶段拼装 squashfs 镜像。最终 derivation 以pname ${pname}-img命名使用pkgs.closureInfo收集rootFsScaffold与contents的完整 store 闭包将闭包内每个 store 路径以cp -a方式放入nix/store目录随后执行SOURCE_DATE_EPOCH0 mksquashfs nix ${rootFsScaffold}/* $out/${pname}_${version}.raw \ -quiet -noappend \ -exit-on-error \ -keep-as-directory \ -all-root -root-mode 755 \ -b ${squash-block-size} -comp ${squash-compression}值得注意的工程细节SOURCE_DATE_EPOCH0被显式设置以保证镜像构建可复现Nixpkgs issue #390696 相关-keep-as-directory将 store 目录与根骨架平级压入镜像根-all-root -root-mode 755统一镜像内文件属主。示例一构建并装载 hello 服务的 Portable Service 镜像以下示例构建一个包含hello包及其 service unit 的 Portable Service 镜像{ lib, writeText, portableService, hello, }: let hello-service writeText hello.service [Unit] DescriptionHello world service [Service] Typeoneshot ExecStart${lib.getExe hello} ; in portableService { pname hello; inherit (hello) version; units [ hello-service ]; }注意writeText hello.service的输出文件名恰好以pname hello开头、以.service结尾满足 命名约束如果不满足构建会直接抛错 Unit names must be prefixed with the service name。构建产物并通过portablectl(1)装载、启动与卸载的完整流程如下$ nix-build (some output removed for clarity) /nix/store/8c20z1vh7z8w8dwagl8w87b45dn5k6iq-hello-img-2.12.1 $ portablectl attach /nix/store/8c20z1vh7z8w8dwagl8w87b45dn5k6iq-hello-img-2.12.1/hello_2.12.1.raw Created directory /etc/systemd/system.attached. Created directory /etc/systemd/system.attached/hello.service.d. Written /etc/systemd/system.attached/hello.service.d/20-portable.conf. Created symlink /etc/systemd/system.attached/hello.service.d/10-profile.conf → /usr/lib/systemd/portable/profile/default/service.conf. Copied /etc/systemd/system.attached/hello.service. Created symlink /etc/portables/hello_2.12.1.raw → /nix/store/8c20z1vh7z8w8dwagl8w87b45dn5k6iq-hello-img-2.12.1/hello_2.12.1.raw. $ systemctl start hello $ journalctl -u hello Feb 28 22:39:16 hostname systemd[1]: Starting Hello world service... Feb 28 22:39:16 hostname hello[102887]: Hello, world! Feb 28 22:39:16 hostname systemd[1]: hello.service: Deactivated successfully. Feb 28 22:39:16 hostname systemd[1]: Finished Hello world service. $ portablectl detach hello_2.12.1 Removed /etc/systemd/system.attached/hello.service. Removed /etc/systemd/system.attached/hello.service.d/10-profile.conf. Removed /etc/systemd/system.attached/hello.service.d/20-portable.conf. Removed /etc/systemd/system.attached/hello.service.d. Removed /etc/portables/hello_2.12.1.raw. Removed /etc/systemd/system.attached.从portablectl的输出可以看到装载的关键动作unit 被复制到/etc/systemd/system.attached/、镜像被软链到/etc/portables/、并套用了 default 的 portable profile 配置。这套流程与 NixOS 集成测试 中验证的行为一致——测试使用portablectl attach --now --runtime装载demo_1.0.raw等待demo.service进入运行态后再detach并断言其已停止。示例二通过 symlinks 使文件全局可用有些服务假设某些文件或目录全局存在例如默认信任全部 SSL 证书的服务会期望证书位于特定位置。此时必须在调用portableService时指定symlinks属性。以下示例在示例一的基础上将/etc/ssl在镜像内全局暴露仅为演示目的——hello本身并不使用/etc/ssl{ lib, writeText, portableService, hello, cacert, }: let hello-service writeText hello.service [Unit] DescriptionHello world service [Service] Typeoneshot ExecStart${lib.getExe hello} ; in portableService { pname hello; inherit (hello) version; units [ hello-service ]; symlinks [ { object ${cacert}/etc/ssl; symlink /etc/ssl; } ]; }实现层面每个{ object, symlink }元素对应执行mkdir -p $(dirname $out/${symlink}); ln -s ${object} $out/${symlink};实现源码并且由于object会被纳入最终镜像的闭包收集其依赖会自动随镜像分发无需手动逐一列举。多 unit 组合socket 激活场景units天然支持多个 unit 的组合。在 NixOS 集成测试 中demo服务就同时携带了demo.service与demo.socket两个 unitservice 通过Requiresdemo.socket、Afterdemo.socket声明对 socket 的依赖socket 则监听/run/demo.sock。这种多 unit 结构是 Portable Service 支持 socket 激活、timer 定时触发等 systemd 经典机制的典型用法所有 unit 只需满足以pname为前缀 正确类型后缀的命名规则即可一同打进镜像。使用前提与限制宿主机 systemd 版本Portable Services 需要 systemd 239 及以上2018-06-22 发布请在目标系统上用systemctl --version确认。宿主机非 NixOS 亦可这正是该方案的典型价值——镜像自包含所有依赖可在众多现代 Linux 发行版上通过portablectl直接使用。unit 命名强约束unit 文件名必须以pname为前缀否则构建阶段assert会直接失败实现源码。镜像只读不可变运行时状态应写入/var、/run、/tmp等由宿主挂载覆盖的目录/etc/resolv.conf与/etc/machine-id在镜像内预留为空文件以接受宿主版本。可复现性实现通过SOURCE_DATE_EPOCH0固定时间戳保证镜像可复现构建。参考资源官方手册原文doc/build-helpers/images/portableservice.section.md实现源码pkgs/build-support/portable-service/default.nix顶层注册pkgs.portableService callPackage ../build-support/portable-service { }pkgs/top-level/all-packages.nixNixOS 集成测试含 socket 激活场景与 attach/detach 验证nixos/tests/systemd-portabled.nix【免费下载链接】nixpkgsNix Packages collection NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考