用容器运行
ctrl、E2B 网关与 Exposure 网关每个版本都另外发布容器镜像,可以用 Kubernetes 或 Docker 运行。node 仍然装在宿主机上,见安装 node。运维容器平台时可以用这些镜像。
本页只写与宿主安装不同的部分。配置项的含义见 ctrl 配置、安装 ctrl,以及安装 E2B 网关与安装 Exposure 网关里的变量表,升级的判据见重装与升级。示例清单在 deploy/kubernetes/ 与 deploy/compose/。
镜像
| 镜像 | 默认命令 | 运行账户(uid:gid) | 端口 | 持久卷挂载点 |
|---|---|---|---|---|
ghcr.io/felinics/kibotos-ctrl | kibotos-ctrl serve | 10001:10001 | 18092(API)、18101(node API)、18192(指标) | /var/lib/kibotos,状态目录是其中的 ctrl/ |
ghcr.io/felinics/kibotos-e2b | kibotos-e2b | 10002:10002 | 18095(API)、18195(指标) | /var/lib/kibotos,身份目录默认 e2b/identity |
ghcr.io/felinics/kibotos-gw | kibotos-gw | 10003:10003 | 18097(tunnel 入口)、18197(指标) | /var/lib/kibotos,身份目录默认 gw/identity |
- 指标端口只有配置了指标监听时才打开,见集群与服务指标。
- E2B 网关还在容器内的
127.0.0.1:18096上打开一个只服务 envd 协议的监听。示例不发布它:18095 端口已经服务 envd 请求。 - tag 与发布版本相同,例如
v1.2.3、v1.2.3-rc.1。没有latest这类会移动的 tag。 - 镜像里的二进制取自同版本的 deb,两者逐字节相同。
- 每个版本的附件
kibotos-images_<版本>.txt按 digest 列出这三个镜像,并被SHA256SUMS覆盖。部署时按 digest 引用,与下载的发布包互相印证。
用下载发布包得到的目录查看这个版本的镜像,并核对镜像报告的版本:
cat kibotos-releases/v1.2.3/kibotos-images_1.2.3.txt
docker run --rm ghcr.io/felinics/kibotos-ctrl:v1.2.3 version --json源码仓库尚未公开,拉取镜像同样要有仓库读取权限:用一个带 read:packages 权限的 GitHub token 登录 ghcr.io,Kubernetes 里把它做成 imagePullSecret。
与宿主安装的不同
状态放在持久卷上
- ctrl 的 PKI 与网关的身份都在持久卷里。 卷挂在
/var/lib/kibotos,服务自己在其中建0700的子目录。 - ctrl 的卷丢失,就丢失了这个部署的身份:全部 node 与 Operator 都要重新注册。要像备份数据库一样备份它。
- 不能把 Secret 或 ConfigMap 目录挂成状态目录或身份目录。 它们由符号链接组成、带组权限,服务会拒绝加载。
- Kubernetes 设
fsGroup与fsGroupChangePolicy: OnRootMismatch。fsGroup让服务能在卷根下建自己的目录。改变策略的默认值Always会让 kubelet 每次挂载都给卷里的文件加上组读写,ctrl 下次启动就会因为私钥权限过宽而拒绝启动。 - Docker 命名卷第一次挂载时复制镜像里的目录,属主已经正确。改用宿主目录时,先用
install -d -o 10001 -g 10001 -m 0700这类命令建好服务的子目录。
配置
- ctrl 的配置可以整目录挂载。 Kubernetes 把 Secret 或 ConfigMap 挂到
/etc/kibotos/ctrl,Docker 绑定挂载一个目录。config.toml、dsn_file与指标签名私钥文件可以是符号链接,最终指向的必须是普通文件,并且服务账户能读取。 - 网关的配置只来自环境变量,变量名与安装包的
config.env相同,完整列表见e2b.example.env与gw.example.env。监听地址默认是回环地址,容器里改成0.0.0.0。 - 缺少必填项或取值非法时,网关一次列出全部有问题的变量后退出,不输出取值。
- 指标监听默认关闭。 ctrl 在
config.toml里写[telemetry],两个网关设KIBOTOS_E2B_TELEMETRY_LISTEN与KIBOTOS_GW_TELEMETRY_LISTEN,容器里同样绑0.0.0.0。示例清单已经打开两个网关的监听;采集方式见集群与服务指标。
ctrl 只能运行一个
- 同一时刻只运行一个 ctrl。 Kubernetes 用
replicas: 1与strategy: Recreate,不要用 RollingUpdate,也不要调高副本数。Docker 只起一个容器。 - 第二个 ctrl 连上同一个数据库时只回答读取,不会和第一个同时写入,见执行权。但两个进程不能共用同一份
storage.root。 - ReadWriteOnce 卷让调度到其他宿主的第二个 Pod 挂不上卷,但它不能代替 Recreate。
- 不要在每次启动时自动执行
init,例如把它放进 initContainer。卷挂错或丢失时,serve应当加载失败;init却会生成一套新的部署身份。 - 承载 ctrl 的宿主失联时,旧 Pod 停在
Terminating,卷不释放,新 Pod 起不来,这段时间没有可用的 ctrl。只有确认旧宿主已经停机后,才能强制删除旧 Pod。
入口与主机名
- ctrl 的两个端口前面只能是 L4 透传。 两者都是端到端 mTLS,Kubernetes 用 LoadBalancer 或 NodePort Service,不能用 Ingress。
[identity].host解析到这个入口。 node 的[ctrl].endpoint、kibo与网关都用这个名字连 ctrl,因为 ctrl 的证书只签这一个名字。- 网关在集群里不能用 Service 的集群内名字连 ctrl。集群 DNS 解析不到
[identity].host时,Kubernetes 用hostAliases把它指向 ctrl Service 的地址;Compose 示例给 ctrl 加了同名的网络别名。
- 网关在集群里不能用 Service 的集群内名字连 ctrl。集群 DNS 解析不到
- artifact store 的
endpoint写 node 与 Operator 工作机都能直连的地址。 ctrl 签发的预签名 URL 用的是同一个 endpoint,写集群内地址会让 node 下载失败。 - 网关要能解析并连到 node 的数据面主机名,即
<node_id>.<host_suffix>。 - 承载 ctrl 的宿主要开启时间同步。 容器用宿主的时钟,要求见时间同步。
网关:单实例,首次启动自行登记
两个网关都是无状态的,但每个实例需要自己的 Operator 身份。几个实例共用一份身份目录时,它们各自续期;ctrl 只接受一个身份当前与上一张证书,第三个实例会被拒绝。示例清单每个网关只提供一份身份,所以每个网关只运行一个实例。更多实例与宿主安装一样,每个都需要自己的身份,见身份的使用规则。
- 网关的身份目录放在持久卷上。卷是空的时,网关用
KIBOTOS_E2B_ENROLLMENT_KEY_FILE或KIBOTOS_GW_ENROLLMENT_KEY_FILE指向的一次性注册密钥登记,然后才开始服务。 - 已经登记过的网关不再读取这个文件,所以用过的密钥可以留在 Secret 里。
- 密钥默认 1 小时内有效,最长 24 小时(
--ttl),签发后在有效期内启动网关。 - Kubernetes 挂载的 Secret 文件带组读权限,网关登记时会记一条「权限过宽」的警告,可以忽略。
- 网关的卷丢失后,它启动时登记失败、不开始服务。为同一个 principal 签发新密钥,更新 Secret 后重启网关。
- Kubernetes 示例用
strategy: Recreate:旧 Pod 停下之后新 Pod 才启动,同一份身份不会被两个进程同时使用。
Kubernetes
示例清单假设命名空间是 kibotos,用到下面几个文件。照实际情况改镜像版本、主机名、域名与存储大小后再应用。
| 文件 | 内容 |
|---|---|
ctrl-state.yaml | ctrl 的持久卷 |
ctrl-command.yaml | 在 ctrl 卷上执行一次性命令的 Job,COMMAND 替换成命令名 |
ctrl.yaml | ctrl 的 Deployment、LoadBalancer Service 与 NetworkPolicy |
e2b.yaml | E2B 网关的持久卷、单副本 Deployment、Service、NetworkPolicy 与 Ingress |
gw.yaml | Exposure 网关的持久卷、单副本 Deployment、Service 与 NetworkPolicy,以及 cloudflared |
三个清单的 NetworkPolicy 只允许指标采集器连 telemetry 端口。采集器的清单是 deploy/metrics/kubernetes-vmagent.yaml,见集群与服务指标。
vmagent 写入的存储可以装在同一个集群里,用 Helm chart deploy/helm/kibotos-metrics/,见集群里的 VictoriaMetrics。
实例指标存储可以装在同一个集群里,用 Helm chart deploy/helm/kibotos-instance-metrics/,见Kubernetes 上的参考部署。
1. 准备命名空间、拉取凭证与 ctrl 配置
config.toml 按安装与初始化 ctrl填写,[storage].root 保持默认的 /var/lib/kibotos/ctrl。它含数据库与存储的凭证,放进 Secret;Secret 整个挂到 /etc/kibotos/ctrl,配置里用到的 dsn_file、指标签名私钥与 CA 文件也放进同一个 Secret,路径写成 /etc/kibotos/ctrl/<文件名>。要导出 ctrl 自己的指标时,配置里加上 [telemetry] 段,listen 写 0.0.0.0:18192。启用实例指标时多加一个 --from-file=metrics-signing-key.pem=...,生成方法见写入凭证:
set -eu
kubectl create namespace kibotos
kubectl -n kibotos create secret docker-registry ghcr \
--docker-server=ghcr.io --docker-username=deploy-bot --docker-password="$GHCR_TOKEN"
kubectl -n kibotos patch serviceaccount default -p '{"imagePullSecrets":[{"name":"ghcr"}]}'
kubectl -n kibotos create secret generic kibotos-ctrl-config --from-file=config.toml=./config.tomlbucket 与权限的准备与宿主安装相同,见准备 bucket 与权限。
2. 初始化 ctrl
先建卷,再在卷上依次执行 init 与 migrate。每个 Job 完成后看一眼日志再删除:
set -eu
kubectl apply -f deploy/kubernetes/ctrl-state.yaml
for command in init migrate; do
sed "s/COMMAND/$command/g" deploy/kubernetes/ctrl-command.yaml | kubectl apply -f -
kubectl -n kibotos wait --for=condition=complete --timeout=10m "job/kibotos-ctrl-$command"
kubectl -n kibotos logs "job/kibotos-ctrl-$command"
kubectl -n kibotos delete job "kibotos-ctrl-$command"
doneinit 已经初始化过时输出 unchanged,不改任何文件。
3. 启动 ctrl 并签发第一个 Operator 密钥
set -eu
kubectl apply -f deploy/kubernetes/ctrl.yaml
kubectl -n kibotos rollout status deploy/kibotos-ctrl --timeout=5m
kubectl -n kibotos get service kibotos-ctrl把 [identity].host 的 DNS 记录指向 Service 的外部地址。然后在 ctrl 容器里签发密钥,取回本机后删除容器里的那份:
set -eu
umask 077
kubectl -n kibotos exec deploy/kibotos-ctrl -- \
kibotos-ctrl enrollment create --operator operator-a --output /var/lib/kibotos/ctrl/operator-a.key
kubectl -n kibotos exec deploy/kibotos-ctrl -- cat /var/lib/kibotos/ctrl/operator-a.key > operator-a.key
kubectl -n kibotos exec deploy/kibotos-ctrl -- rm /var/lib/kibotos/ctrl/operator-a.key之后按身份与注册用这把密钥注册 kibo,再按安装 node接入 node。
4. 启动 E2B 网关
先为网关签发一把注册密钥。不敏感的变量写在 e2b.yaml 的 ConfigMap 里;API key、token 密钥与命名空间放进 Secret,e2b-secret.env 里只写这三个变量:
set -eu
umask 077
kibo enrollment create --operator kibotos-e2b --ttl 24h --output kibotos-e2b.key
kubectl -n kibotos create secret generic kibotos-e2b-enrollment --from-file=enrollment.key=kibotos-e2b.key
kubectl -n kibotos create secret generic kibotos-e2b --from-env-file=e2b-secret.env
kubectl -n kibotos create secret tls kibotos-e2b-tls --cert=sandbox.crt --key=sandbox.key
kubectl apply -f deploy/kubernetes/e2b.yaml
kubectl -n kibotos rollout status deploy/kibotos-e2b --timeout=10m
rm kibotos-e2b.keyIngress 的要求与宿主上的反向代理相同,见配置反向代理:证书覆盖 api.<domain> 与 *.<domain>,保留原始 Host,允许 WebSocket,关闭请求与响应缓冲。用 ingress-nginx 时,加上 nginx.ingress.kubernetes.io/proxy-buffering: "off" 与 nginx.ingress.kubernetes.io/proxy-request-buffering: "off" 两个注解。
5. 启动 Exposure 网关
ctrl 的 config.toml 要先填好 [exposure] zone。tunnel 的凭证文件由 provider 创建,放进 Secret:
set -eu
umask 077
kibo enrollment create --operator kibotos-gw --ttl 24h --output kibotos-gw.key
kubectl -n kibotos create secret generic kibotos-gw-enrollment --from-file=enrollment.key=kibotos-gw.key
kubectl -n kibotos create secret generic kibotos-cloudflared --from-file=credentials.json=./tunnel-credentials.json
kubectl apply -f deploy/kubernetes/gw.yaml
kubectl -n kibotos rollout status deploy/kibotos-gw --timeout=10m
rm kibotos-gw.key- cloudflared 的几个副本都运行同一条 named tunnel,把整条 zone 发给网关的 Service。tunnel 的配置见配置 tunnel。
- 网关与 tunnel 之间是明文 HTTP。示例的 NetworkPolicy 只允许 cloudflared 的 Pod 连网关的服务端口,指标端口只允许指标采集器连接,集群要启用 NetworkPolicy。
Docker Compose
compose.yaml 在一台 Docker 宿主上运行 ctrl、E2B 网关,以及 Exposure 网关与 cloudflared。它读取同目录下的这些文件:
| 文件 | 内容 | 属主 |
|---|---|---|
ctrl/config.toml | ctrl 配置。目录整个挂到 /etc/kibotos/ctrl,配置里用到的其他文件也放在这里 | 目录与文件都是 10001:10001,目录 0700、文件 0600 |
e2b/e2b.env | E2B 网关的环境变量,从 e2b.example.env 复制后填写 | 只给 Docker 读取,0600 |
e2b/enrollment.key | E2B 网关的注册密钥 | 10002:10002,0600 |
gw/gw.env | Exposure 网关的环境变量,从 gw.example.env 复制后填写 | 只给 Docker 读取,0600 |
gw/enrollment.key | Exposure 网关的注册密钥 | 10003:10003,0600 |
gw/cloudflared.yaml、gw/cloudflared-credentials.json | tunnel 配置与凭证 | 65532:65532,0600 |
- 两个环境文件里的
CTRL_ENDPOINT都写https://<[identity].host>:18092:Compose 网络里 ctrl 带着这个名字的别名,示例里是ctrl.kibotos.example,改成实际的名字。 gw/cloudflared.yaml的 ingress 指向http://gw:18097,其余与配置 tunnel相同;credentials-file写/etc/cloudflared/credentials.json。- 不提供 Exposure 时,删掉
gw与cloudflared两个服务。 - 两个网关的指标监听已经打开,只在 Compose 网络里可达;ctrl 的在
ctrl/config.toml里写[telemetry]。采集方式见集群与服务指标。
以 ctrl 的配置为例,把目录与文件交给服务账户:
set -eu
sudo install -d -o 10001 -g 10001 -m 0700 deploy/compose/ctrl
sudo install -o 10001 -g 10001 -m 0600 config.toml deploy/compose/ctrl/config.toml初始化 ctrl、启动并签发第一个 Operator 密钥:
set -eu
cd deploy/compose
docker compose run --rm ctrl init
docker compose run --rm ctrl migrate
docker compose up -d ctrl
docker compose exec ctrl kibotos-ctrl enrollment create --operator operator-a --output /var/lib/kibotos/ctrl/operator-a.key
(umask 077; docker compose exec ctrl cat /var/lib/kibotos/ctrl/operator-a.key > operator-a.key)
docker compose exec ctrl rm /var/lib/kibotos/ctrl/operator-a.key用这把密钥注册 kibo 后,为网关签发 key、放进上表的位置,再启动其余服务:
set -eu
cd deploy/compose
docker compose up -d
docker compose ps
docker compose logs e2b gwE2B 网关发布在 127.0.0.1:18095,宿主上的 HTTPS 反向代理转发到这里。
一次性命令
宿主安装里以服务账户执行的 kibotos-ctrl 命令,在容器部署里这样执行:
| 命令 | 什么时候 | Kubernetes | Docker Compose |
|---|---|---|---|
init | 首次部署,只执行一次 | ctrl-command.yaml 的 Job | docker compose run --rm ctrl init |
migrate | ctrl 停止后,用目标版本的镜像 | 同上 | docker compose run --rm ctrl migrate |
check-upgrade | 停机前预检,以及停止后的正式检查 | 同上,用目标版本的镜像 | docker compose run --rm ctrl check-upgrade |
enrollment create | ctrl 运行时 | kubectl exec 进 ctrl 的 Pod | docker compose exec ctrl kibotos-ctrl enrollment create |
catalog apply | ctrl 运行时 | 同上 | 同上 |
- 一次性命令都以镜像里的服务账户运行,不要加
--user 0或改用 root:root 建出的文件服务账户读不了。 - ctrl 运行时的
check-upgradeJob 要和 ctrl 落在同一台宿主上才能挂载卷,ctrl-command.yaml为此设了 Pod 亲和;调度到别处时 Job 停在ContainerCreating。
升级
判据与步骤与宿主部署相同,见同一 MINOR 内逐台升级与跨版本升级。
ctrl
ctrl 只能先停后起,替换期间新的 Workspace 操作与新的数据面会话要等它恢复,已经建立的数据面连接不受影响。以 Kubernetes 为例,v1.2.3 升到 v1.2.4:
set -eu
target=v1.2.4
run_ctrl_command() {
sed -e "s/COMMAND/$1/g" -e "s/kibotos-ctrl:v[^ ]*/kibotos-ctrl:$target/" deploy/kubernetes/ctrl-command.yaml | kubectl apply -f -
kubectl -n kibotos wait --for=condition=complete --timeout=10m "job/kibotos-ctrl-$1" || true
kubectl -n kibotos logs "job/kibotos-ctrl-$1"
kubectl -n kibotos delete job "kibotos-ctrl-$1"
}
kubectl -n kibotos scale deploy/kibotos-ctrl --replicas=0
kubectl -n kibotos wait --for=delete pod -l app.kubernetes.io/name=kibotos-ctrl --timeout=2m
run_ctrl_command check-upgrade报告没有阻塞项时,按报告执行迁移,再换镜像启动:
set -eu
target=v1.2.4
sed -e "s/COMMAND/migrate/g" -e "s/kibotos-ctrl:v[^ ]*/kibotos-ctrl:$target/" deploy/kubernetes/ctrl-command.yaml | kubectl apply -f -
kubectl -n kibotos wait --for=condition=complete --timeout=10m job/kibotos-ctrl-migrate
kubectl -n kibotos delete job kibotos-ctrl-migrate
kubectl -n kibotos set image deploy/kibotos-ctrl ctrl="ghcr.io/felinics/kibotos-ctrl:$target"
kubectl -n kibotos scale deploy/kibotos-ctrl --replicas=1
kubectl -n kibotos rollout status deploy/kibotos-ctrl --timeout=5m- 报告有阻塞项时不换镜像,把副本数调回 1,ctrl 以原版本恢复服务。
- Docker Compose 的顺序相同:
docker compose stop ctrl,用目标版本镜像docker compose run --rm ctrl check-upgrade与migrate,改compose.yaml里的版本后docker compose up -d ctrl。 - 迁移执行过后,旧版本的 ctrl 会拒绝更高的 schema,只能往前修。
网关
同一发布线内,网关可以在 ctrl 升级之后的任意时刻替换:
- Kubernetes:
kubectl -n kibotos set image deploy/kibotos-e2b e2b=ghcr.io/felinics/kibotos-e2b:v1.2.4。 - Docker Compose:改
compose.yaml里的版本后docker compose up -d e2b。
网关只有一个实例,替换期间它的入口短暂不可用。网关收到停止信号后立即停止接收新连接,进行中的请求有 5 秒完成;之后仍未结束的长连接(命令输出流、终端、Exposure 的 WebSocket)会断开,客户端重连即可。
跨版本升级时网关随停机流程一起替换,先升 ctrl 再升网关。
排查
| 现象 | 原因 | 处理 |
|---|---|---|
ctrl 日志 load managed PKI state: ... must not grant group or other access | kubelet 给卷里的文件加了组权限:fsGroupChangePolicy 不是 OnRootMismatch | 改成 OnRootMismatch,用一个以 10001 运行的一次性 Pod 对 /var/lib/kibotos/ctrl 执行 chmod -R go= 后重启 ctrl |
ctrl 日志 config: open /etc/kibotos/ctrl/config.toml: permission denied | 配置文件对服务账户不可读 | Kubernetes 设 fsGroup 并让 Secret 的 defaultMode 带组读权限;Docker 把文件交给 10001 |
网关日志 holds no enrolled identity and no enrollment key file is configured | 卷是新的,又没有配置 key 文件 | 签发 key 并配置 KIBOTOS_E2B_ENROLLMENT_KEY_FILE 或 KIBOTOS_GW_ENROLLMENT_KEY_FILE |
网关日志 enroll: 后面是 permission_denied、failed_precondition 或 already_exists | 密钥已被使用、过期或被撤销 | 为这个网关的 principal 签发新密钥,更新 Secret 后重启它 |
| 网关连不上 ctrl,或报证书不匹配 | 用了 ctrl Service 的集群内名字,或 Pod 里解析不到 [identity].host | 用 [identity].host,必要时加 hostAliases |
Pod 停在 ErrImagePull,原因是 401 或 403 | 拉取镜像的凭证缺失或没有读取权限 | 按镜像一节配置 imagePullSecret |
安装 E2B 网关
本页介绍如何安装 E2B 网关,让基于 E2B SDK 的程序接入 Kibotos。
重装与升级
本页介绍如何在不停止 Workspace 的情况下重装同版本的包、升级到新版本。各版本额外的升级步骤见从 v0.1.x 升级到 v0.2.0与从 v0.2.x 升级到 v0.3.0,卸载见卸载。 升级分两种:同一发布线内(同一 MAJOR.MINOR 的正式版本与 -rc.N 版本之间)逐台替换,ctrl 与 node 不用同时停;跨 MINOR 以及涉及 -alpha.N、-beta.N 的升级整套停机替换。 首次安装见安装概览。node 与 VMM 的进程模型和维护检查命令的细节见 node 与 VMM 进程。 ctrl 与网关以容器运行时,判据与步骤与本页相同,执行方式见用容器运行。