构建自己的 Template
本页从一个公开的 OCI 镜像开始,走完写定义文件、用 kibo-build 构建、发布、批准,到用新 Template 创建 Workspace 的完整流程。
- 构建机与 Operator 工作机分开时的工具清单与交付方式见在构建机上构建并发布。
- 批准、停用与撤回的完整规则见 目录。
概述
一个 Template 由一份定义文件和它产出的 rootfs 组成:
- 定义文件声明内容来源、rootfs 容量、guest 能力和受管进程的默认值。命令行不重复这些信息。
- rootfs 是一个只读的 ext4 镜像,由
kibo-build从 OCI 镜像展开生成。 - 内核、initramfs 与 guest 程序属于 Platform,不在 Template 里。ctrl 创建 Workspace 时把 Template 与兼容的 Platform 组合起来。
完整流程:
- 选定镜像的精确摘要,写定义文件。
- 在构建机上以 root 运行
kibo-build template,得到构建结果目录。 - 用 Operator 身份运行
kibo template publish,把结果上传到目录。 - 批准这个 revision 并设为默认。
- 用它创建 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_id | Template 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_requirements | Template 要求 Platform 支持的 guest 协议与 rootfs 约定版本。照抄示例中的值 |
guest.capabilities | Template 提供的 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_mib | ext4 的容量,单位 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-file | auth.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 listapprove让这个 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: reference | reference 写的是 tag | 按选定镜像摘要解析出摘要后填入 |
registry source must not carry a tag alongside its digest | reference 同时写了 tag 和摘要 | 去掉 tag,写成 仓库@sha256:摘要 |
template definition requires a rootfs size of at least 256 MiB | rootfs.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 在镜像中不存在 | 修改定义后重新构建并发布。只改这两项时不重新下载镜像 |
下一步
- 在构建机上构建并发布:构建机与 Operator 工作机分开时的交付方式,以及 Platform 的构建。
- 创建与列表:用
--template-revision固定 revision。资源与 Volume 见资源与运行状况与 Volume 与导出恢复。 - 目录:批准、停用、撤回与默认版本。