Kibotos

Go SDK

Go SDK 让 Go 程序调用 Kibotos。本页介绍安装、用 Operator 身份连接 ctrl、写操作的约定,以及访问 Workspace 里的文件、进程与服务。

SDK 调用的就是 API 概览介绍的原生 API。各服务做什么看 API 的各页,本页只讲 SDK 替你做了什么。

SDK 替你做的事

不用 SDK 时要自己写用 SDK
只信任 server CA 的 mTLS、TLS 1.3、HTTP/2、不跟随重定向、正向代理kibotos.Dial
续期 30 天的证书,并把请求换到出示新证书的连接上在后台运行,直到 Close
幂等键(command ID),以及没收到结果时重发命令NewCommandID、DeriveCommandID、Settle
使用新字段前核对能力名Client.Require、Client.Has
轮询 Workspace 命令直到结束Client.WaitWorkspace
签发、缓存、续期与更换数据面凭证dataplane.Session
带完整性校验的 /v1/files 传输、可续传的进程流、服务连接dataplane.Session 的方法

表中每一行遵循原生 API 各页的规则:连接与证书见认证,幂等键与能力见写操作与错误,会话凭证见数据面。其他语言的程序直接按这些页面实现。

安装

SDK 是 Go 模块 github.com/felinics/kibotos/sdk/go。每个 Kibotos 版本都在同一提交上打 sdk/go/vX.Y.Z,所以某个版本的 SDK 版本号与该版本相同。第一个带 SDK 的版本是 v0.3.0:

go get github.com/felinics/kibotos/sdk/go@v0.3.0

模块要求 Go 1.27 或更新版本。它只给你的构建带来 connect-go、protobuf、github.com/coder/websocket 与 golang.org/x/net。

说明

Kibotos 仓库尚未公开。在公开之前,go get 需要仓库的读权限,并且不能经过公共模块代理:

  1. 向 Kibotos 维护者申请 felinics/kibotos 的读权限。

  2. 让 Go 直接拉取这个模块。GOPRIVATE 同时关闭它的公共校验和数据库:

    go env -w GOPRIVATE=github.com/felinics/kibotos
  3. 为 GitHub 配置 Git 凭证,两种方式任选其一:

    • HTTPS 加令牌,例如通过 GitHub CLI:

      gh auth setup-git
    • SSH,把 GitHub 地址改写为 SSH:

      git config --global url."git@github.com:".insteadOf "https://github.com/"

在 CI 里同样设置 GOPRIVATE,并提供能读取这个仓库的令牌或部署密钥。没有凭证时,go get 报 could not read Username for 'https://github.com'。

连接 ctrl

kibotos.Dial 用身份存储里的 Operator 身份连接 ctrl。身份存储保存客户端证书、私钥、签发它的 control-client CA 与 server CA:

store, err := managedidentity.Open("/var/lib/example/kibotos-identity")
if err != nil {
	log.Fatal(err)
}
client, err := kibotos.Dial(ctx, "https://ctrl.example.com:18092", store,
	kibotos.WithEnrollmentKey(os.Getenv("KIBOTOS_ENROLLMENT_KEY")))
if err != nil {
	log.Fatal(err)
}
defer client.Close()
  • 空的身份存储先注册。 给出 WithEnrollmentKey 时,空存储先用这把密钥注册。密钥用 kibo enrollment create --operator 生成,见 身份与注册。已有身份的存储不会使用密钥,所以密钥用掉之后可以继续留在配置里。
  • managedidentity.Open 使用 kibo context 目录的布局。 用 kibo context enroll 注册的目录可以直接用。目录不能开放给属组或其他用户;不存在时会按正确的权限创建。每个程序用各自的目录:ctrl 只接受一个身份的当前与上一张证书,两个程序续期同一个身份会互相把对方挤掉。
  • 证书在后台续期。 进入续期窗口(30 天有效期的最后 10 天)后,客户端在自己的连接上续期,先把新证书写入存储,再让新请求走出示新证书的连接。在途请求照常在原连接上结束。
  • 续期过程写日志,用 slog.Default() 或 WithLogger 给出的 logger。要自己处理这些事件,传 WithRenewalObserver。
  • 正向代理遵循 HTTPS_PROXY、ALL_PROXY 与 NO_PROXY。WithProxy 显式指定一个;WithProxy("") 表示直连。

把身份存在别处

自己保管密钥的程序,可以在自己的存储上实现 kibotos.IdentityStore:Exists、Load 与 Publish。

Publish 必须在返回前把续期后的身份持久化。重启后丢失的续期结果会让程序只剩一张 30 天内到期的证书。

测试时,或要加载以 PEM 给出的身份时,用 managedidentity.ParsePEM 与 managedidentity.NewMemoryFrom。在返回的存储上设置 OnPublish 来持久化续期结果。

调用服务

客户端返回各服务的生成 Connect 客户端,例如 client.Workspaces()、client.Volumes()、client.Egresses(),Operator API 用 client.Operator().NodeRegistration() 这样的方法。所有调用都走客户端的 mTLS 连接。

写操作

commandID := kibotos.DeriveCommandID("example", "create-workspace", "order-1234")
created, err := client.Workspaces().CreateWorkspace(ctx, connect.NewRequest(&ctrlv1.CreateWorkspaceRequest{
	Metadata: &ctrlv1.CommandMetadata{CommandId: commandID},
	Spec: &ctrlv1.WorkspaceCreateSpec{
		Template:  &ctrlv1.TemplateSelector{TemplateId: "coding"},
		Resources: &ctrlv1.ResourceRequest{CpuMillis: 1000, MemoryBytes: 512 << 20},
	},
	Labels: map[string]string{"example.com/order": "1234"},
}))
if err != nil {
	log.Fatal(err)
}
workspace, err := client.WaitWorkspace(ctx, created.Msg.GetWorkspace(), commandID)
if err != nil {
	log.Fatalf("create failed: %v (reason %s)", err, kibotos.Reason(err))
}
函数用途
NewCommandID(prefix)随机幂等键,例如 example-3f9c…
DeriveCommandID(prefix, parts...)从程序已有的键(如订单号)推导幂等键。重启后同一个键仍对应同一条命令,ctrl 会重放它,而不是再建一个 Workspace
Settle(ctx, options, send)ctrl 回答仍在执行或没有回答时,以同一 ID 重发命令。已定的失败直接返回,同一个 ID 只会回放它。options.Executing 取代默认的判断 StillExecuting
Client.WaitWorkspace等待 Workspace 命令,直到 Workspace 可用。命令失败时返回 ctrl 记录的错误
Client.Require(ctx, names...)ctrl 没有宣告所需能力时,让这一个操作失败。老版本的 ctrl 会静默丢掉它不认识的字段
Detail(err)、Reason(err)ctrl 在拒绝里附带的 ErrorDetail

幂等键与版本校验的规则见写操作与错误。

数据面

dataplane.Open 为一个 Workspace 准备会话,第一次需要凭证的调用才向 ctrl 申请:

session, err := dataplane.Open(client, workspace.GetWorkspaceId(), dataplane.WithScopes(
	ctrlv1.DataPlaneScope_DATA_PLANE_SCOPE_FILE_READ,
	ctrlv1.DataPlaneScope_DATA_PLANE_SCOPE_FILE_WRITE,
	ctrlv1.DataPlaneScope_DATA_PLANE_SCOPE_PROCESS,
))
if err != nil {
	log.Fatal(err)
}
defer session.Close()

content := "print('hello')\n"
if _, err := session.PutFile(ctx, "hello.py", strings.NewReader(content), int64(len(content)), dataplane.PutOptions{}); err != nil {
	log.Fatal(err)
}

stream, err := session.StartProcess(ctx, &ctrlv1.StartRequest{
	Argv: []string{"python3", "hello.py"},
	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)
}

凭证怎样管理

  • 会话的所有调用共用一张凭证,直到它的有效期过去 70%–80%。并发调用等待同一次签发。
  • 被拒绝的凭证立即更换。 node 回答 SESSION_EXPIRED 或 SESSION_REVOKED 时,会话申请一张新凭证。Unary 与 GetFile 随后把这次调用再发一次;Filesystem、Desktop 客户端与 PutFile 不重发,下一次调用用上新凭证。
  • 签发失败时退避:连续第二次失败后等 1 秒,之后翻倍,最长 30 秒。期间的调用直接拿到上一次的错误,不再问 ctrl,所以繁忙的程序不会把自己的流量变成对 ctrl 的调用。
  • WithExecution(generation) 把会话绑定到 Workspace 的一次执行,即 GetWorkspace 报告的 generation。Workspace 换了一次执行之后,调用以 dataplane.ErrExecutionChanged 失败;要访问新的执行,打开新会话。

会话提供什么

方法作用
PutFile、GetFile/v1/files 传输。上传最多 1 GiB,核对 node 报告的大小与 SHA-256;下载拒绝部分内容或不带长度的回答,内容不足所报长度时报错。超过 1 GiB 的文件分段读取,期间被改写时以 ErrFileChanged 失败;自行管理凭证的调用方用 Download 得到同样的行为。一分钟没有数据流动的传输会结束
StartProcess、AttachProcess进程流在 node 重启或数据面升级后,从最后一个输出偏移续上,见进程流续传
DialService到 Workspace 内某个服务目标的一条连接,类型是 net.Conn
ServiceDialer针对一个目标、自行续期凭证的拨号器,给一直运行、持续开新连接的适配器用
Filesystem、Desktop、Unary数据面服务的生成 Connect 客户端,每次调用都带会话的凭证

服务目标要用 WithServiceTargets 给出。经中继到达 node 时用 WithDialer:它负责建立 TCP 连接,TLS 仍按 node 自己的主机名校验。

完整程序

Kibotos 仓库里有只导入 SDK 的程序。每个程序第一次运行时为空的身份目录完成注册:

go run ./examples/go/quickstart -endpoint https://ctrl.example.com:18092 \
  -identity "$HOME/.local/state/kibotos-quickstart" -enrollment-key-file quickstart.key
  • examples/go/quickstart:连接、创建 Workspace、运行命令、传文件并删除。
  • 场景:编码 Agent、不可信代码、批量操作与桌面的程序。

兼容性

v1 之前,SDK 的 Go API 只在 MINOR 版本改变,PATCH 版本不改。每次改变都写在 CHANGELOG.md 对应版本的升级须知里。

线上协议有自己的兼容承诺,见兼容承诺。用较老 SDK 构建的程序,在那里所写的兼容窗口内可以继续对较新的 ctrl 工作。

下一步

  • 写操作与错误:幂等键、版本校验的规则,以及 Detail 与 Reason 读取的错误。
  • 数据面:会话凭证、流的中断,以及各服务需要的权限范围。
  • 身份与注册:为程序自己的身份生成注册密钥。

本页内容