RLark 深度解析:跨集群具身智能云原生平台的全栈架构
RLark 深度解析:跨集群具身智能云原生平台的全栈架构
具身智能有个硬约束:训练在云上 GPU 集群跑,部署在边缘的机械臂/相机/传感器上——两者物理上分布在不同的网络、不同的集群、甚至不同的安全域。现有平台要么只管云训练(K8s/Volcano),要么只管边缘设备(ROS),中间那条"云到边、跨集群、带鉴权"的路没人铺。
RLark(github.com/RLinf/RLark,2026/08 开源,Apache 2.0)就是来铺这条路的——一个跨集群具身智能云原生平台:用 kcp 做轻量控制面,统一管理云 GPU 集群和边缘设备,通过 Domain/Node/Job/Task/Workflow 一套 CRD 把"训练→部署"全链路声明式化,再用 TUN+gVisor+SSH 隧道让跨集群 Pod 直接通信,最后用 Device Plugin 把 ROS 机器人和相机接进 K8s 调度。
本文基于源码深挖,把 RLark 拆成四层:CRD 模型、kcp 控制面、跨集群 Pod 网络、具身运行时。
关联阅读:具身智能训练栈与数据管线见 从 LLM Infra 到具身智能 Infra;VLA 模型格式见 为什么 VLA 训练选 FSDP。RLark 是这些模型/数据之上的"基础设施编排层"。
一、全景:四层架构一张图
1 | ┌──────────────────────────────────────────────────────────────────┐ |
四个二进制构成控制面+数据面:server(中枢)、gateway(REST 入口)、controller-manager(CRD 调谐)、agent(数据面,分 Cluster Agent / Node Agent);外加 network-sidecar(跨集群网络)、embodied-runtime(边缘设备)。前端 React+Vite,数据库 PostgreSQL+Bun ORM。
二、CRD 模型:七个对象表达"训练→部署"
RLark 的 API group 是 rlinf.io/v1alpha1(注意:Go 包目录叫 rlark.io,但 API group 是 rlinf.io),七个 CRD:
| CRD | 作用 | scope | 关键字段 |
|---|---|---|---|
| Domain | 一个虚拟 L3 网络域(跨集群) | cluster | spec.cidr + status.ipAllocations[] |
| DomainPeer | 域内某集群的 Pod 集合 + SSH 证书 | namespaced | spec.pods[] + spec.cert/key(SSH 证书) |
| Node | 域内一个计算节点(GPU 服务器/边缘设备) | namespaced | 支持 unschedulable(cordon) |
| Job | 用户声明的训练作业 | cluster | spec.tasks[](JobTaskTemplate)+ spec.domain |
| Task | 叶子工作单元(映射到本地 Pod) | namespaced(rlark-{clusterID}) |
spec.kubernetes/docker/raw + role(Actor/Rollout/Env) |
| Workflow | Job 的 DAG | cluster | spec.jobTemplates[].dependencies[] |
| Pod | 数据面 Pod 的镜像(不是用户创建) | namespaced | status.phase/node/ip |
2.1 Job → Task:声明式 DAG 拆分
Job 是用户面 (cluster-scoped),它声明一组 JobTaskTemplate(每个带 Head bool 标记 Ray head/worker、内联 TaskSpec)。Job controller 在 buildTask(job/build.go:49-73)里对每个 template 生成一个 Task,名字 {job}-{task},namespace 从 template 的 NodeSelector 解析出目标 Node 所在的 rlark-{clusterID} 命名空间,继承 spec.domain 和 spec.sshPublicKey,盖上 Ray 注解(ray-role/ray-total-nodes/ray-node-rank-start)。
Task 是叶子(namespaced,每个数据面集群一个 rlark-{clusterID} namespace),支持三种 runtime:KubernetesTaskSpec(Deployment/DaemonSet/StatefulSet/CloneSet + pvcStorageMap)、DockerTaskSpec、RawTaskSpec——这覆盖了"K8s 集群、Docker 节点、裸机"三种数据面形态。Task 的 Role 取 Actor/Rollout/Env,直接对应 RL 训练的角色分工。
2.2 Workflow:Job 之上的 DAG
WorkflowSpec.JobTemplates[] 每个 WorkflowJobTemplate 带 Dependencies []string——这就是 DAG 边。Workflow controller 的 dag.go:16-60 用入度表建图,dispatchReady 返回入度为 0 且未派发的 template,逐层创建子 Job({wf}-{jt},带 SetControllerReference + rlinf.io/workflow 标签)。状态机:Pending → Running → Succeeded/Failed。
2.3 Job/Task 状态机
Job controller(job/statemachine.go:22-53):
1 | "" → Pending(init) |
evaluateJobEvent 把子 Task 的 phase 聚合成事件(EventAllTasksDone/EventAnyTaskFailed/EventTasksRunning),驱动状态流转。Stopped=true 会把所有子 Task 的 Replicas 强制为 0——声明式停机。
2.4 Domain + DomainPeer:跨集群网络的契约
Domain(cluster-scoped)定义一个虚拟 L3 网络(spec.cidr),Domain controller 从 CIDR 里给每个 Pod 分配虚拟 IP(IPPool),按集群 namespace 分组,在每个集群写一个 DomainPeer(namespaced)。DomainPeer 的 spec.pods[] 记录该集群内属于此 domain 的所有 Pod(GlobalNamespace/Namespace/Name/UID/Node/IP 虚拟 IP/LocalIP 真实 Pod IP),外加 spec.cert/key——一份该 domain 的 SSH 证书,用于跨集群 Pod 通信时的隧道鉴权。这套 CRD 就是后面跨集群网络的路由依据。
三、控制面:kcp 上的 Server/Gateway/ControllerManager
控制面跑在 kcp(轻量 K8s API server)上,三个组件:
3.1 Server:中枢(HTTPS + SSH + 反代 + CA)
Server(apps/rlark/pkg/server/)是整个平台的中枢,一个进程跑五件事(server.go:90-116):
- HTTPS API(8443):mTLS(
tls.RequireAndVerifyClientCert),handleCertCheck读客户端证书的自定义 OID 扩展,查吊销列表(DB + 内存缓存)。路由包括/api/connect(agent websocket 隧道)、/api/proxy/:target/*(反代到 agent)、/api/podproxy、/api/taskproxy、/api/terminal(WebSocket 终端)、/api/sign(签发证书)、/api/revoke、/api/kubernetes/*(impersonated kube API)。 - SSH server(2222):
charmbracelet/ssh+wish,host key 用 TLS CA 的 key。sshPublicKeyAuth信任 CA 公钥,校验吊销,UserKeyFallback兜底查 DB 的SSHUserKeyStore。handleSSHChannel实现direct-tcpip端口转发——这是跨集群 Pod 网络的关键一跳(后面讲)。 - 反代到 agent:核心是
reverseproxy/dialer_factory.go(包装rancher/remotedialer)。Agent 通过 websocket 反向连到 Server(/api/connect,带X-Remote-Dialer-Client-Key=agentID),Server 注册该 agent 的 RBAC(建rlark-{id}namespace、rlark-agentSA、CRUD Role)。Gateway/SSH 要访问 agent 时,调GetDial(ctx, "default", target, ...)→getAgentDialer(agentID)→ 复用那条反向隧道。约定 agent 本地 HTTP 在0.0.0.0:1。 - CA + 证书签发:
sign.go按 role 签不同证书:admin→ X.509,kube impersonation 全权agent→ X.509,agent-id+namespace=rlark-{id}+ kube impersonationsystem:serviceaccount:rlark-{id}:rlark-agent+remote-dialer-client-idpeer→ X.509,server-to-server 发现domain→ SSH 证书,domain-id(跨集群转发身份)ssh-guest→ SSH 证书,用户 SSH 到 Pod
- Lease 锁初始化:
init抢rlark-server-init-lockLease,一次性建 CA、签 admin 证书、迁移 DB。
3.2 Gateway:REST 入口
Gateway(:8080)是给前端/CLI 用的 REST API。路由(router.go):/api/v1/rlinf.io/v1alpha1/{nodes,workflows,jobs,tasks,pods,domains} 全 CRUD(Jobs/Workflows/Domains 是 cluster-scoped),Jobs 的 GET /:name/logs(扇出到 Task→Pod CR,经 Server 反代拉 Pod 日志)、Tasks 的 GET /:name/tensorboard/*(反代到 Pod :6006)、Pods 的 GET /:name/terminal(WebSocket 终端)。还有 /api/v1/certificates/agent(签 agent 证书)、/api/v1/ssh-user-keys、/api/v1/clusters、addon catalog。
读路径DB 优先、kube 兜底(handler.go:91-113):配了 PostgreSQL 就走 Bun ORM 的 ResourceStore(JSONB 查询),否则走 kcp typed client。
3.3 ControllerManager:五个调谐器
controller_manager.go:61-113 起五个 reconciler(Job/Task/Workflow/Node/Domain),配了 DB 再加四个 *-sync(把 CR 持久化到 PG)。共享 driver(base.go:22-55):Get → IsTerminal → ReconcileStateMachine → Status().Update。Job controller For(Job).Owns(Task)(子 Task 事件触发 Job 调谐),Workflow For(Workflow).Owns(Job),Domain watch Domain+Pod CR 算 IP、写 DomainPeer。
四、跨集群 Pod 网络:TUN+gVisor+SSH,无 NAT 穿透
这是 RLark 最有技术含量的部分。核心命题:让集群 A 的 Pod 能直接用虚拟 IP 访问集群 B 的 Pod,且两边都在 NAT 后、无入站端口。
4.1 为什么不用 WireGuard/IPsec
- TUN 而非内核 VPN 隧道:sidecar 跑在 Pod 容器里(只需
CAP_NET_ADMIN),TUN+用户态 netstack 不需要内核模块、不需要每节点wg接口。per-Pod/per-Domain 注入,DomainPeer CR 动态改写不碰节点路由表。 - gVisor netstack 做"连接级路由"而非"逐包封装":netstack 终止虚拟 TCP 握手,每个 flow 发一条 target line(
tcp://10.2.0.5:80),路由变成每连接一次的控制面决策(发往哪个集群/节点),而非逐包 IP 路由+IPsec SA 选择。WireGuard/IPsec 是包级隧道,要全 mesh 路由、封装每个包,且对应用不可见——而 RLark 的 flow 级agent-node寻址正需要连接级可见。 - SSH+WebSocket 反向隧道彻底避免 NAT 穿透:WireGuard/IPsec 至少要一端可达(或 UDP 打洞/STUN/TURN),对 NAT 后的边缘机器和云 GPU(无入站防火墙洞)不可行。RLark 让每个 agent 主动拨出到控制面(SSH/WebSocket),跨集群 Pod 流量经控制面 hub 中继。代价是多一跳,换零入站端口需求——对边缘场景划算。
- 复用已有 PKI:domain SSH 证书(
DomainPeer.Spec.Cert/Key,roledomain)在GetDial里做checkHostInDomain域级鉴权。WireGuard/IPsec 只给传输加密,无应用级授权;RLark 在 SSH 层白拿 mTLS+域策略。
4.2 数据路径:Pod A → Pod B 全程
设集群 A 的 Pod A(虚拟 IP 10.2.0.4)访问集群 B 的 Pod B(10.2.0.5:80):
1 | Pod A 进程: TCP 10.2.0.4:40000 → 10.2.0.5:80 |
应用在 A/B 两端看到的是扁平 IP 网络、无 NAT,字节实际经控制面 SSH+WebSocket 中继。同集群流量可绕过控制面直连目标 Pod proxy(network.go:212-219);跨集群直连是 TODO(network.go:222-224),当前都走 relay。
4.3 DomainPeer:路由契约 + SSH 证书
1 | type DomainPeerSpec struct { |
Domain controller watch Domain+Pod CR,从 IPPool 分虚拟 IP,按集群 namespace 分组写 DomainPeer,签 domain SSH 证书(role domain)。NodeServer 路由时查 DomainPeer 找目标 Pod 的 LocalIP+Node+agentID,控制面 checkHostInDomain 校验目标在该域内——CRD 即网络路由表 + 鉴权策略。
五、具身运行时:把 ROS 机器人和相机接进 K8s
apps/embodied-runtime/ 解决"边缘节点上的机器人/相机怎么被 K8s 调度和控制"。核心是 Kubernetes Device Plugin + gRPC 服务。
5.1 Device Plugin:advertise rlinf.io/device
pkg/deviceplugin/plugin.go 实现 K8s Device Plugin,ListAndWatch(:424-453)上报 rlinf.io/device 资源(每个配置了的机器人/相机算一个 device)。Allocate(:464-474 + :496-570)给请求该资源的 Pod 注入:
/var/run/rlark(controller socket)和/opt/rlinf/bin(rosctr/camctr)只读挂载;RLINF_EMBODIED_{ROS,ROS2,CAMERA}_{ENABLED,SOCKET_PATH}环境变量;- 若只有一个机器人缓存,直接注入
ROS_MASTER_URI(ROS1)或ROS_DOMAIN_ID(ROS2),让 Pod 里的 ROS 工具零配置连上机器人。
5.2 ROS/ROS2 Controller + Camera Controller
pkg/roscontroller/+pkg/ros2controller/:管理 ROS/ROS2 机器人。一个"机器人"是一个可roslaunch/ros2 launch的 launch 文件 + ROS master/domain。gRPCRobotController(robot.proto)暴露StartRobot/StopRobot/GetRobotStatus/SwitchMode/ResetRobot/ListRobots/ListModes+ 包查询(ListPackages/GetLaunchFileArgs)。StartRobot→launchMode→roslaunch serl_franka_controllers impedance.launch robot_ip:=172.16.0.2(robot_ops.go:314-333)。pkg/cameracontroller/:CameraController(camera.proto)暴露ListCameras/OpenCamera/CaptureFrame/CaptureFrames/WatchFrames(server-streaming)。controller.go后台 capture loop 拉帧供观测数据采集。
ROS1 和 ROS2 共用同一套 RobotController 契约(差异在 ros_master_uri vs ros_domain_id),上层 SDK 无感。
5.3 devinit + MutatingWebhook:macvlan 注入
边缘机器人常在物理子网(如 172.16.0.0/24),Pod 要 L2 访问就得挂 macvlan。pkg/mutatingwebhook/ 给请求 rlinf.io/device 的 Pod 自动注入 rlark-devinit init container(webhook.go:240-252),它跑 devinit setup,经 devinit.sock 调 DeviceServer.Setup(device_server.go:155-209)。Setup 用 SO_PEERCRED 拿调用方 PID,在该 Pod 的 netns 里创建 macvlan 接口(pkg/netmac/,vishvananda/netlink 纯 Go 实现,无 ip/nsenter shell-out),把 Pod 接到机器人物理子网。
5.4 SDK:Python/Go 客户端
- Python(
sdks/embodied-runtime-python/):RobotClient(robot.py:133)+CameraClient(camera.py:36),socket 路径从注入的RLINF_EMBODIED_*_SOCKET_PATH环境变量解析(_transport.py),也支持host:portTCP。 - Go(
sdks/embodied-runtime-go/):DialRobot/DialRobot2/DialCamera(transport.go:79-97),env 感知。
5.5 云训练 → 边缘机器人控制流
- 训练/评测 Pod 请求
rlinf.io/device: 1,K8s 调度到有该资源的边缘节点; Allocate注入 socket 挂载 + ROS endpoint 环境变量;- (若配 macvlan)webhook 注入 devinit,把 Pod 接到机器人子网;
- Pod 内的 workload/agent 经 Unix socket 调
rosctr/SDK →StartRobot→roslaunch→ 机械臂动;CaptureFrame→ 拉观测帧; - 云控制面调度 Pod 到边缘节点(可带
nodeSelector匹配特定机器人),经 gRPC over socket 控制机器人;HTTP gateway 还把/v1/robots/{id}/proxy/*反代到机器人自带 web UI。
核心抽象:机器人/相机被 Device Plugin advertise 成 K8s extended resource,Pod request 它就被调度到对应节点并拿到控制 socket——硬件设备变成了 K8s 可调度、可声明式消费的一等公民。
六、端到端:一个具身 RL 训练作业怎么跑
把四层串起来,一个"云训练 + 边缘 rollout"的 RL 作业:
1 | 1. 用户提交 Workflow(DAG: train-job → eval-job) |
整个过程:训练在云 GPU、rollout 在边缘机器人、数据经虚拟网络回流——三件事在一个 kcp 控制面下声明式管理,跨集群 Pod 直连不需要 NAT 穿透,机器人作为 K8s 资源被调度。这就是 RLark 的核心价值。
七、读后感:几个设计亮点
- kcp 做控制面:不绑死某个 K8s 集群,跨集群统一管 CRD,数据面可以是 K8s/Docker/裸机三种形态——这比"一个大盘子 K8s 集群"灵活得多,尤其边缘设备跑不了完整 K8s。
- DomainPeer = 路由表 + 鉴权策略:跨集群网络的路由信息和 SSH 证书都塞进 CRD,声明式管理,Domain controller 动态维护——网络拓扑随 Pod 增减自动更新,不碰节点路由表。
- gVisor netstack 做连接级路由:用用户态 TCP 栈把"逐包路由"变成"每连接一次控制面决策",这是相对 WireGuard 的关键差异——应用可见的 flow 级寻址,才能做
agent-node这种基于 CRD 的路由。 - SSH+反向隧道避 NAT:边缘机器人和云 GPU 都在 NAT 后,双方都拨出到控制面 hub,跨集群流量中继——用一跳延迟换零入站端口,边缘场景的正确取舍。
- Device Plugin 把硬件变成 K8s 资源:机器人/相机 advertise 成
rlinf.io/device,Pod request 就被调度并注入控制 socket——具身智能的"硬件即 K8s 资源"抽象,让云端训练框架能像消费 GPU 一样消费机械臂。 - 双层证书(X.509 + SSH):X.509 管 mTLS+kube impersonation,SSH 证书管跨集群转发和用户到 Pod 的访问,role 分(admin/agent/peer/domain/ssh-guest)——一套 CA 出两种证书,覆盖所有鉴权场景。
它跟之前写的具身 Infra 系列正好互补:《从 LLM Infra 到具身智能 Infra》讲训练栈+数据管线+仿真集群,本篇讲这些之上的跨集群编排与硬件接入层——把云训练、边缘 rollout、跨集群通信、机器人调度串成一个声明式平台。这是具身智能走向生产化的一块关键基础设施。