仓库为 Private 时,先配置 GitHub 读取凭证和私有模块前缀,再引用精确版本:
go env -w GOPRIVATE=github.com/TencentCloudAgentRuntime
go get github.com/TencentCloudAgentRuntime/sandcamp@v0.1.0import "github.com/TencentCloudAgentRuntime/sandcamp"Sandcamp 返回官方 AGS Go SDK v20250920 的 StorageMount 和
CustomConfiguration 类型,但不会读取凭证、访问 Registry、创建 Tool 或启动
Instance。
mounts, err := sandcamp.RenderMounts(images)
if err != nil {
return err
}
configuration, err := sandcamp.RenderStart(images, spec)
if err != nil {
return err
}
toolRequest.StorageMounts = append(toolRequest.StorageMounts, mounts...)
toolConfiguration := *configuration
toolConfiguration.Image = &mainImage
toolConfiguration.ImageRegistryType = common.StringPtr(string(registryType))
toolRequest.CustomConfiguration = &toolConfiguration
// startRequest.CustomConfiguration 与 startRequest.MountOptions 保持 nil,
// 分别继承 Tool.CustomConfiguration 和 Tool.StorageMounts。这里的 Renderer 只是指 RenderMounts 和 RenderStart 两个“把声明转换成请求字段”
的函数,不是独立服务。两者都是确定性的纯函数。custom Tool 要求默认 Command 和
Probe,所以 Tool 与 Instance 都使用 RenderStart 输出;Tool 副本额外补充主镜像、
Resources 等 Tool 字段。Instance 继承 Tool 的 StorageMounts。
type ImageRegistryType string
const (
ImageRegistryPersonal ImageRegistryType = "personal"
ImageRegistryEnterprise ImageRegistryType = "enterprise"
)
type Image struct {
Reference string
ImageRegistryType ImageRegistryType
}
type SidecarImage struct {
Name string
Reference string
ImageRegistryType ImageRegistryType
}
type ImageSet struct {
SandcampRuntime Image
Sidecars []SidecarImage
}ImageSet 只描述 Image Volume;AGS 主镜像仍配置在 Tool 的
CustomConfiguration.Image。
ImageRegistryPersonal的 AGS 值是personal,对应腾讯云 CCR 个人版;ImageRegistryEnterprise的 AGS 值是enterprise,对应腾讯云 TCR 企业版;- Runtime 和 Sidecar 的
Reference必须指向所选类型的腾讯云 Registry; - GHCR 等通用 OCI Registry 不能直接作为 AGS Image Volume,使用前需要把镜像同步到 调用方自己的 CCR 或 TCR;
- Sidecar
Name必须符合进程名称规则且不可重复; - Sidecar
Name必须与对应的Process.Name一致; - 当前一个 Sidecar Image 声明对应一个 Sidecar Process;
- Reference 不允许为空、首尾空白或 NUL。
func RenderMounts(images ImageSet) ([]*ags.StorageMount, error)返回只读 Image StorageMounts:
| 镜像 | Mount Name | MountPath |
|---|---|---|
| Runtime | sandcamp-runtime |
/mnt/sandcamp |
Sidecar NAME |
sandcamp-sidecar-NAME |
/mnt/sandcamp-sidecars/NAME |
Mount Name 与路径属于 Sandcamp 运行时契约,调用方直接使用返回值。
type Spec struct {
Sidecars []Process
Main []Process
}
type ProcessKind string
const (
Service ProcessKind = "service"
RunToCompletion ProcessKind = "run-to-completion"
)
type Process struct {
Name string
Kind ProcessKind
Command []string
Env map[string]string
WorkDir string
User *ProcessUser
Expose []int
OverlayDevice string
Mounts []BindMount
DiskMounts []string
Probe *ReadinessProbe
Timeout time.Duration
}处理顺序固定为 Sidecars 的声明顺序,随后是 Main 的声明顺序。Main 可以为空或
包含多个进程;其中每个进程都直接使用同一 AGS 主镜像 RootFS。整个声明至少包含一个
Service,不能只有 RunToCompletion Job。
- Main 与 Sidecar 都必须显式填写;
- 长度 1–64;
- 首字符必须是 ASCII 字母或数字;
- 后续只能包含字母、数字、点、下划线和连字符;
- 同一个
Spec内不可重复; - Sidecar 必须存在同名
SidecarImage。
Kind 为空时默认为 Service。
Service:
- 可以配置
Probe,不能配置Timeout; - 有 Probe 时,首次达到成功阈值后才处理下一项;
- 没有 Probe 时,进程成功创建即通过启动 Gate;
- 初始化完成后,PID 存活与持续 Probe 状态共同决定聚合
/ready; - Service 退出会让
/ready变为 503,但不会终止其他 Service; - campd 不自动重启 Service。
RunToCompletion:
- 必须配置
Timeout; - 不能配置
Probe或Expose; - 在 Timeout 内以 0 退出才会处理下一项;
- 非零退出或超时会使初始化失败,后续声明不再启动;
- 已经启动的 Service 保持运行,聚合
/ready保持 503。
- 完整 argv,不是 Shell 字符串;
Command[0]必须是该进程 RootFS 中的规范化绝对路径;- 不执行 Shell 展开、变量替换或引号解析;
- Main 路径在主 Mount Namespace 中解析,可以来自主镜像或 Tool StorageMount;
- Sidecar 路径来自同名 Sidecar Image Volume;
- 调用方不拼接
sandrun参数,也不在Command中加入--。
需要 Shell 时显式声明:
Command: []string{"/bin/sh", "-c", "exec /app/server --port 8080"}- Key 必须符合
[A-Za-z_][A-Za-z0-9_]*; - Key 与 Value 不允许包含 NUL;
- 不允许覆盖
SANDCAMP_SPEC; - Sidecar 启动前清空继承环境,只接收自己的
Process.Env; - Main 保留主镜像与 AGS 环境,再由
Process.Env覆盖同名变量; SANDCAMP_SPEC不会传给子进程;- Image Volume 不应用 OCI
ENV,Sidecar 通常需要显式提供PATH、HOME等。
全部声明编码到一个 SANDCAMP_SPEC,Base64 编码后上限为 120 KiB。证书、大型策略
或配置文件应通过挂载传递。
- Main:主 Mount Namespace 中的绝对路径,可以位于主镜像或 Tool StorageMount;
- Sidecar:同名 Sidecar 镜像中的绝对路径;
/可作为 WorkDir;- Sidecar WorkDir 在
pivot_root后应用。
OverlayDevice 仅适用于 Sidecar,用于 Sidecar 可写 Overlay upper/work 和
DiskMounts。默认值为 /dev/vdb;当 Cube 没有注入平台 Sidecar、业务容器系统盘
位于其他设备时,可以显式覆盖:
OverlayDevice: "/dev/vda"设备必须是规范化绝对路径,并且已由 AGS/Cube 提供为可挂载的块设备。Cube 中设备号 由容器系统盘顺序决定,因此调用方应以业务 sandbox 系统盘为准,而不是根据容量猜测。
type BindMount struct {
Source string
Target string
ReadOnly bool
}
Mounts: []sandcamp.BindMount{{
Source: "/mnt/share",
Target: "/mnt/share",
}}Mounts 仅适用于 Sidecar。Source 是 Sidecar 启动前在主 Mount Namespace 中看到的
绝对路径,可以来自主镜像 RootFS 或 Tool StorageMount;Target 是 Sidecar RootFS
中的绝对路径。sandrun 在 Sidecar OverlayFS 上完成 Bind 后才执行 pivot_root。
- Source 缺失时,sandrun 会在主 Mount Namespace 中将 Source 及其缺失父级创建为
0777目录;若 Source 需要是普通文件,必须提前创建; - Target 缺失时,sandrun 会在 Sidecar OverlayFS 中按 Source 类型创建:目录及其
缺失父级使用
0777,普通文件使用0666; - 已存在的 Source、Target 和父目录不会被 chmod/chown;Target 必须与 Source 类型 一致,即目录对应目录、文件对应文件;
- Source、Target 都必须是规范化绝对路径,且不能是
/; - Target 不能重复、互相包含,或与
/proc、/dev、/sys、/tmp、/run、/etc/hosts、/etc/resolv.conf等标准挂载重叠; ReadOnly只限制当前 Sidecar 的挂载,不改变 Source 在 Main 或其他 Sidecar 中的权限;- Bind 不复制数据,也不执行 UID/GID 映射;
- 多个 Sidecar Bind 同一 Source 时共享文件修改;
- Sidecar 私有 Overlay upper 不在主 Mount Namespace 中,不能作为其他 Sidecar 的共享源。
因为 Sidecar 在 Main 进程之前启动,缺失的目录 Source 由第一个使用它的 Sidecar 预先创建,后续 Sidecar 和 Main 都能看到同一目录。位于只读挂载中的 Source 无法创建。
Docker 等嵌套运行时不应把数据目录直接放在 Sidecar OverlayFS 中。需要使用外部 共享或持久化存储时,调用方应先通过 AGS/Cube 把 XFS 或 ext4 存储挂入主 Mount Namespace,再显式 Bind 到 Sidecar:
Mounts: []sandcamp.BindMount{{
Source: "/mnt/docker-data/rdockerd",
Target: "/var/lib/docker",
}}Source 必须是真实存储挂载中的目录;仅在主容器 Overlay RootFS 中创建目录再 Bind,
不会改变其文件系统类型。每个 dockerd 必须使用独立 Data Root。
DiskMounts 仅适用于 Sidecar。每个 Target 对应沙箱系统盘中的一个稳定私有目录,
sandrun 在自己的 Mount Namespace 中挂载 Overlay Device 后创建该目录,再直接 Bind
到 Sidecar Target:
DiskMounts: []string{"/var/lib/docker"}- Target 缺失时自动在 Sidecar OverlayFS 中创建;
- 目录按
Sidecar Name + RootFS + Target派生,进程重启复用; - 数据与沙箱系统盘同生命周期,不与 Main 或其他 Sidecar 共享;
- Target 不能重复、互相包含或与标准挂载、
Mounts重叠; - 目录直接位于 Overlay Device 的 ext4/XFS 文件系统,适合作为 Docker
overlay2Data Root,避免 Nested Overlay; - 每个 Sidecar 仍使用同一系统盘配额,Sandcamp 不单独限制 DiskMount 容量。
Sidecar 标准挂载会自动把调用方可见的 /sys/fs/cgroup 递归 Bind 进 RootFS,保留
cgroup v1 controller 子挂载或 cgroup v2 unified mount 及其原始权限。该行为不会授予
Capability,也不会把只读 cgroup 变为可写。能否创建子 cgroup 仍取决于 AGS/Cube
提供的 cgroup 委托、Capabilities 和 Seccomp 策略。
type ProcessUser struct {
Name string
UID uint32
GID uint32
}Main 和 Sidecar 都可以使用数字 UID/GID:
User: &sandcamp.ProcessUser{UID: 65532, GID: 65532}两者也都可以使用进程所属镜像内的用户名:
User: &sandcamp.ProcessUser{Name: "app"}Name与数字 UID/GID 是互斥的两种模式;- Main 名称从主镜像
/etc/passwd解析;Sidecar 名称从对应 immutable lower RootFS 的/etc/passwd解析; - Main 命名身份在任何声明启动前解析并缓存,Init Job 改写 passwd 不会改变后续身份;
- 数字身份不要求 passwd 条目,也不会创建用户、组或 Home 目录;
User=nil时 Main 与 Sidecar 都继承运行时身份,通常为 root;- Sandcamp 不自动恢复主镜像 OCI
USER; - 主 GID 取 passwd 条目,不支持
user:group; - 不查询 NSS/LDAP,不解析附加组;
- 所有显式身份都会清空附加组;非 root 身份还会清空
Inheritable/Effective/Permitted/Ambient Capabilities 并设置
no_new_privs; - 显式
0:0或名称root保留 root Capabilities,不设置no_new_privs; - 用户不存在或 passwd 非法时直接失败,不回退 root;
HOME、USER、LOGNAME由Env显式设置。
- 转换为 AGS
CustomConfiguration.Ports的 TCP 端口; - 整个 Spec 最多 8 个,不可重复;
49982、32000、57890为保留端口;- 沙箱内 Loopback 通信不需要 Expose;
- 没有任何 Expose 时,Renderer 不发送 Ports 覆盖。
type ReadinessProbe struct {
Path string
Port int
StartupTimeout time.Duration
Period time.Duration
Timeout time.Duration
FailureThreshold int
SuccessThreshold int
}
probe := sandcamp.HTTPReadinessProbe("/healthz", 9200)只支持从共享 Network Namespace 发起的 Loopback HTTP GET,HTTP 200–399 视为成功。
| 字段 | 默认值 | 作用 |
|---|---|---|
StartupTimeout |
8 秒 | 首次 Ready 的最长等待时间 |
Period |
500 毫秒 | 连续探测周期 |
Timeout |
1 秒 | 单次 HTTP 探测上限 |
FailureThreshold |
3 | Ready 后连续失败多少次变为 Unready |
SuccessThreshold |
1 | Starting/Unready 后连续成功多少次变为 Ready |
首次 Ready 只负责启动 Gate;campd 在初始化完成后仍持续探测,因此 /ready 可以
200→503→200。Probe 失败只改变可观测就绪状态,不终止进程。
时间必须是整毫秒。StartupTimeout 范围 1–30 秒,Period 与 Timeout 范围
100 毫秒–30 秒。所有 Probe StartupTimeout 加所有 Job Timeout 的声明预算最多
25 秒,为 AGS 的 30 秒就绪期限保留启动开销。
Probe 的契约是“配置的共享 Loopback HTTP 端点可用”,而不是“声明该 Probe 的 Linux PID 已就绪”。监听或代理该端点的任意进程都可以满足 Probe;进程身份校验不在 Sandcamp Probe 的职责内。
func RenderStart(images ImageSet, spec Spec) (*ags.CustomConfiguration, error)返回内容:
| 字段 | 内容 |
|---|---|
Command |
/mnt/sandcamp/bin/campd |
Args |
['--'] |
Env |
Base64 编码的 runtime declaration v3 (SANDCAMP_SPEC) |
Ports |
所有 Expose 端口;为空时省略 |
Probe |
AGS GET /ready:49982 |
runtime declaration 是 Go SDK 与 campd 之间的序列化协议,也就是文档或代码中所说的
“wire”结构。调用方不应自行构造它。Renderer 为 Sidecar 注入解析后的 Image Volume
RootFS,把 Mounts 转换为 Sidecar binds;Expose 已转换为 AGS Ports,所以不会
继续出现在 wire Process 中。
顶层 Args=['--'] 用于阻止主镜像原有 CMD 被 AGS 追加到 campd。它不是业务进程
参数。campd 启动 Sidecar 时会自行构造 sandrun argv,并在 sandrun 选项与
Process.Command 之间加入内部 --;调用方不处理这两个标记。
可以使用 errors.Is:
| 错误 | 含义 |
|---|---|
ErrInvalidImage |
镜像引用、Registry Type 或 Sidecar Name 非法 |
ErrDuplicateSidecarImage |
Sidecar Image Name 重复 |
ErrMissingSidecarImage |
Sidecar Process 没有同名镜像 |
ErrInvalidSpec |
名称、路径、Kind、命令、Env、User、Probe 或 Job 组合非法 |
ErrTooManyPorts |
Expose 超过 8 个 |
ErrDuplicatePort |
Expose 端口重复 |
ErrReservedPort |
使用保留端口 |
ErrDuplicateProcess |
进程名称重复 |
ErrStartupBudget |
Probe 与 Job 启动预算超过 25 秒 |
ErrSpecTooLarge |
编码后的 SANDCAMP_SPEC 超过 120 KiB |
- Runtime 镜像包含
/bin/campd、/bin/campd.real和/bin/sandrun; - Runtime setuid launcher 以 PID 1 启动,并获得 EUID 0;
- campd/sandrun 具备 root 与
CAP_SYS_ADMIN; - 业务 sandbox 系统盘作为可用于 OverlayFS upper/work 的 ext4/XFS Block Device 暴露;
- 主镜像与 Image Volume 架构一致;
- Main 与 Sidecar 共享 Network Namespace;
- Mount Namespace、OverlayFS 与
pivot_root不是安全隔离边界。
完整请求示例见 examples/cookbook/main.go。