创建沙箱
沙箱可以运行命令时返回,status 为 running。如果节点还没有构建过该镜像,则立即返回,status 为 starting;需要该沙箱的调用会等待它,如果镜像无法拉取,沙箱会变为 failed。
所需权限范围:write。
https://api.glac.ai/v1/sandboxes授权
请求头参数
请求体JSON可选
- startoptionalboolean
true:虚拟机启动时运行镜像自己的启动命令(ENTRYPOINT 和 CMD);false:不启动任何东西,无论镜像的标签要求什么。只要给出了(start、start_command和ready中的任何一个),就只以它为准:不读取镜像的标签sbx.start和sbx.ready。沙箱从用这些设置和env构建的模板创建(同一镜像的两种设置就是两个模板;某种设置的第一个沙箱在构建期间以starting返回),只有其所有者的沙箱会从这个模板创建。不适用于从snapshot或template创建的沙箱 (400invalid_start)。 - start_commandoptional
启动命令,以镜像的用户身份运行,其输出由沙箱的代理程序作为命令
exe-start的输出保留(最后 16 MiB,像任何命令一样用GET …/exec/exe-start和…/exec/exe-start/output读取;对于应用的实例,这也是应用日志中该实例的内容):字符串由/bin/sh -c运行,可以有多行;列表按原样运行,不经过 shell(适用于没有 shell 的镜像)。最多 1000 个参数、64 KiB,不能含 NUL 字节,第一个参数不能为空。与start: false一起给出时返回 400invalid_start。 - readyoptional
沙箱模板的虚拟机何时算作已启动:只有这项检查通过后,节点才会保存模板(使用这些设置的每个沙箱都从它克隆),这样每个沙箱中的服务从一开始就在运行。三者选一。
tcp和http由节点在宿主机上,通过预览 URL 访问沙箱所用的地址进行尝试:不需要在沙箱中运行任何东西,因此没有 shell 的镜像也能检查,但服务必须监听所有地址,而不只是 127.0.0.1。command以镜像的用户身份在沙箱中由sh运行,与标签sbx.ready一样。每 0.2 秒尝试一次,最多 120 秒;在此期间启动命令以失败结束的,等待立即结束。无论哪种情况,沙箱都会以failure_codeboot_failed失败,failure会给出检查最后得到的结果以及启动命令最后打印的内容。需要start: true或start_command(400invalid_start)。不给出时:不等待,直接保存模板。 - initoptional
沙箱的 PID 1 是什么。不给出或为 null:平台的 init,它会启动镜像的启动命令(如果有的话:
start、start_command、镜像的标签)。systemd:完整的系统。镜像自己的 systemd 作为 PID 1,节点的代理程序是它的一个服务:用户可以安装软件包、用systemctl启动服务、使用 cron 和 journal,并通过 SSH 登录,就像在一台机器上一样。创建请求指定的任何东西都不会被启动(start、start_command和ready会被拒绝,返回 400invalid_start),在 systemd 报告机器已启动后保存模板。不带image时:使用节点的完整系统镜像(--systemd-image;带 systemd 和 OpenSSH 的 Ubuntu 24.04,sandboxd/images/ubuntu),没有这种镜像的节点会拒绝,返回 400invalid_start。没有 systemd (/usr/lib/systemd/systemd) 的镜像会构建失败,failure_code为invalid_image。其模板对以同样方式请求同一镜像的任何人都可用,除非其中有调用方自己的东西(runtime、image_pull_secret)。从快照或模板创建的沙箱会被拒绝 (400invalid_start),它们沿用其虚拟机原来的样子。沙箱的其他一切功能都与任何沙箱一样:命令、文件、终端、SSH 网关、休眠和唤醒、快照和分叉、迁移、出站规则(sandboxd/README.md,「整个系统:systemd 当 PID 1」)。 - image_pull_secretoptionalstring
保存非公开镜像所在仓库凭据的机密 (
PUT /v1/secrets/{name}) 的名称:可打印 ASCII 格式的username:password,用户名到第一个冒号为止。只接受令牌的仓库会说明与令牌搭配的用户名(ECR 为AWS,Google 的为oauth2accesstoken,GHCR 任何名称均可)。节点只把凭据交给拉取镜像的工具:沙箱、其模板和节点日志中都不会有凭据的任何内容,仓库拒绝凭据时给出的说明也不含凭据(failure_code为registry_auth_failed)。沙箱从只属于其所有者的模板创建,只有指定了凭据的创建请求才会找到这个模板:不带凭据指定同一镜像时,无论节点有没有该模板,都会不登录直接向仓库请求镜像。该机密必须存在 (404secret_not_found),必须允许发往镜像所在的仓库(其allowed_hosts涵盖镜像引用中写的仓库主机,不含端口,没有指定时为docker.io;否则返回 403secret_host_not_allowed),且必须保存这样一对凭据 (400invalid_field),还必须与image一起使用(沙箱不是从镜像创建时返回 400invalid_field)。maxLength: 128 - limitsoptionalobject
创建请求所要求的规格。没有给出(或为 null)的每一项,都取节点基础模板的值,也就是不指定规格的沙箱得到的规格。最多为本节点给沙箱的上限,即
GET /internal/node的max_vcpus和max_memory_bytes(默认为宿主机的 CPU 数,最多 32,以及其内存;从不小于基础模板的规格);超出返回 400invalid_field。同一镜像的每种规格都是一个独立的模板:节点还没有用该镜像构建过某种规格时,这种规格的第一个沙箱在节点用同一镜像构建模板期间以starting返回,之后的沙箱从它克隆。不带image时,基础模板以外的规格从节点的基础镜像构建。从snapshot或template创建的沙箱沿用其规格:请求相同的规格没问题,请求其他规格返回 400invalid_field(param 为limits)。磁盘大小由节点决定:给出disk_bytes返回 400invalid_field。 - secret_envoptional映射,值为string
更多的环境变量,其值为机密:沙箱中启动的每个命令都会和
env一起得到它们,启动命令也一样(在写env的地方为它写入,别处都不写:模板的键和记录中都没有它们的值)。任何响应都不显示值:沙箱只显示其名称 (Sandbox.secret_env)。最多 64 个变量、64 KiB,名称规则与env相同,其中任何一个都不能同时出现在env中(400invalid_field,param为secret_env.<name>);在创建沙箱时确定,之后不能修改(如果 PATCH 在env中给出其中某个名称,也会同样被拒绝,param为env.<name>)。快照中不包含它们:从快照创建的沙箱使用它自己的创建请求给出的那些。 - awake_portsoptionalarray | null
通过预览 URL 升级的连接(WebSocket:浏览器的 DevTools 客户端在 9222 端口,屏幕的观看者在 6080 端口)在这些端口上只要打开,就让沙箱保持运行并为此计费。只要这里指定了端口,沙箱是否空闲就只取决于这些连接以及交给它的请求和调用,与客户机做什么无关:其 CPU、定时器、连往任何地方的连接及其流量都不会让它保持运行,它也不会为定时器而被唤醒(期间到期的定时器会在下一个连接唤醒它时触发)。最后一个这样的连接结束
sleep_after_seconds秒后,它就进入休眠,不会询问其客户机。这类连接在客户端一侧启用了 TCP keepalive,因此没有关闭连接就离开的客户端会在约一分钟内被发现。null 或[](默认):与任何沙箱一样判断空闲。按顺序保存,去掉重复项。不适用于一次性运行 (POST /v1/run)。浏览器会话 (reports/browsers/README.md) 创建时为[9222]。 - held_asleepoptionalboolean | null
true:休眠期间,发往其端口的请求不会唤醒它:预览监听器会代为返回 502
sandbox_paused,与 E2B 的代理对已暂停沙箱的响应一样 (x-preview-ingress),只有 API 调用(或它自己的定时器)才会唤醒它。它本身不会让沙箱休眠:那是POST /v1/sandboxes/{id}/sleep的事。false 或 null:与任何沙箱一样。(相当于 E2B 不带autoResume的pause,由控制平面的 E2B 兼容层设置,在其connect时移除。)default: false - sleep_hold_max_msoptionalinteger | null
客户机中的程序发起的保持(在
/run/sbx/awake上发送+)最多能让沙箱保持运行多久,单位为毫秒,从第一次发现它被保持、且没有别的东西让它保持运行的那一刻起算;到时无论有什么保持,它都会休眠(如果sleep_notice_ms要求通知,会先向已连接的程序发送sleep N cap),下一次安静时重新开始计算。保持期间它在运行,并按运行计量。0:不理会保持。null 或不给出:节点的设置(--sleep-hold-max-seconds,未设置时为 30 秒)。maximum: 900000 · minimum: 0 - suspend_after_msoptionalinteger | null
空闲规则停止沙箱后,暂停层级(
GET /internal/node的pause_tier)让它保持暂停多久,才保存其内存并真正休眠,单位为毫秒。暂停时,它在一两毫秒内响应,按其占用的内存计量;休眠时,按其在磁盘上保存的数据计量。无论这里怎么设置,节点的--pause-max-seconds(未设置时为 600)也是保持暂停的最长时间:暂停的虚拟机会占用宿主机的内存。宿主机内存低于下限、其他虚拟机需要占用槽位,或者有人请求休眠,都会让它更早休眠。null 或不给出:节点的设置。节点没有暂停层级时不起作用。maximum: 86400000 · minimum: 1000 - wake_daily_atoptionalstring | null
每天的某个 UTC 时刻 (
HH:MM),沙箱休眠或暂停期间,节点每天此时唤醒它,让它完成等待唤醒、自己不计时的工作(比如数据库维护)。唤醒本身不算活动:唤醒后运行的东西才算,运行完毕后空闲规则会再次让它休眠。不适用于按连接判断空闲的沙箱 (awake_ports)。此外,有状态服务的实例(其启动命令为等待存储卷而推迟)还会为其 PostgreSQL 服务器pg_cron任务的下一次运行而被唤醒,节点在让它休眠之前会向其代理程序询问这一时间;有任务在运行,或在 5 秒内到期时,空闲规则不会让它休眠。null 或不给出:不唤醒。pattern: ^([01][0-9]|2[0-3]):[0-5][0-9]$ - checkpointoptional
沙箱运行期间的检查点:每隔
every_seconds秒,把沙箱在某一时刻的内存和磁盘送到节点的对象存储(为此节点会把其虚拟机暂停几毫秒;发送的是自上一个检查点以来写入的内容),这样在节点的宿主机丢失时,沙箱会在别处连同进程一起从最新的检查点接管,最多丢失此后做的事。只有上一个检查点已进入对象存储,才会开始下一个:写入速度超过节点检查点链路承载能力的沙箱会落后 (checkpoint.behind_seconds),而不是把链路占满。休眠时,从其休眠镜像接管。null(默认):不启用。任何沙箱都可以启用,在创建时给出或通过PATCH设置(控制平面也会为checkpoint要求启用的应用的键控实例启用);对象存储为其最新检查点保存的数据,在沙箱运行期间按休眠沙箱的状态计费 (checkpoint.stored_bytes)。不把磁盘复制到对象存储 (--upload) 的节点,或内存不分层的节点,会拒绝启用(409checkpoints_unavailable,param为checkpoint),控制平面只会在支持检查点的节点上创建启用了检查点的沙箱。其云盘会与它自己的磁盘在同一时刻固定下来,从检查点接管时会连同内存一起恢复到那一刻的状态;挂载了非云盘的存储卷时(这种存储卷在对象存储中没有副本),或者客户机在其虚拟机的某块磁盘上有一次读取被卡住时,不会创建检查点。检查点不会重置客户机的 vsock 连接(代理程序的调用照常进行),如果对象存储中已有其休眠镜像,唤醒后的第一个检查点只发送客户机此后写入的内容。
Returns
201
沙箱。
- image_digeststring | nullrequired
其磁盘所来自的镜像,按其清单(或清单列表)的摘要,与构建其模板时镜像仓库给出的一致 (
Template.image_digest):即当时标签所指的镜像。之后移动标签,会让之后创建的沙箱使用另一个镜像(一个更新的模板),而这个沙箱的保持不变。在其模板构建期间 (starting):构建所用的镜像,如果创建时已知(发现标签指向比那里的模板更新的镜像,就按那个摘要构建),否则为 null;模板没有说明时(从磁盘文件构建的,或在模板记录这一信息之前构建的)也为 null。pattern: ^[A-Za-z0-9]+:[0-9A-Fa-f]{32,}$ - sleep_tierrequired
sleeping状态的沙箱以何种方式休眠;其他状态为 null。paused:其虚拟机连同进程和连接静止在节点的内存中(开启了暂停层级的节点会暂停空闲的沙箱,而不是保存它),调用会在约一毫秒内恢复它;其虚拟机占用的内存像运行中的沙箱一样计量(usage.memory_bytes、memory_gib_seconds),CPU 不计量。saved:其内存和设备状态保存在节点上,什么都没有在运行;调用会在几毫秒内从中唤醒它。disk:只剩下磁盘(其虚拟机自行结束了,节点守护进程没有让它休眠就结束了,或者其内存无法保留);下一个需要它的调用会从磁盘启动它,原来运行的东西都已不在。POST /v1/sandboxes/{id}/sleep会保存沙箱,无论它是暂停的还是在运行的。 - failure_coderequired
沙箱为何处于
failed,供程序使用;其他情况为 null。image_not_found:镜像不在其仓库中,或者不是公开的(对于没有指定凭据的创建请求,需要登录的仓库只会这样说)。image_pull_failed:连不上镜像仓库,或者仓库没有交出镜像,或者拉取工具因节点的某项限制被停止(failure会说明是哪一项)。registry_auth_failed:镜像仓库拒绝了创建请求指定的凭据 (image_pull_secret);failure中有仓库的说明。image_unpack_failed:镜像放不进磁盘、超过节点接受的大小(failure中有具体数字),或者无法解压。invalid_image:镜像仓库位于节点上或私有网络中的地址,或者无法按要求启动镜像(start: true却没有 ENTRYPOINT 或 CMD,或者标签要求了无法做到的事)。boot_failed:用镜像创建的虚拟机没有启动,或者其启动命令没能启动(此时failure会给出就绪检查得到的结果以及它最后打印的内容)。no_capacity:节点没有空间。memory_full:宿主机的内存没有空间容纳构建其模板所用的虚拟机,或容纳它自己的虚拟机。drive_full:构建镜像模板期间,节点的数据盘满了。record_lost:无法读取沙箱的记录;其文件保留在节点上,但它无法启动,只能删除。recovery_failed:沙箱的启动恢复无法完成;其文件和恢复意图保留在节点上,它无法启动,只能删除。start_failed:其他任何情况;节点日志中有记录。 - egressobjectrequired
沙箱存储下来的出站规则:它可以访问什么、可以向某个主机请求什么,以及它的哪些请求会被加上机密。总是存在;没有规则的沙箱,
default为allow,四个列表为空,其流量走的是没有规则时的路径。deny涉及的连接或数据包会被拒绝;allow涉及的会放行;其他的按default处理。inject中的主机视为允许,http中的主机对其条目允许的请求也视为允许。无论规则怎么说,沙箱都访问不到节点、其他沙箱、私有地址段以及各云平台的元数据地址。主机名会与 TLS 握手的服务器名称、HTTP 请求的 Host 头匹配(任何端口),也会与沙箱的 DNS 查询匹配;对于规则可以拒绝某个名称的沙箱,节点会自己回答其 DNS 查询(被拒绝的名称得到
REFUSED)。指明某个允许主机的连接,会发往节点为该名称解析出的地址,无论沙箱原本要连哪个地址。既不是 TLS 也不是 HTTP 的 TCP 连接,只有发往规则列出的地址,或节点作为某个允许名称的地址告诉沙箱的地址时,才会放行。UDP 和其他所有协议只按地址判断。在default: allow下,deny列表算不上一堵墙:任何允许的中转流量都能绕过它。 - delete_after_idle_msinteger | nullrequired
休眠多久后节点删除该沙箱,单位为毫秒,从
idle_since起算:被唤醒的沙箱从下一次休眠起重新计算。null:不会因此删除(默认);沙箱会一直保留,直到被删除或其expires_at到期。不会自行休眠的沙箱(sleep_after_seconds为 null)计算的是被让它进入的休眠(POST /v1/sandboxes/{id}/sleep、其节点重启)。冻结期间(其所有者的支出上限)不计算:冻结会连同数据保留它,解冻后,其当前的休眠从那时开始计算。format: int64 · minimum: 1000 - awake_becausestring | nullrequired
运行中的沙箱为什么还没有被休眠,用几个词说明。
status不是running时为 null。应用的实例给出节点提供的原因:手头有请求、有调用在进行中、有请求在不到其sleep_after_seconds之前刚结束,或者其应用的下限让它保持运行。被项目网络上的数据包唤醒、之后再没有别的东西让它保持运行的沙箱,会说明这一点以及是哪个数据包:woken by a packet: TCP from 10.64.0.7:41594 to port 5432 [S] …。项目内部的连接不会让任何沙箱保持运行:在它休眠期间经由这些连接到来的数据,由节点暂存 (Node.hold)。 - url_patternoptionalstring | null
端口的预览 URL,其中的
{port}和{signature}需要填入:签名为 base32(HMAC-SHA256(key =url_secret, message = port)) 的前 12 个字符(port 为十进制的端口号),使用字母表0123456789abcdefghjkmnpqrstvwxyz。对于公开端口(public_ports中的端口,或者给出public_ports_except时其中没有指定的端口),去掉-{signature}。节点没有预览域名时为 null;调用方是沙箱令牌时没有这个字段。
400
请求不符合本调用的要求。invalid_json:请求体不是 JSON 对象;unknown_field:请求体中有本调用不接受的字段;unknown_parameter:查询中有本调用不接受的参数;invalid_field:某个字段或查询参数类型错误或超出范围;invalid_name:name 不是有效的名称;invalid_env:env 中有环境变量无法容纳的名称或值;invalid_metadata:metadata 超出限制;expires_in_the_past:expires_at 不是将来的时间;invalid_egress:egress 不是有效的规则集:param 指明出错的部分;invalid_start:给出的 start、start_command 和 ready 不能这样搭配(有 ready 却没有启动命令,start: false 却带了 start_command),或者其中某一项与 init: "systemd" 一起给出(此时由 systemd 按其单元启动程序),或者 init: "systemd" 没有指定 image,而节点上没有完整系统的镜像 (--systemd-image),或者沙箱是从 snapshot 或 template 创建的:param 指明是哪个字段;ambiguous_source:image、snapshot 和 template 给出了不止一个;invalid_image:image 不是指向互联网上镜像仓库中某个镜像的引用;invalid_path:某个存储卷的 path 不是绝对路径,或者是 /;invalid_idempotency_key:Idempotency-Key 为空或超过 255 个字符。
401
missing_api_key, invalid_api_key, expired_api_key, invalid_token.
403
凭据无权执行此操作。needs_api_key:沙箱令牌不能执行此操作。secret_host_not_allowed:机密会被发往它不允许去的主机:通过 egress.inject(给出的规则,或快照中的规则),或作为 image_pull_secret 发往 image 的镜像仓库(param 指明是哪一个;与 error 并列的 allowed_hosts 列出它可以去的地方)。account_share_reached:账户在本节点数据盘上占用的空间已达到单个账户的上限(见 GET /internal/node 的 drive.accounts),放不下沙箱的内存外加 1 GB 的写入空间;错误信息会说明它在这里已占用多少、上限是多少。没有创建任何东西。
404
请求中指定的对象不存在。snapshot_not_found:快照不存在;template_not_found:模板不存在;volume_not_found:volumes 中指定的某个存储卷不存在;secret_not_found:egress.inject 中指定的机密,或 image_pull_secret 指定的机密不存在。
409
某个对象的状态不允许此操作。name_taken:名称已被占用;volume_in_use:volumes 中某个非共享存储卷属于另一个沙箱;duplicate_volume:某个存储卷在 volumes 中出现了两次;mount_path_taken:volumes 中有两个存储卷的 path 相同;too_many_volumes:volumes 中非共享存储卷的数量超过了沙箱所在机器的空余磁盘数(八个;从早于八个磁盘时期的模板或快照创建的沙箱为两个);snapshot_lost:快照的 status 为 lost;no_project_address:请求了 project_network,但节点上没有该项目的网络,或者网络中已没有空余地址,或者 project_address 指定的地址在此期间被另一个沙箱占用了;sandbox_failed:沙箱的模板推迟了启动命令 (TemplateBuildRequest.start_after_volumes),而存储卷挂载后命令没能启动:错误信息会给出它最后打印的内容,不保留任何东西;idempotency_key_reused:该键已与另一个请求体一起使用过;idempotency_in_progress:使用该键的第一个请求仍在执行。volume_restoring:它指定的某个存储卷正在从备份创建,或者没能创建成功。checkpoints_unavailable(param 为 checkpoint):开启了 checkpoint,但本节点不支持检查点(它不把磁盘复制到对象存储,或者它的内存不分层)。
503
不可用。not_served_here:给出了 egress 规则,但本节点无法执行规则(它没有 nft);或者请求了 project_network,但本节点不把沙箱放进项目网络;或者沙箱要从节点没有模板的 image 创建,而本节点不拉取镜像(原因见 GET /internal/node 的 image_pulls;param 为 image);或者 key_missing:本节点启用了加密 (src/seal.rs),却没有所属项目的密钥或平台的密钥(错误信息会说明是哪一个):控制平面会提供密钥,之后再次发起同一调用即可 (Retry-After);或者 unavailable:volumes 中的某个存储卷没能及时挂载,稍后再次发起同一调用即可 (Retry-After):客户机没能在 60 秒内完成挂载(云盘的数据块迟迟不来:对象存储不在线,或取块程序连不上),或者这样的挂载之后它仍在从机器上卸下,或者它是逐块传来的云盘,而它的数据块暂时没有任何来源响应。
default
其他任何失败,格式相同。internal (500):节点出错;错误信息不会多说,节点日志中以请求的 id 记录了原因。unavailable(503,可重试):no_capacity,节点的网络槽位都已占用;memory_full,调用需要启动的虚拟机(创建、唤醒、从磁盘启动、恢复、回滚、构建)在宿主机内存中放不下(还要算上已承诺给其上运行的虚拟机的内存),也没有暂停或空闲的虚拟机可以让其休眠来腾出空间(节点的 memory);drive_full,调用需要构建模板,而节点的数据盘没有空间(节点的 drive);unavailable,节点正在关闭、沙箱 10 分钟后仍处于 starting、沙箱没有响应,或者沙箱正在迁到本节点(带 Retry-After: 1);fenced,节点的租约隔离了该沙箱 (Node.lease)。moved (409):沙箱已迁到另一个节点,由 to 指明。