云沙箱支持直接从容器镜像仓库拉取镜像来构建 Sandbox 模板。当业务需要预置特定语言运行时、系统依赖、私有 SDK 或数据科学工具链时,把这些内容打包成一个容器镜像,再从镜像构建模板,比在运行时逐条安装依赖更可控、更可复现。
本文以 GitHub Container Registry(GHCR) 上的私有镜像为例,说明如何通过 OpenAPI CreateTemplate 在中国香港地域(cn-hongkong)从 ghcr.io/<owner>/<image>:<tag> 构建第二代(micro)运行时模板、创建 Sandbox 并校验运行结果。GHCR 属于需要鉴权的私有仓库,创建模板时需要携带拉取凭据;GHCR 是海外仓库,构建地域需要能够访问 ghcr.io,本文选择中国香港地域。
业务场景
-
团队把统一的 Python/Node 运行时和常用依赖打包成镜像,多个 Agent 共用同一基础模板。
-
分析类任务需要预装
pandas、numpy、绘图库等重依赖,希望构建期一次装好,而不是每次运行时安装。 -
私有 SDK 或内部工具只发布到 GHCR 私有仓库,不能公开拉取。
-
需要把镜像版本与模板版本对齐,做到构建可追溯、可回滚。
前置条件
-
已开通云沙箱,已创建 Team 并获取 Team ID。
-
调用 OpenAPI 的阿里云账号或 RAM 用户已配置模板 API 权限(
fcsandbox:CreateTemplate、fcsandbox:GetTemplate、fcsandbox:DeleteTemplate),参见构建和管理模板的权限配置。 -
已获取目标地域的
E2B_API_KEY(用于创建 Sandbox),参见 E2B SDK 接入参数说明。 -
已在 GHCR 上准备好镜像,且账号与目标地域已开通第二代运行时白名单(未开通请联系技术支持)。第二代运行时仅支持 Ubuntu/Debian 系镜像,且需保留
systemd、openssh-server、sudo、chrony、fuse3、iptables等系统组件,完整要求见构建和管理模板。满足要求的最小 Dockerfile 示例:FROM debian:12 RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ systemd systemd-sysv openssh-server sudo chrony fuse3 iptables \ python3 curl ca-certificates \ && rm -rf /var/lib/apt/lists/* CMD ["/sbin/init"] -
已创建用于拉取镜像的 GitHub 凭据(见下节 镜像仓库凭据 )。
-
安装云沙箱 OpenAPI SDK 和 E2B SDK:
pip install alibabacloud_fcsandbox20260509 e2b==2.31.0
镜像仓库凭据
从 GHCR 私有仓库拉取镜像需要携带用户名和密码。这里的凭据是 GitHub 的 Personal Access Token (classic),不是账号登录密码:
-
用户名:填 GitHub 用户名(镜像所属的 owner)。
-
密码:填创建的 Personal Access Token (classic) 值(形如
ghp_xxxxxxxx)。
Token 权限要求:Token 只需勾选 read:packages 权限。此外 Token 所属账号必须对该私有包有读取权限;镜像属于开启了 SSO 的组织时,还需在 Token 页面为该组织完成授权。缺少这些权限会导致构建阶段拉取镜像失败(鉴权 401 / 403,可见 GitHub 相关文档)。
创建路径:GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token (classic),在 Select scopes 中勾选 read:packages。
凭据通过 CreateTemplate 请求体的 runtime_config.sandbox_config.registry_config.auth_config 传递:
|
字段 |
含义 |
取值 |
|
|
镜像仓库用户名 |
GitHub 用户名 |
|
|
镜像仓库密码 |
Personal Access Token (classic) |
凭据属于敏感信息,建议通过环境变量或密钥管理服务注入,不要硬编码进代码或提交进代码仓库。
推荐流程
建议把“自定义镜像 → 模板 → Sandbox”拆成四步:
-
调用
CreateTemplate创建模板,sandbox_config.image指定 GHCR 镜像地址、generation指定为2(第二代 micro 运行时)、registry_config.auth_config传入仓库凭据,拿到template_id。 -
轮询
GetTemplate直到构建状态进入终态(ready/error)。 -
用
template_id创建 Sandbox,等待数据面就绪后执行命令做冒烟校验。 -
校验通过后销毁 Sandbox,避免资源长期占用;模板确认不再使用时调用
DeleteTemplate删除。
其中模板构建是一次性动作:同一镜像构建出的 template_id 可以被后续多次 Sandbox.create 复用,生产上通常把构建和运行拆成两个阶段,而不是每次运行都重新构建。
示例代码
以下示例从 GHCR 私有镜像 ghcr.io/<owner>/<image>:<tag> 构建第二代运行时模板,创建 Sandbox,执行命令打印校验标记,最后销毁 Sandbox。阿里云 AK、GitHub 凭据、E2B_API_KEY 均从环境变量读取。
cpu 单位为核,memory_size、disk_size 单位均为 MB;三项均可缺省,缺省时使用默认规格 2 核 / 2048 MB / 15360 MB。规格不足会导致模板构建或 Sandbox 启动失败:内存不足时镜像内进程被 OOM 终止,CPU 不足会拖慢构建与冷启动,磁盘不足会导致文件写入失败。第二代运行时(generation 为 2)配置的磁盘大小包含镜像自身占用,实际可写空间 = disk_size - 镜像大小,需按镜像解压后体积,加上工作负载的峰值内存与磁盘写入量取值并留出余量。
import os
import time
from alibabacloud_fcsandbox20260509 import models
from alibabacloud_fcsandbox20260509.client import Client as FCSandboxClient
from alibabacloud_tea_openapi import models as open_api_models
from e2b import Sandbox
REGION = "cn-hongkong"
POP_ENDPOINT = f"fcsandbox.{REGION}.aliyuncs.com"
API_URL = f"https://api.{REGION}.e2b.fc.aliyuncs.com"
DOMAIN = f"{REGION}.e2b.fc.aliyuncs.com"
SOURCE_IMAGE = "ghcr.io/<owner>/<image>:<tag>"
SANDBOX_MARKER = "sandbox-ok"
READY_TIMEOUT_SECONDS = 90
READY_INTERVAL_SECONDS = 3
def required_env(name: str) -> str:
value = os.environ.get(name, "").strip()
if not value:
raise RuntimeError(f"缺少环境变量: {name}")
return value
def main() -> int:
team_id = required_env("FCSANDBOX_TEAM_ID")
pop_client = FCSandboxClient(
open_api_models.Config(
access_key_id=required_env("ALIBABA_CLOUD_ACCESS_KEY_ID"),
access_key_secret=required_env("ALIBABA_CLOUD_ACCESS_KEY_SECRET"),
region_id=REGION,
endpoint=POP_ENDPOINT,
)
)
# 1. 创建模板:第二代 micro 运行时 + GHCR 私有镜像 + 拉取凭据,创建后立即开始首次构建
response = pop_client.create_template(
models.CreateTemplateRequest(
body=models.CreateTemplateInput(
name=f"ghcr-demo-{int(time.time())}",
team_id=team_id,
runtime_config=models.CreateTemplateRuntimeConfig(
cpu=2,
memory_size=2048,
disk_size=15360,
sandbox_config=models.CreateTemplateSandboxConfig(
image=SOURCE_IMAGE,
generation=2,
registry_config=models.CreateTemplateRegistryConfig(
auth_config=models.CreateTemplateRegistryAuthConfig(
user_name=required_env("GHCR_USERNAME"),
password=required_env("GHCR_PASSWORD"),
),
),
),
),
)
)
)
template_id = response.body.template_id
print(f"template_id={template_id}")
# 2. 轮询 GetTemplate 直到构建进入终态
deadline = time.time() + 900
while True:
got = pop_client.get_template(template_id, models.GetTemplateRequest(team_id=team_id))
state = got.body.status.state
print(f"state={state}")
if state == "ready":
break
if state == "error":
reason = got.body.status.reason.message if got.body.status.reason else ""
raise RuntimeError(f"构建失败: {reason}")
if time.time() > deadline:
raise RuntimeError("构建超时")
time.sleep(10)
# 3. 用 template_id 创建 Sandbox
sandbox = Sandbox.create(
template=template_id,
timeout=900,
api_key=required_env("E2B_API_KEY"),
api_url=API_URL,
domain=DOMAIN,
)
print(f"sandbox_id={sandbox.sandbox_id}")
try:
# 等待数据面就绪并执行冒烟校验
last_error = None
deadline = time.monotonic() + READY_TIMEOUT_SECONDS
while True:
remaining = deadline - time.monotonic()
if remaining <= 0:
raise RuntimeError(f"数据面等待超时: {last_error}") from last_error
try:
result = sandbox.commands.run(f"echo {SANDBOX_MARKER}", timeout=min(60, remaining))
break
except Exception as err:
last_error = err
time.sleep(min(READY_INTERVAL_SECONDS, max(deadline - time.monotonic(), 0)))
if result.exit_code != 0 or SANDBOX_MARKER not in result.stdout:
raise RuntimeError(f"校验失败: exit={result.exit_code}, stdout={result.stdout}")
print(f"stdout={result.stdout.strip()}")
finally:
# 4. 销毁 Sandbox
sandbox.kill()
# 模板可长期复用;确认不再使用时删除模板清理资源
# pop_client.delete_template(template_id, models.DeleteTemplateRequest(team_id=team_id))
return 0
if __name__ == "__main__":
raise SystemExit(main())
运行方式(凭据放在环境变量中):
export ALIBABA_CLOUD_ACCESS_KEY_ID="<your-access-key-id>"
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="<your-access-key-secret>"
export FCSANDBOX_TEAM_ID="<your-team-id>"
export GHCR_USERNAME="<github-username>"
export GHCR_PASSWORD="ghp_xxxxxxxxxxxxxxxxxxxx" # Personal Access Token (classic)
export E2B_API_KEY="<your-e2b-api-key>"
python3 build_from_ghcr.py
正常输出会依次打印 template_id
、构建状态、sandbox_id
,以及校验标记 sandbox-ok
;脚本最后会销毁 Sandbox。
上线建议
-
凭据管理:GitHub Personal Access Token (classic)、阿里云 AK 和
E2B_API_KEY通过环境变量或密钥管理服务注入,不要写进代码或提交进 Git;Token 遵循最小权限原则,仅勾选read:packages,并定期轮转。 -
构建与运行分离:模板构建是一次性动作,构建产出的
template_id应持久化保存并复用;不要在每次创建 Sandbox 前重复构建。 -
镜像版本化:镜像使用明确的 tag(避免只用
latest),做到模板与镜像版本对齐、可回溯、可回滚。 -
资源规格 :
cpu(核数)、memory_size(MB)、disk_size按镜像内工作负载评估,缺省时使用默认规格。 -
及时销毁:任务结束后调用
sandbox.kill()(放在finally中),避免异常路径下 Sandbox 长期占用;模板确认不再使用时调用DeleteTemplate删除。