Kibotos

构建自己的 Template

本页从一个公开的 OCI 镜像开始,走完写定义文件、用 kibo-build 构建、发布、批准,到用新 Template 创建 Workspace 的完整流程。

概述

一个 Template 由一份定义文件和它产出的 rootfs 组成:

  • 定义文件声明内容来源、rootfs 容量、guest 能力和受管进程的默认值。命令行不重复这些信息。
  • rootfs 是一个只读的 ext4 镜像,由 kibo-build 从 OCI 镜像展开生成。
  • 内核、initramfs 与 guest 程序属于 Platform,不在 Template 里。ctrl 创建 Workspace 时把 Template 与兼容的 Platform 组合起来。

完整流程:

  1. 选定镜像的精确摘要,写定义文件。
  2. 在构建机上以 root 运行 kibo-build template,得到构建结果目录。
  3. 用 Operator 身份运行 kibo template publish,把结果上传到目录。
  4. 批准这个 revision 并设为默认。
  5. 用它创建 Workspace,确认能启动、能执行命令。

注意:

  • 导入时不执行镜像内的任何程序。镜像的 Cmd、Entrypoint、User、WorkingDir 与 Env 只记录为来源信息,Workspace 里的进程环境由定义文件决定。
  • 导入不保证任意镜像都能在两个后端上启动,第 5 步的验证不能省。
  • 展开时保留数字形式的属主、权限、xattr、file capability 和链接语义。
  • 没有自助构建服务,也不提供自动批准、镜像签名生成或漏洞扫描。

前提条件

  • 一台 Linux amd64 构建机,可以用 root 运行命令,已安装 kibo-build 的 Debian 包,见独立构建工具包。从镜像仓库导入不需要 Docker、Go 或源码。
  • 一个可用的集群,以及一份 Operator 身份,见注册 Operator。
  • node 所用后端已有批准并设为默认的 Platform。还没有时,用发布包里的 kibotos-platform-<backend>_<tag>_amd64.tar.zst(例如 kibotos-platform-kibovm_v0.2.0_amd64.tar.zst) 发布,见 platform。
  • 构建终端不载入 Operator、ctrl 或对象存储的凭证。环境里有 KIBOTOS_OPERATOR_*、KIBOTOS_CTRL_*、KIBOTOS_ARTIFACT_STORE_* 或 KIBOTOS_S3_* 变量时,kibo-build 拒绝运行。

步骤

选定镜像摘要

定义文件只接受精确摘要,不接受 tag。用 Skopeo 把 tag 解析成摘要:

skopeo inspect --format '{{.Digest}}' docker://docker.io/library/python:3.13-slim
  • 输出形如 sha256:09a1…。多平台镜像得到的是 image index 的摘要,kibo-build 从中选出 linux/amd64。
  • tag 会随上游更新指向新的镜像,摘要不会。同一个摘要每次构建得到同样的内容。

写定义文件

下面在 /srv/templates/python 下写一份定义,把上一步的摘要填进 source.reference:

digest=$(skopeo inspect --format '{{.Digest}}' docker://docker.io/library/python:3.13-slim)
install -d /srv/templates/python
cat > /srv/templates/python/template.json <<EOF
{
  "schema": "kibotos.template-definition/v1",
  "template_id": "python-slim",
  "display_name": "Python 3.13 slim",
  "description": "Python 3.13 imported from the official slim image",
  "platform": "linux/amd64",
  "architecture": "x86_64",
  "source": {
    "kind": "registry",
    "reference": "docker.io/library/python@$digest"
  },
  "platform_requirements": {
    "guest_protocol": 1,
    "rootfs_contract": 2
  },
  "guest": {
    "capabilities": ["filesystem.v1", "process.v1", "application-config.v1"],
    "ready_semantics": "vsock-http-ready/v1"
  },
  "required_capabilities": {},
  "rootfs": {
    "size_mib": 2048
  },
  "process_defaults": {
    "path": "/usr/local/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin",
    "shell": "/bin/bash"
  }
}
EOF

各字段:

字段说明
schema固定为 kibotos.template-definition/v1
template_idTemplate ID,创建 Workspace 时用 --template 引用它。同一个 ID 的每次发布是它的一个 revision
display_name、description在 kibo template list 与 get 中显示
platform、architecture固定为 linux/amd64 与 x86_64
source内容来源。kind 为 registry 时 reference 写 仓库@sha256:摘要,不能同时带 tag;为 containerfile 时见用 Containerfile 构建
platform_requirementsTemplate 要求 Platform 支持的 guest 协议与 rootfs 约定版本。照抄示例中的值
guest.capabilitiesTemplate 提供的 guest 能力。上面三项是文件、进程与启动配置的基本能力。桌面(desktop.v1)需要 rootfs 里装有桌面组件,kibo workspace desktop view 还需要 service.unix-stream.v1;写法参考仓库里的 desktop 配方
guest.ready_semantics固定为 vsock-http-ready/v1
required_capabilities要求 node 具备的能力。没有特殊要求时写 {}
rootfs.size_mibext4 的容量,单位 MiB,不小于 256。要装得下展开后的镜像,并给 Workspace 里的常用操作留出余量
process_defaults通过 Workspace 进程接口启动的进程的默认值,见下表

process_defaults 的字段都可以省略:

字段未声明时说明
path通用的默认 PATH完整的 PATH 值。不从镜像的 ENV 推导,镜像把程序装在非标准目录时要在这里写出来。声明为空字符串表示空 PATH。请求里的 environment.PATH 优先于它,空字符串也会保留
shell/bin/sh绝对路径。请求的命令行为空时执行这个程序。命令行非空时直接执行,不会自动加上 shell 参数;SHELL 环境变量不影响程序选择
user进程以 root 运行设置时必须是镜像 /etc/passwd 里已有的非 root 账户。受管进程、文件操作与桌面会话随后默认以这个账户运行

注意:

  • 示例镜像把 Python 装在 /usr/local/bin,所以 path 把它放在最前面。/bin/bash 存在于这个镜像里;换成 Alpine 等镜像时,按镜像实际内容修改 shell。
  • path 与 shell 也不从宿主环境或 shell 初始化文件推导。文件是否存在、是否可执行在进程启动时检查。发布和读取时都拒绝包含 NUL 的值。
  • Template 要求 Platform 支持 application-config.v1,不支持的 Platform 无法与它组合。
  • Workspace 的持久 Volume 默认挂在 /data,挂载路径在创建 Workspace 时指定,不写在定义里。

构建

以 root 运行一条命令,完成下载镜像、生成 ext4 和组装 Template:

template_result=$(sudo kibo-build template /srv/templates/python/template.json)
echo "$template_result"
jq '{kind, publication, provenance}' "$template_result/result.json"
  • 标准输出只打印结果目录的路径,进度写到标准错误。
  • 构建根默认是 /var/lib/kibotos/build:结果在 results/ 下,stage 缓存在 cache/stages/ 下。改用其他目录时在 /etc/kibotos/build/config.toml 里写 root = "/srv/build-root",或用 KIBOTOS_BUILD_CONFIG、--config FILE 指向另一份配置。配置文件可以不存在,子命令的显式路径参数优先于它。
  • 结果目录只保存元数据与 stage 引用,rootfs 本身留在 stage 缓存里。发布时两者都要能读到。
  • 重新构建会复用已完成的 stage。只改名称、能力或 process_defaults 时不重新下载和展开镜像。

镜像在私有仓库时,用下面的参数指定访问配置。kibo-build 不读取 root 账户默认的认证文件和仓库配置:

参数说明
--registry-auth-fileauth.json 格式的认证文件
--registry-cert-dir私有 CA(*.crt)或客户端证书与私钥(*.cert、*.key)所在目录
--signature-policy镜像签名策略文件。指定后每次构建都重新校验签名,缓存命中也一样
--registry-signatures-dir签名附件的存储配置目录,配合 --signature-policy 使用
  • 默认接受未签名的镜像,但所有输入都必须通过摘要校验。
  • kibo-build 也不读取 REGISTRY_AUTH_FILE 等环境变量。凭证路径和内容不会写入任何产物。
  • Skopeo 复用标准的 /var/lib/containers/cache 元数据缓存。该缓存不保存认证配置,也不替代摘要与签名校验。

发布

在载入了 Operator 身份的终端里执行。下面假设发布账户与构建在同一台机器上,并且能读取构建根:

template_result=/var/lib/kibotos/build/results/kibo-build-result-sha256-0000000000000000000000000000000000000000000000000000000000000000
kibo template publish "$template_result" --json > python-publish.json
template_id=$(jq -er '.result.template.template_id' python-publish.json)
revision=$(jq -er '.result.template.revision' python-publish.json)
  • 把 template_result 换成构建步骤实际打印的路径。结果目录以内容摘要命名,results/ 对其他账户可读。
  • 发布账户与构建使用同一个构建根时,kibo 从同一份构建配置找到 stage 缓存,不需要 --build-cache-root。
  • 构建机与 Operator 工作机分开时,按交付并发布复制构建根,并传 --build-cache-root。
  • 发布只在目录里登记一个候选 revision。批准之前它不出现在默认的 kibo template list 里,用 kibo template list --selection any 查看。

批准并设为默认

kibo template approve "$template_id" --revision "$revision"
kibo template set-default "$template_id" --revision "$revision"
kibo template list
  • approve 让这个 revision 可以被选用;set-default 让不指定 revision 的新 Workspace 使用它。
  • 这两条命令只改 Template 目录,不影响任何 Platform 的默认版本。

创建 Workspace 验证

用新 Template 创建一个 Workspace,执行一条命令,确认之后删除:

workspace_id=$(kibo workspace create --template "$template_id" --memory 1G -q)
kibo workspace exec "$workspace_id" -- python3 --version
kibo workspace delete "$workspace_id"
  • 第一次使用某个 Template 时,node 要先下载并解压 rootfs,Workspace 在 CREATING 停留的时间更长。kibo workspace create 默认等到它可用。
  • 能执行命令说明 guest 已经启动并就绪,process_defaults 里的 shell 与 path 也生效了。

用 Containerfile 构建

需要在镜像上再装软件时,把 source 改成 Containerfile。kibo-build 用 BuildKit 构建出 OCI 镜像,再按同样的流程生成 rootfs:

cat > /srv/templates/python/Containerfile <<'EOF'
FROM docker.io/library/python:3.13-slim@sha256:09a17cad02668ae61ece136b4fd36ac3849e14f670ebedb637ea3e15580525bc
RUN apt-get update \
 && apt-get install -y --no-install-recommends git \
 && rm -rf /var/lib/apt/lists/*
EOF
jq '.source = {"kind": "containerfile", "context": ".", "containerfile": "Containerfile"}' \
  /srv/templates/python/template.json > /srv/templates/python/template.json.new
mv /srv/templates/python/template.json.new /srv/templates/python/template.json
sudo kibo-build template /srv/templates/python/template.json
  • 构建机需要 Docker Engine 与 Buildx。工具清单见构建工具与 Debian 包。
  • context 与 containerfile 是相对定义文件所在目录的路径,不能指到这个目录之外。
  • 基础镜像建议写成 tag@sha256:摘要,否则每次构建可能拿到不同的基础镜像。
  • 普通构建复用 BuildKit 的层缓存。要重新拉取基础镜像并重装软件,加 --refresh-content。
  • 仓库里的 coding、desktop 与 minimal 都是这种写法,可以作为参考。

从本地 OCI 导入

定义文件不能直接引用本地目录。镜像在本地 OCI layout 目录里时分两步:先生成 ext4,再用同一份定义组装:

sudo kibo-build rootfs --oci-layout /srv/images/python \
  --size-mib 2048 --output /srv/output/python.ext4
sudo kibo-build template /srv/templates/python/template.json \
  --rootfs /srv/output/python.ext4
  • --size-mib 必须与定义里的 rootfs.size_mib 一致,组装时会再次核对。
  • 第一步同时写出 /srv/output/python.ext4.json,记录来源与校验信息。输出文件必须不存在。第二步要用其他文件名的 JSON 时,传 --rootfs-metadata FILE。
  • 第二步不需要 root,能读取这两个文件的账户就可以执行。它不重新展开镜像,只校验 ext4 与 JSON 是否对应,然后组装。只改定义里的名称、能力或 process_defaults 时,只需重跑这一步。
  • 目录里有多个镜像时,用 --source-tag 选择一个。
  • 从 OCI layout 格式的 tar 归档导入时,把 --oci-layout 换成 --oci-archive /srv/images/python.oci.tar。docker save 生成的归档不是这种格式。

构建缓存

kibo-build 在输入没有变化时复用阶段结果。缓存命中时会重新校验记录的输入与结果摘要。

改动影响
名称、描述或 process_defaults只重新组装,不重新展开镜像,也不重新压缩 ext4
容量重新生成 ext4,复用 OCI 准备的结果
选定的 OCI manifest、实际的宿主工具或 kibo-build 可执行文件得到新的缓存键,重新构建 ext4。升级工具或工具包就属于这种情况
Containerfile 构建上下文里的任何文件(按 .dockerignore 过滤后)重新执行内容阶段。未提交的修改也算,缓存键按文件内容计算
  • 压缩只依赖 ext4,使用同一镜像的两个 Template 只压缩一次。
  • BuildKit 进度输出中的 CACHED 表示镜像层命中,kibo-build 输出的 HIT、MISS 表示阶段结果,两者分开看。
  • 当前格式的损坏结果会移入隔离区并重建。未知格式需要显式清理,见清理构建缓存。
  • 缓存命中不证明构建是确定性的。要验证这一点,按 Rootfs 构建与启动验收强制重建。

注意

  • 构建成功只说明 rootfs 通过了构建时的校验:摘要、平台、层展开、ext4 检查。guest 能否启动、Workspace 行为是否正确,要在选定的 Platform 上验证。
  • rootfs 中的内核模块目录或固件目录非空时构建失败。内核与模块由 Platform 提供。
  • 来源或 blob 的摘要不符、平台或 media type 不支持、层展开失败、ext4 或 e2fsck 检查失败、结果的完整性或敏感字段校验失败时,构建同样失败。
  • 当前版本不检测 /sbin/init 冲突,导入成功不代表这一点已被验证。
  • 镜像内容或定义改变后发布得到新的 revision。已有 Workspace 继续使用它创建时固定的 revision,不会自动更新。
  • 把默认版本移回旧 revision、停用或撤回某个 revision 的方法见 template 准入。

排查

现象原因处理
registry source requires an exact @sha256: referencereference 写的是 tag按选定镜像摘要解析出摘要后填入
registry source must not carry a tag alongside its digestreference 同时写了 tag 和摘要去掉 tag,写成 仓库@sha256:摘要
template definition requires a rootfs size of at least 256 MiBrootfs.size_mib 小于 256调大容量
展开镜像时报空间不足rootfs.size_mib 装不下镜像内容调大容量后重新构建
命令报告无法确认清理是否完成失败时 kibo-build 通常会终止工作进程、回收挂载和临时目录,并保留已成功的 stage 供重跑复用。这里它保留了恢复所需的状态和路径按提示执行 kibo-build rootfs-recover STAGING_DIRECTORY 后重试。恢复不触碰其他构建
build process refuses inherited configuration or credential构建终端里有 Operator、ctrl 或对象存储的环境变量换一个没有载入这些变量的终端
发布提示应传的 stage 缓存路径--build-cache-root 传成了 cache 而不是 cache/stages按提示的精确路径重传,见发布
set-default 以退出码 13 失败这个 revision 还没有批准,或已被撤回先执行 kibo template approve
kibo workspace exec 报找不到程序程序不在 process_defaults.path 里,或 shell 在镜像中不存在修改定义后重新构建并发布。只改这两项时不重新下载镜像

下一步

本页内容