命令与终端
在 Workspace 里执行一条命令并取回输出,或者在 guest 里打开交互式终端。与 node 的连接断开时,两者都会自动续上。示例使用 Go SDK、E2B SDK 与 kibo。直接调用 ProcessService 的程序见用 API。
执行命令
在 guest 里运行一条命令,接收它的标准输出与标准错误,并等它结束。下面的例子列出 Volume 里的内容。Go 示例使用连接 ctrl 里的 client:
session, err := dataplane.Open(client, workspaceID, dataplane.WithScopes(
ctrlv1.DataPlaneScope_DATA_PLANE_SCOPE_PROCESS,
))
if err != nil {
log.Fatal(err)
}
defer session.Close()
stream, err := session.StartProcess(ctx, &ctrlv1.StartRequest{
Argv: []string{"sh", "-c", "ls -la"},
WorkingDirectory: "/data",
Mode: &ctrlv1.StartRequest_Pipe{Pipe: &ctrlv1.ProcessPipeMode{}},
})
if err != nil {
log.Fatal(err)
}
defer stream.Close()
for stream.Receive() {
event := stream.Event()
switch {
case event.Output != nil:
os.Stdout.Write(event.Output.GetData())
case event.Exited != nil:
fmt.Println("exit code", event.Exited.GetExitCode())
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}用 Go SDK 与 kibo 时,命令不经过 shell 解释。需要管道、通配符这类 shell 语法时,写 sh -c '...'。E2B SDK 接收一个命令字符串,并在 shell 里运行它。
命令以非零退出码结束时,E2B SDK 抛出 CommandExitError,其中带有 exitCode。
kibo workspace exec 的选项:
-w(--working-directory)与--user指定命令在哪个目录、以哪个账号运行,见执行账号与工作目录。--exec-timeout超过给定时长就终止命令,默认不限时。- 全部参数见 kibo workspace exec。
-i 把本地标准输入转发给远端进程,读到结尾时关闭远端的标准输入。下面的例子把本地目录复制到 Volume 里:
tar czf - src | kibo workspace exec -i "$workspace_id" -- tar xzf - -C /data退出码
kibo workspace exec 的退出码就是远端进程的退出码,与 ssh 一致。进程被信号终止时是 128 加信号编号,被 --exec-timeout 终止时是 124。
kibo 自身在命令开始前或跟随过程中失败时,以 255 退出,错误输出写明原因。见 kibo 退出码。
打开终端
kibo workspace terminal 在 guest 里打开一个交互式终端:
kibo workspace terminal "$workspace_id"要在终端里运行指定的程序,写在 -- 之后:
kibo workspace terminal "$workspace_id" -- /bin/bash -l- 本地的标准输入与标准输出都必须是终端。
- 初始大小取本地终端的。要自己指定时,
--rows与--columns一起传。 --working-directory与--user的规则与exec相同,见执行账号与工作目录。- 退出码与
exec相同。
断线续传
与 node 的连接断开时,exec 与 terminal 自动续上,不会重新运行命令。
- 断开期间的输出会补上。
- 没送达的输入按位置重发,不会重复。
- 最多重试 30 秒,续不上时以
255退出。
丢失的输出
guest 保留最近的输出,供续上的命令补收:
- 每个进程的每个输出通道保留最近 1 MiB。
- guest 里全部进程合计最多保留 16 MiB 与 guest 内存 1/16 中较小者,但不少于 1 MiB。
- 超出合计上限时,guest 整块释放其他进程的缓冲:先释放已退出的进程,再释放最近一次输出最早的运行中进程。
续上时已经不在保留范围内的输出补不回来:
| 命令 | 结果 |
|---|---|
exec | 在标准错误写明哪个通道丢了多少字节,继续接收到远端进程结束,然后以 255 退出。错误信息里带远端的退出状态。--json 输出里,丢失是一条 "type":"gap" 记录 |
terminal | 在终端上提示后继续,退出码仍是远端的 |
执行账号与工作目录
每个请求都可以指定在 guest 里以哪个账号执行,进程请求还可以指定工作目录。读写文件时按同样的规则选择账号。
| 入口 | 执行账号 | 工作目录 |
|---|---|---|
kibo workspace exec | --user | -w、--working-directory |
kibo workspace terminal | --user | --working-directory |
kibo workspace file 各子命令 | --user | 不适用 |
ProcessService/Start | user | working_directory |
FilesystemService 各请求 | user | 不适用 |
/v1/files | 查询参数 user | 不适用 |
选择执行账号
- 不指定账号时,使用 Template 声明的账号;Template 没有声明时是 root。
root明确使用 root。- 其他账号名由 node 按 Template 的声明校验。
- Template 声明的账号在 guest 里不可用时,见默认账号不可用。
工作目录与相对路径
- 不指定工作目录时,进程在执行账号的 home 目录启动,例如
/home/user或/root。用环境变量设置HOME,无论是-e还是ProcessService/Start的environment,都不改变这个目录。 - 工作目录不存在时,请求返回
not_found。 - 工作目录与文件路径可以写相对路径,按执行账号的 home 目录解析:账号是
user时,notes/a.txt指/home/user/notes/a.txt。 - 响应里的路径都是解析后的绝对路径。
- 相对路径要求 guest 为
v0.3.0或更新版本。guest 更早时,文件请求只接受绝对路径,相对的工作目录也不按 home 解析。重启 Workspace 后可用相对路径。
重要
home 目录不是承诺保留数据的路径。需要在 Volume 里执行时,传 -w /data。见保证与边界。
用 API
程序通过数据面的 ProcessService 运行进程,需要 PROCESS 范围的数据面凭证,见申请会话凭证。kibo 自己申请所需的凭证。全部方法与消息见 ProcessService。
启动进程
ProcessService/Start 启动进程并返回它的输出流。进程不随 Start 或 Attach 的流结束:流断开后进程照常运行,调用方用 Attach 重新订阅。
进程退出后,对它的 SendInput、CloseStdin、SendSignal 与 Resize 返回 failed_precondition,并且:
ErrorDetail.reason为CONFLICTstage为USING_PROCESS- 不带
input_offset
进程的结果照常从输出流的 exited 送达。以它为准,不要把这个错误当作会话失败。
进程流续传
重新 Attach 就能补上断开期间、guest 仍保留着的输出,输入也可以安全地重发。保留多少输出见丢失的输出。kibo 与 E2B 网关会自动这样做,见断线续传与 E2B 兼容。
续传要求 guest 为 v0.2.0 或更新版本。guest 更早时,Attach 忽略 resume_from,订阅从当前位置开始,start 等于 offset;带 input_offset 的 SendInput 返回 unimplemented(UNSUPPORTED)。重启 Workspace 后可以续传。
续传的步骤:
- 记下每个通道已经收到的位置:
ProcessOutputEvent的offset加上data的长度。 - 流在
attached之后以unavailable或连接中断结束时,用同一个 PID 与incarnation调Attach,在resume_from里给出每个通道的位置。 - 新的
attached在每个通道上给出start:补发从这里开始,之后是实时输出,同一通道里连续。start大于请求的位置时,中间的字节已经不在缓冲里,差值就是丢失的字节数。 SendInput带上input_offset,即这段输入在进程输入里的起始位置。重发时进程已经接受的部分被跳过,不会写两次。
输入位置:
- 成功的
SendInput响应里的input_offset是进程至此已接受的输入字节数,也就是下一段输入的位置。 attached里的input_offset是续传那一刻的值。- 请求里的
input_offset大于进程已接受的字节数时,返回failed_precondition(CONFLICT),ErrorDetail.input_offset是已接受数,什么都不写。
其他规则:
resume_from的位置超过通道已产生的字节数时,Attach返回invalid_argument,原来的订阅不受影响。- 进程退出后,在结果保留期内照样可以续传:先补发输出,再收到
exited。 - 进程的缓冲为守住 guest 合计上限而被提前释放时,续传的
start是释放时的位置。 - 续传时凭证已经过期的,重新申请再续。
- 有的客户端库把 node 换用新数据面进程时的连接关闭报成协议错误。同样按连接中断续传,见流的中断。
排查
默认账号不可用
- 现象:Workspace 照常启动,但不指定
user或指定 Template 声明的账号的请求返回failed_precondition,ErrorDetail.reason为CONFLICT,不可重试,message注明账号与原因。桌面也不可用,root照常可用。 - 原因:Template 声明的账号在 guest 里不存在、uid 或 gid 为 0、或没有 home 目录,通常是在 guest 里删除或改名了它。
- 处理:以
root恢复账号,然后重启 Workspace,默认账号与桌面随之恢复。