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 需要仓库的读权限,并且不能经过公共模块代理:
-
向 Kibotos 维护者申请
felinics/kibotos的读权限。 -
让 Go 直接拉取这个模块。
GOPRIVATE同时关闭它的公共校验和数据库:go env -w GOPRIVATE=github.com/felinics/kibotos -
为 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.keyexamples/go/quickstart:连接、创建 Workspace、运行命令、传文件并删除。- 场景:编码 Agent、不可信代码、批量操作与桌面的程序。
兼容性
v1 之前,SDK 的 Go API 只在 MINOR 版本改变,PATCH 版本不改。每次改变都写在 CHANGELOG.md 对应版本的升级须知里。
线上协议有自己的兼容承诺,见兼容承诺。用较老 SDK 构建的程序,在那里所写的兼容窗口内可以继续对较新的 ctrl 工作。