Kibotos

命令与终端

在 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/Startuserworking_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 为 CONFLICT
  • stage 为 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 后可以续传。

续传的步骤:

  1. 记下每个通道已经收到的位置:ProcessOutputEvent 的 offset 加上 data 的长度。
  2. 流在 attached 之后以 unavailable 或连接中断结束时,用同一个 PID 与 incarnation 调 Attach,在 resume_from 里给出每个通道的位置。
  3. 新的 attached 在每个通道上给出 start:补发从这里开始,之后是实时输出,同一通道里连续。start 大于请求的位置时,中间的字节已经不在缓冲里,差值就是丢失的字节数。
  4. 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,默认账号与桌面随之恢复。

下一步

  • 文件:上传、下载 guest 里的文件,监视目录变化。
  • 服务与端口:打开 Web 服务、转发端口。
  • 桌面:查看和操作图形桌面。
  • API 概览:自己的程序需要的凭证与约定。

本页内容