news 2026/9/25 1:34:02

rkt export 实战指南:将已退出 Pod 导出为 ACI 镜像

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
rkt export 实战指南:将已退出 Pod 导出为 ACI 镜像
  • 容器运行时
  • 云原生
  • 网络

【免费下载链接】rkt

[Project ended] rkt is a pod-native container engine for Linux. It is composable, secure, and built on standards.

项目地址:https://gitcode.com/gh_mirrors/rk/rkt
点击查看免费下载

rktexport子命令负责把一个已经退出(exited)的单应用 Pod的根文件系统连同其 Image Manifest 重新打包成一个标准的 App Container Image(.aci)文件,从而实现「容器运行态 → 可分发镜像」的逆向转化。本文以 Documentation/subcommands/export.md 为骨架,结合 rkt/export.go、pkg/pod/pods.go 等源码实现,系统讲解rkt export的调用方式、前置条件、应用选择规则、--overwrite行为以及底层 OverlayFS 挂载与 ACI 打包原理,帮助你理解并正确使用这一镜像导出能力。

命令概览与基本用法

rkt export的用法非常简洁,命令形式为:

rkt export UUID OUTPUT_ACI_FILE

其中UUID是已退出 Pod的 UUID(支持前缀匹配,见后文),OUTPUT_ACI_FILE是要写入的.aci文件路径。最简单的调用示例如下:

$ rkt export UUID .aci

即:指定 Pod 的 UUID 与输出 ACI 文件名,rkt 就会把该 Pod 中应用的根文件系统与清单打包为 ACI。从 rkt/export.go 的 cobra 命令定义可以看到其完整形式:

export [--app=APPNAME] UUID OUTPUT_ACI_FILE

--app用于在包含多个应用的 Pod 中指定要导出的应用,默认行为见「应用选择逻辑」一节。

命令的入口逻辑位于 rkt/export.go 的runExport函数:它首先校验参数个数(必须是两个),然后校验输出文件扩展名,解析 Pod UUID,检查 Pod 状态,选择应用,最后根据 Pod 是否使用 OverlayFS 走不同的文件收集路径并调用buildAci完成打包。

前置条件:只能导出已退出的 Pod

rkt export只接受已退出的 Pod。源码中,rkt 通过 Pod 在数据目录中的位置与锁状态判定其生命周期状态(详见 pkg/pod/pods.go 中定义的状态常量,如Embryo、Preparing、Prepared、Running、Exited、ExitedGarbage等):

state := p.State() if state != pkgPod.Exited && state != pkgPod.ExitedGarbage { stderr.Print("pod is not exited. Only exited pods can be exported") return 254 }

也就是说,只有状态为exited或exited garbage的 Pod 才能被导出;仍处于running、prepared或preparing等状态的 Pod 会被拒绝,并打印提示pod is not exited. Only exited pods can be exported。

在实际操作中,一个常见的「导出工作流」是:

$ rkt prepare coreos.com/etcd:v2.3.4 # 先准备 Pod $ rkt run-prepared <UUID> # 运行 Pod $ rkt list # 查看 Pod 状态与 UUID $ rkt export <UUID> my-app.aci # Pod 退出后导出为 ACI

UUID 支持前缀匹配

runExport通过pkgPod.PodFromUUIDString解析 UUID(见 rkt/export.go)。该函数内部会先把传入字符串转为小写,再对所有 Pod 目录做前缀匹配(见 pkg/pod/uuid.go):

  • 无匹配:报错no matches found for "...";
  • 多个匹配:报错ambiguous uuid, N matches;
  • 恰好一个匹配:返回完整的 UUID。

因此你可以像rkt export bc3c export.aci这样使用 UUID 前缀,只要前缀是唯一的即可。

应用选择逻辑:单应用自动选中,多应用必须指定--app

一个 Pod 中可以包含多个应用(App)。导出时必须明确「导出哪个应用」。rkt/export.go 中getApp函数实现了三段式选择逻辑:

  1. 若通过--app=APPNAME显式指定了应用名,则从 Pod Manifest 的Apps列表中按名字匹配;若该名字不存在,报错app <name> is not present in pod;
  2. 若未指定--app,且 Pod 中恰好只有一个应用,则直接导出该应用;
  3. 若未指定--app,而 Pod 中包含多个应用,则把所有应用名打印到stderr,并要求用户用rkt export --app=...明确指定,否则报错退出。

也就是说,单应用 Pod 可以省略--app,多应用 Pod 则必须显式指定。应用名来自 Pod Manifest 中runtimeApp的name字段(schema.RuntimeApp),导出时会据此定位该应用的 rootfs 与清单文件。

--overwrite:控制是否覆盖已存在的输出文件

rkt export唯一专属选项是--overwrite。原文档中的参数表如下:

FlagDefaultOptionsDescription
--overwritefalsetrueorfalseOverwrite the output ACI if it exists

该选项默认关闭(false)。源码层面,rkt/export.go 的buildAci在打开输出文件时根据该标志组合文件打开模式:

mode := os.O_CREATE | os.O_WRONLY if flagOverwriteACI { mode |= os.O_TRUNC } else { mode |= os.O_EXCL } aciFile, err := os.OpenFile(target, mode, 0644) if err != nil { if os.IsExist(err) { return errors.New("target file exists (try --overwrite)") } ... }
  • 不传--overwrite时使用O_EXCL,若目标文件已存在则直接报错target file exists (try --overwrite);
  • 传入--overwrite时改用O_TRUNC,目标文件会被截断并覆盖写入。

因此,重复导出到同一路径时要么先删除旧文件,要么加上--overwrite标志。

输出文件必须使用.aci扩展名

runExport会强制校验输出文件扩展名(rkt/export.go):

ext := filepath.Ext(outACI) if ext != schema.ACIExtension { stderr.Printf("extension must be %s (given %s)", schema.ACIExtension, outACI) return 254 }

schema.ACIExtension来自 appc spec,即.aci。如果输出文件名不是.aci结尾(例如export.tar),命令会以退出码 254 报错:extension must be .aci (given ...)。

底层导出流程:OverlayFS 与非 Overlay 两条路径

rkt export的打包核心是buildAci,但在进入打包之前,它必须先从已退出的 Pod 中收集到应用的「完整 rootfs 快照」。由于 rkt 支持用 OverlayFS 运行 Pod,导出逻辑依据p.UsesOverlay()(即 Pod 目录下是否存在overlay-prepared标记文件,见 pkg/pod/pods.go 与 common/common.go 的OverlayPreparedFilename)分两条路径处理。

路径一:OverlayFS Pod —— 重新挂载合并视图

当 Pod 使用 OverlayFS 时,应用的最终 rootfs 由底层镜像(lower)与应用运行期写入的上层(upper)叠加而成。为了导出完整内容,runExport会:

  1. 在数据目录下创建临时目录$dataDir/tmp/rkt-export-<UUID>,并在其中建立rootfs子目录(rkt/export.go);
  2. 调用mountOverlay把该应用的真实文件系统挂载到这个临时目录上(rkt/export.go)。

mountOverlay通过 imagestore 与 treestore 定位底层:从 Pod 的appsinfo/<app>/treeStoreID文件读取 treestore ID(GetAppTreeStoreID),以其 rootfs 作为 overlay 的lowerdir;upper 层位于 Pod 目录的overlay/<treeStoreID>/upper/<app>,work 层位于overlay/<treeStoreID>/work/<app>。随后调用 common/overlay/overlay.go 的Mount执行mount("overlay", dest, "overlay", 0, opts)系统调用:

func Mount(cfg *MountCfg) error { err := syscall.Mount("overlay", cfg.Dest, "overlay", 0, cfg.Opts()) ... }

其中挂载选项由MountCfg(Lower/Upper/Work/Dest/Lbl)构造为lowerdir=...,upperdir=...,workdir=...,目录名中的冒号与逗号会被转义以避免被内核当作分隔符(sanitize)。挂载完成后,临时挂载点便呈现出应用在退出时的完整文件系统视图。

在runExport中,临时挂载点与临时目录通过defer保证在函数返回前被卸载(syscall.Unmount)和删除(os.RemoveAll)。这一设计确保导出过程不污染 Pod 数据目录。

路径二:非 OverlayFS Pod —— 校验无残留挂载点

当 Pod 未使用 OverlayFS 时,应用的 rootfs 直接存在于磁盘上($poddir/opt/stage2/<app>/rootfs),理论上可以直接打包。但若 Pod 退出后仍残留未清理的挂载点,直接打包会得到不完整的文件系统。因此runExport会先用mountinfo.ParseMounts解析宿主挂载表,并按应用的 rootfs 路径前缀过滤(rkt/export.go):

appRootfs := common.AppRootfsPath(p.Path(), app.Name) + string(filepath.Separator) mnts, err := mountinfo.ParseMounts(0) ... mnts = mnts.Filter(mountinfo.HasPrefix(appRootfs)) if len(mnts) > 0 { stderr.Printf("pod has remaining mountpoints. Only pods using overlayfs or with no mountpoints can be exported") return 254 }

注意 rootfs 路径后追加了路径分隔符,目的是避免误匹配到appRootfs前缀相同的其他路径。如果检测到残留挂载点,则报错pod has remaining mountpoints. Only pods using overlayfs or with no mountpoints can be exported。换言之,要么使用 OverlayFS,要么 Pod 中不存在任何残留挂载点,二者满足其一即可导出。

ACI 打包:gzip + tar + appc ImageWriter

无论走哪条路径,最终都汇聚到buildAci(rkt/export.go),其打包流程如下:

  1. 以相应打开模式创建输出.aci文件(权限0644);
  2. 依次构造gzip.Writer与tar.Writer,即 ACI 实际是 gzip 压缩的 tar 归档;
  3. 从$poddir/appsinfo/<app>/manifest(即common.AppInfoPath+aci.ManifestFile)读取并解析该应用的 Image Manifest(schema.ImageManifest);
  4. 使用 appc 规范提供的aci.NewImageWriter(im, tr)创建镜像写入器;
  5. 通过filepath.Walk(root, aci.BuildWalker(root, iw, walkerCb))递归遍历 rootfs,将每个文件以 tar 头写入;
  6. 调用iw.Close()收尾,写入 ACI 布局所需的补充条目;
  7. 若中途出错,defer 中会关闭所有 writer 并删除目标文件,避免留下不完整的 ACI。

用户命名空间支持:导出时对 uid/gid 反偏移

如果 Pod 是以--private-users启动的(即使用了用户命名空间),Pod 目录下会存在private-users-prepared文件(见 common/common.go 的PrivateUsersPreparedFilename)。runExport会读取并反序列化其中的shift:count偏移信息(rkt/export.go),构造*user.UidRange:

privUserFile := filepath.Join(p.Path(), common.PrivateUsersPreparedFilename) privUserContent, err := ioutil.ReadFile(privUserFile) if err == nil { uidRange = user.NewBlankUidRange() if err := uidRange.Deserialize(privUserContent); err != nil { ... } }

UidRange的定义与序列化/反序列化实现位于 pkg/user/uid_range.go。该结构记录Shift(偏移量)与Count(范围大小),启动时由--private-users触发SetRandomUidRange(user.DefaultRangeCount)(默认范围0x10000,见 rkt/run.go 与 pkg/user/uid_range.go)。

导出时,由于 Pod 内文件的所有者 uid/gid 是「宿主 uid + 偏移量」后的值,直接打包会得到错误的属主。因此buildAci在写入每个 tar 头时通过uidRange.UnshiftRange(uid, gid)把 uid/gid 减去偏移量,还原成镜像内部的原始属主(rkt/export.go):

var walkerCb aci.TarHeaderWalkFunc = func(hdr *tar.Header) bool { if uidRange != nil { uid, gid, err := uidRange.UnshiftRange(uint32(hdr.Uid), uint32(hdr.Gid)) ... hdr.Uid, hdr.Gid = int(uid), int(gid) } return true }

这样导出的 ACI 在普通命名空间下重新运行时,文件属主是正确且一致的。

全局选项

与 rkt 的所有子命令一样,rkt export也接受一组全局选项(原文档通过[global-options]链接指向 Documentation/commands.md,此处摘录关键项):

FlagDefaultOptionsDescription
--debugfalsetrueorfalse向stderr输出更多调试信息
--dir/var/lib/rkt目录路径rkt 数据目录路径
--local-config/etc/rkt目录路径本地配置目录路径
--system-config/usr/lib/rkt目录路径系统配置目录路径
--user-config''目录路径用户配置目录路径
--insecure-optionsnonenone、http、image、tls、pubkey、capabilities、paths、seccomp、all-fetch、all-run、all逗号分隔的需关闭的安全特性列表
--trust-keys-from-httpsfalsetrueorfalse自动信任从 HTTPS(或配合pubkey时从 HTTP)获取的 GPG 公钥

其中--dir与导出关系最为密切:它决定了 Pod 数据目录的位置,进而决定runExport查找 Pod、创建临时目录以及访问 imagestore/treestore 的根路径。

错误处理与退出码

rkt export的错误路径统一返回退出码254(源码中以return 254表示失败),典型失败场景包括:

  • 参数个数不为 2(打印用法并退出);
  • 输出文件扩展名不是.aci;
  • UUID 无法解析、无匹配或存在歧义;
  • Pod 尚未退出(仍处于 running/prepared 等状态);
  • 指定的--app不存在,或多应用 Pod 未指定--app;
  • 非 OverlayFS Pod 存在残留挂载点;
  • 目标 ACI 已存在但未加--overwrite;
  • 打开、打包或卸载过程中出现文件系统错误。

此外,清理临时目录或卸载临时挂载点时若发生错误,exit会被置为 1,作为非致命错误的温和提示。

测试验证:覆盖六类典型场景

仓库中的功能测试为rkt export的行为提供了完整的验证样本。核心测试定义在 tests/rkt_export_test.go 中,exportTestCases覆盖了六类场景:

  • noOverlaySimpleTest:--no-overlay启动,导出单应用 Pod 后运行新 ACI 校验文件内容;
  • specifiedAppTest:使用--app=rkt-inspect显式指定应用;
  • multiAppPodTest:多应用 Pod 中通过--app导出指定应用(另一个应用写入不同的文件内容以作区分);
  • userNS:--private-users --no-overlay启动后导出,验证 uid/gid 反偏移逻辑;
  • overlaySimpleTest:OverlayFS Pod 导出;
  • overlaySimulateReboot:模拟重启后卸载 overlay 再导出。

每个用例的验证流程高度一致:用 inspect 镜像的--write-file在 Pod 内写入一个测试文件 → 运行 Pod 使其退出 →rkt export导出 ACI → 用rkt run运行新 ACI 并执行--read-file读取同一文件,确认内容一致(ThisIsATest)→ 最后执行rkt gc与rkt image gc清理。

测试按 stage1 风格分流执行:常规容器 stage1 在 tests/rkt_export_container_test.go 中遍历全部用例(并通过common.SupportsOverlay()、common.SupportsUserNS()探测运行环境能力,不满足条件的用例自动跳过);fly stage1 则在 tests/rkt_export_fly_test.go 中单独运行overlaySimpleTest。这些测试文件同时是「如何完整走通一次导出并验证产物」的最佳实操范例。

与rkt image export的区分

rkt export与镜像仓库导出命令rkt image export容易混淆,二者针对不同的对象:

命令对象行为
rkt export UUID OUTPUT_ACI_FILE已退出的 Pod把应用在 Pod 中的运行态文件系统(含运行时写入)重新打包为 ACI
rkt image export IMAGE OUTPUT_ACI_FILE本地镜像仓库中的镜像把已存储的原始镜像(按镜像 ID 或名字引用)直接复制为 ACI,不做任何重新打包

从实现上看,rkt/image_export.go 只是打开 imagestore 并io.Copy原始数据流到输出文件;而rkt export需要经过挂载合并、清单读取、tar/gzip 重建等完整流程。如果你需要的是「容器运行后产生的最终状态」,请使用rkt export;如果只是备份仓库中某个镜像,应使用rkt image export。

小结

rkt export是一个把运行态 Pod 逆向还原为可分发 ACI 镜像的实用工具。使用时需要记住四个关键点:只能导出已退出的 Pod、输出文件必须带.aci扩展名、多应用 Pod 必须用--app指定导出目标、目标文件已存在时需要--overwrite。在底层,rkt 依据 Pod 是否使用 OverlayFS 分别通过「临时挂载合并视图」或「残留挂载点校验」收集完整 rootfs,再借助 appc 规范的 ImageWriter 重建 gzip+tar 格式的 ACI,并针对--private-users启动的 Pod 自动完成 uid/gid 反偏移。相关实现与测试可作为进一步学习 rkt 生命周期、OverlayFS 与 ACI 规范的绝佳切入点。

  • 容器运行时
  • 云原生
  • 网络

【免费下载链接】rkt

[Project ended] rkt is a pod-native container engine for Linux. It is composable, secure, and built on standards.

项目地址:https://gitcode.com/gh_mirrors/rk/rkt
点击查看免费下载
上一篇:从T5到Tk-Instruct-small-def-pos:解码器模型的革命性进化
下一篇:为什么选择anton-l/wav2vec2-large-xlsr-53-estonian?爱沙尼亚语语音识别工具对比分析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 1:33:58

treg公司数据富化实战:用Crunchbase、Apollo等API秒查企业信息

treg公司数据富化实战&#xff1a;用Crunchbase、Apollo等API秒查企业信息 【免费下载链接】treg OpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn 项目地址: https://gitcode.com/GitHub_Trending/treg/treg 如果你想在不签月费、不注…

作者头像 李华
网站建设 2026/9/25 1:33:24

第一个MLX90614项目:用ohcode积木亲手打造非接触式温度计

第一个MLX90614项目&#xff1a;用ohcode积木亲手打造非接触式温度计 【免费下载链接】CupCode_MLX90614红外测温模块 源师兄的红外测试模块扩展 项目地址: https://gitcode.com/yuanshixiong/mlx0614 还在为“怎么从零做一个红外测温仪”发愁&#xff1f;MLX90614 红外…

作者头像 李华
网站建设 2026/9/25 1:32:14

Qt权限管理系统源码解析:RBAC模型与数据库对接实战

简介&#xff1a;本资源是一套基于Qt框架开发的权限管理系统源码&#xff0c;面向学习C与Qt桌面开发、需要理解权限控制模型的学生和开发者。项目同时提供图形界面与命令行两种交互方式&#xff0c;覆盖用户登录注销、读写更新删除等权限的授权与回收、对象列表查看、用户权限信…

作者头像 李华
网站建设 2026/9/25 1:31:32

Base64解码实战:原理、场景与工具全解析

上周有个朋友发来一串字符&#xff1a;aHR0cHM6Ly9ibG9nLnlvdXJkb21haW4uY29t&#xff0c;问我这是不是病毒。我瞥了一眼结尾的&#xff0c;直接说这是Base64编码&#xff0c;解出来是个网址。他一脸惊讶&#xff0c;问我怎么做到的。其实这事儿门槛很低——Base64解码这个操作…

作者头像 李华