Skip to content
TencentCloudAgentRuntimePublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Sandcamp

Sandcamp 是一个用于 AGS 的进程编排 SDK。它让一个沙箱同时运行:

  • 主镜像中的零到多个进程;
  • 通过 Image Volume 挂载的 Sidecar 镜像进程;
  • 在 Service 之前执行并成功退出的 Init Job。

主镜像不需要包含 campd 或 sandrun。这两个静态二进制由单独的 Runtime Image Volume 提供。

发布标签严格使用稳定 SemVer:vMAJOR.MINOR.PATCH。从 v1.0.0 开始,Major 表示 不兼容变更,Minor 表示向后兼容的新功能,Patch 表示向后兼容的修复;v0.x.y 表示 公开 API 仍处于初始开发阶段,Minor 用于功能或 API 演进,Patch 用于兼容修复。推送 合规标签后,CI 会先执行完整回归,再发布 linux/amd64 Runtime 镜像的精确版本、 sha-<完整 Git Commit> 和 latest 标签。普通 main push 与 Pull Request 只执行测试, 不发布镜像。AGS Image Volume 当前不能直接从 GHCR 拉取;使用前必须把 Runtime 镜像同步到调用方自己的腾讯云 CCR 或 TCR。需要固定内容时,同步源使用精确版本、SHA 标签或 Digest,目标则使用 不可变 tag。AGS 主镜像字段要求 tag,不接受 tag@digest。

完整 Cookbook · API 参考 · 技术方案 · 排障手册 · 验证范围 · 运行态回归

最小接入

仓库为 Private 时,先配置 GitHub 读取凭证和私有模块前缀,再引用精确版本:

go env -w GOPRIVATE=github.com/TencentCloudAgentRuntime
go get github.com/TencentCloudAgentRuntime/sandcamp@v0.1.0

先把 Runtime 镜像同步到 AGS 能访问的 Registry。例如使用 CCR:

docker pull ghcr.io/tencentcloudagentruntime/sandcamp-runtime:v0.1.0
docker tag \
  ghcr.io/tencentcloudagentruntime/sandcamp-runtime:v0.1.0 \
  ccr.ccs.tencentyun.com/<namespace>/sandcamp-runtime:v0.1.0
docker push ccr.ccs.tencentyun.com/<namespace>/sandcamp-runtime:v0.1.0

TCR 使用对应实例的 Registry 地址。主镜像与 Sidecar 镜像同样需要位于 AGS 支持的 Registry。Sandcamp 对 AGS 的两种 Image Registry 类型提供了明确常量:

SDK 常量 AGS 值 Registry
sandcamp.ImageRegistryPersonal personal 腾讯云 CCR 个人版
sandcamp.ImageRegistryEnterprise enterprise 腾讯云 TCR 企业版

GHCR 仅作为构建产物的分发来源,不能填写到 Image.Reference 后再标记为 personal 或 enterprise。

先声明 Runtime、Sidecar 镜像和进程:

registryType := sandcamp.ImageRegistryPersonal // 企业版使用 ImageRegistryEnterprise

images := sandcamp.ImageSet{
    SandcampRuntime: sandcamp.Image{
        Reference:         runtimeImage,
        ImageRegistryType: registryType,
    },
    Sidecars: []sandcamp.SidecarImage{{
        Name:              "proxy",
        Reference:         proxyImage,
        ImageRegistryType: registryType,
    }},
}

spec := sandcamp.Spec{
    Sidecars: []sandcamp.Process{{
        Name:    "proxy", // 与 SidecarImage.Name 对应
        Command: []string{"/usr/local/bin/python", "/opt/proxy/app.py"},
        WorkDir: "/opt/proxy",
        User:    &sandcamp.ProcessUser{Name: "app"},
        Env: map[string]string{
            "HOME": "/home/app",
            "PATH": "/usr/local/bin:/usr/bin:/bin",
        },
        Mounts: []sandcamp.BindMount{{
            Source: "/mnt/share", // 主镜像或 Tool StorageMount 中的现有路径
            Target: "/mnt/share", // Sidecar RootFS 中的现有路径
        }},
        Expose: []int{9200},
        Probe:  sandcamp.HTTPReadinessProbe("/healthz", 9200),
    }},
    Main: []sandcamp.Process{
        {
            Name:    "prepare",
            Kind:    sandcamp.RunToCompletion,
            Command: []string{"/app/prepare"},
            Timeout: 2 * time.Second,
        },
        {
            Name:    "api",
            Command: []string{"/app/server"},
            User:    &sandcamp.ProcessUser{UID: 0, GID: 0},
            Expose:  []int{8080},
            Probe:   sandcamp.HTTPReadinessProbe("/healthz", 8080),
        },
    },
}

Sidecars 先按声明顺序处理,随后处理 Main。Main 是数组,因此可以为空、包含 一个进程,或让同一主镜像 RootFS 启动多个进程。Sidecar 的 Mounts 会把主 Mount Namespace 中的路径 Bind 到 Sidecar RootFS;多个 Sidecar 绑定同一个 Source 时共享 同一份数据。

再把声明转换为 AGS 请求字段:

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

// Instance 无需覆盖启动配置:CustomConfiguration 与 MountOptions 均保持 nil,
// 分别继承 Tool.CustomConfiguration 和 Tool.StorageMounts。

AGS custom Tool 要求默认 Command 和 Probe,所以 Tool 使用 RenderStart 的结果, 再在副本中补充主镜像和资源配置。Instance 如果不需要修改进程声明,直接继承 Tool 即可;只有每个 Instance 需要不同声明时,才重新调用 RenderStart 并作为覆盖配置传入。 完整、直接发送请求的代码见 examples/cookbook/main.go。

运行 Cookbook 时从示例配置创建本地 .env:

cp examples/cookbook/.env.example examples/cookbook/.env
# 编辑 examples/cookbook/.env
go -C examples/cookbook run .

Cookbook 使用独立 go.mod 管理示例依赖。真实 .env 已被 Git 忽略;进程已有的环境 变量优先于文件中的同名值。

Cookbook 固定使用体积较小的 Docker Official Bash 主镜像和 OpenSandbox Egress Sidecar。主镜像自带 Bash、ps、wget、nslookup 和 nc,因此 Instance 进入 RUNNING 后可以直接登录查看共享 PID/Network Namespace 中的效果。 示例还会把 Main 的 /var/tmp Bind 到 Egress 的 /var/tmp,并打印双向写入与 inode 对比命令;两个上游镜像都预先包含该目录,不需要制作派生镜像。

运行原理

AGS PID 1
  └─ campd-launcher (受限 setuid bootstrap)
       └─ campd (root)
            ├─ sandrun → OverlayFS / pivot_root → Sidecar Service 或 Job
            ├─ sandrun → OverlayFS / pivot_root → Sidecar Service 或 Job
            ├─ exec Main Job
            └─ exec Main Service(s)

GET 127.0.0.1:49982/ready → 聚合 Readiness

Go SDK 保留结构化声明,并把 Sidecar 名称解析为对应 Image Volume 的挂载路径。 campd 在运行时构造 sandrun 参数;调用方只填写进程镜像中的 Command、 WorkDir 和 User,不拼接 sandrun 或 --。

sandrun 只为 Sidecar 创建 Mount Namespace。它不创建 PID、Network、User 或 IPC Namespace,因此 Main 与 Sidecar 共享 Loopback、PID 可见性、cgroup 和沙箱资源限制。 Mount Namespace 与 pivot_root 用于依赖兼容,不构成额外的安全隔离边界。

进程与就绪语义

Process.Kind 省略时默认为 Service:

  • Service 有 Probe:首次达到成功阈值后才启动下一项,之后持续探测;
  • Service 没有 Probe:进程成功创建即通过启动 Gate,存活 PID 代表 Ready;
  • RunToCompletion:必须在 Timeout 内以 0 退出,成功后才处理下一项;
  • 声明中至少要有一个 Service,不能只有 Job。

campd 的 /ready 是动态聚合状态。任一 Service 退出或探针达到失败阈值会返回 503; 探针恢复到成功阈值后可再次返回 200。campd 不自动重启进程,也不会因一个 Service 退出而终止其他 Service。退出进程遗留在同一进程组的子进程会先收到 SIGTERM,超时 后升级为 SIGKILL。

如果 Init Job 非零退出、超时,或某个 Service 在初始化阶段失败,后续声明不会启动, 已经启动的 Service 保持运行,但整体 /ready 保持 503。

RootFS 与用户

Sidecar Image Volume 是 immutable OverlayFS lowerdir。sandrun 使用 AGS 提供的 ext4 设备承载独立 upperdir/workdir,完成挂载后执行 pivot_root;普通镜像路径具有 Copy-on-Write 语义,而 lowerdir 不变。

Main 和 Sidecar 使用同一个身份模型。可以直接指定数字 UID/GID:

User: &sandcamp.ProcessUser{UID: 65532, GID: 65532}

也可以使用进程所属镜像内的用户名:

User: &sandcamp.ProcessUser{Name: "app"}

Main 名称从主镜像 /etc/passwd 解析;Sidecar 名称从对应 Sidecar immutable lower RootFS 的 /etc/passwd 解析。同一个名称可以在不同镜像中得到不同 UID/GID。数字 身份不查询 passwd,直接应用声明的 UID/GID。

User=nil 时继承运行时身份,通常为 root。显式选择非 root 身份时会清空附加组与 Capabilities,并设置 no_new_privs;显式选择 0:0 或名称 root 时保留 root Capabilities。用户缺失或 passwd 非法时直接失败,不回退 root。Sandcamp 不查询 NSS/LDAP、解析附加组,也不自动设置 HOME、USER 或 LOGNAME。

Image Volume 不会自动应用 OCI Entrypoint、Cmd、Env、WorkingDir、User 或 健康检查;Sidecar 需要显式声明这些运行信息。

标准挂载会将沙箱现有的 /sys/fs/cgroup 递归映射进每个 Sidecar,保留 cgroup 原有读写权限,但不授予额外 Capability。Docker Data Root 可通过 DiskMounts: []string{"/var/lib/docker"} 直接使用 Sidecar Overlay Device 上的独立 ext4/XFS 目录,避免在 Sidecar OverlayFS 上再次使用 overlay2。Overlay Device 默认 是 /dev/vdb,业务系统盘位于其他设备时可通过 Process.OverlayDevice 覆盖;若需要 外部持久化或共享,再通过 Process.Mounts 绑定 AGS/Cube 提供的 StorageMount。

当前边界

  • 主镜像与所有 Image Volume 必须是兼容的 Linux CPU 架构;
  • OverlayFS 与 Mount Namespace 需要 root 和 CAP_SYS_ADMIN;
  • 修改共享网络策略的 Sidecar 还需要相应网络 Capability;
  • 全部声明编码到一个 SANDCAMP_SPEC,编码后上限为 120 KiB;
  • 启动预算是所有 Probe StartupTimeout 与 Job Timeout 之和,最多 25 秒;
  • HTTP Probe 表示共享 Loopback 上的端点状态,不承诺响应者属于某个 Linux PID;
  • campd 的后代回收保证以进程组为边界,主动 setsid 的后代不在该保证内;
  • Sandcamp 可映射现有 cgroup,但不创建、委托或分配 cgroup,也不提供单进程重启或 Registry 元数据解析。

开发与验证

task fmt
task test
task lint
task build
task test:campd-linux
task test:sandrun-linux
task test:dind-linux
task test:stack-linux
task e2e:list
task e2e:build

普通单元测试不会访问云 API。真实 AGS 回归需要显式运行 test/e2e。Cookbook 会实际创建 Tool 和 Instance,也需要在 准备好镜像与凭证后单独执行。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages