—— 基于 gRPC Stream + Etcd 的类ChatOps基础设施构建笔记(2026高并发版)
在运维规模达到数千台物理机/虚拟机,且混合云架构成为常态的今天,传统的 SSH 跳板机模式面临四个不可逾越的痛点:
authorized_keys 难以实现精细化权限管控(如仅允许特定用户执行特定脚本且带审计)。restart service 后,网络闪断即丢失执行结果,缺乏最终一致性保证。Clawdbot 的定位:一个常驻守护进程(Daemon),运行在每台目标主机上,通过 双向流式 gRPC(Bidirectional Streaming) 与中心 Control Plane 建立长连接,实现指令的即时下推与异步回执。
我们将 Clawdbot 架构划分为三层,避免逻辑耦合:
核心数据结构(Proto 定义):
syntax = "proto3";
service ClawdBot {
// 双向流,Agent 启动即建立
rpc Connect(stream AgentMessage) returns (stream ControlMessage);
}
message AgentMessage {
string agent_id = 1;
string hostname = 2;
// 心跳与任务回执复用同一通道
oneof payload {
Heartbeat ping = 3;
TaskResult result = 4;
RegisterInfo info = 5;
}
}
message ControlMessage {
string trace_id = 1; // 全链路追踪ID
string task_uuid = 2; // 幂等ID
string command = 3; // 原始命令或Base64编码脚本
int32 timeout_sec = 4;
int32 max_retry = 5; // 重试策略
}当 10000+ Agent 同时发起连接时,我们的 Control Plane 如何保持稳定且保证指令在 200ms 内下推到特定节点?以下是我们的三项关键实践:
高并发下,Linux 系统的 fd(文件描述符)和内存是首要瓶颈。
net.ipv4.tcp_keepalive_time=60 结合应用层 Ping-Pong 机制(间隔 30s)。若连续 3 次 Ping 无响应,服务端主动关闭连接并标记 Agent 为 Offline。bufconn 或复用池,将单连接内存占用从默认的 4MB 压缩至 ~128KB。服务端连接处理核心 Golang 代码片段:
// 使用 gRPC 拦截器限制并发连接数并设置读取超时
func (s *Server) Connect(stream pb.ClawdBot_ConnectServer) error {
// 1. 令牌桶限流,防止突发大量重连打爆 Selector
select {
case connToken <- struct{}{}:
defer func() { <-connToken }()
default:
return status.Error(codes.ResourceExhausted, "too many connections")
}
// 2. 设置 Recv 超时,若 Agent 30s 未发送任何包则断开
for {
msg, err := stream.Recv()
if err != nil {
// 清理注册表
s.registry.Deregister(agentID)
return err
}
// 处理消息...
}
}在分布式网络环境中,我们必须容忍网络抖动导致的重复下推。
task_uuid 由业务方生成(如 md5(timestamp + user + cmd))。Agent 侧维护一个 布隆过滤器(Bloom Filter) 缓存最近 5 分钟已执行成功的 UUID,接收到任务时先查询过滤器,若命中则直接返回成功且不重复执行,防止 rm -rf 等危险操作被误触发两次。Agent 侧幂等处理逻辑(伪代码):
class TaskExecutor:
def __init__(self):
self.dedup_cache = BloomFilter(capacity=10000, error_rate=0.001)
self.lock = threading.RLock()
def execute(self, task_uuid, command):
with self.lock:
if self.dedup_cache.contains(task_uuid):
logger.info(f"Duplicate task {task_uuid}, skipping execution")
return TaskResult(status="SUCCESS", note="Idempotent skipped")
# 执行命令(带超时控制)
proc = subprocess.run(command, shell=True, timeout=10, capture_output=True)
self.dedup_cache.add(task_uuid)
return TaskResult(status="SUCCESS" if proc.returncode==0 else "FAILED", output=proc.stdout)基于 IP 白名单的传统方式在动态 K8s 环境中已失效。我们采用 SPIFFE(Secure Production Identity Framework for Everyone) 理念:
join_token 向 Control Plane 注册,Control Plane 签发短期有效的 X.509 证书(有效期 24h),后续所有通信基于 mTLS 加密。(User, Action, Target_Agent_Label)。
devops 角色对带有 env=prod 标签的主机执行 delete 类命令。OPA 策略规则示例(rego):
package clawdbot.auth
default allow = false
allow {
input.user_role == "admin"
}
allow {
input.user_role == "devops"
input.action == "read"
not input.command == "rm -rf"
input.target_labels.env == "staging"
}
# 高危指令拦截
deny[msg] {
contains(input.command, "rm -rf /")
msg = "Destructive command blocked by policy"
}当 Control Plane 跨 AZ(可用区)部署时,网络分区可能导致 Agent 同时连接两个 Leader(脑裂)。
Active_Leader_ID 下发的时间戳最新的指令。/var/cache/clawdbot/pending.lock,待连接恢复后按顺序重放,确保运维动作不因中心化故障而完全瘫痪。uptime 命令,P99 完成时间 1.2s,无连接超时。restart nginx,端到端延迟(E2E)平均 45ms,命令丢失率 0%。Clawdbot 不仅仅是一个命令通道,更是我们构筑故障自愈(Autonomous Healing)底座的基石。通过将原子化指令与可观测性 Metrics(我们上一期提到的 eBPF 采集)结合,Clawdbot 现在能够响应 Prometheus 告警,自动执行预定义的“液化脚本”(如磁盘满时自动清理特定目录)。
下一步规划:引入 WebAssembly(WASM)插件系统,替代 Python 解释器,实现更轻量、更安全的用户自定义脚本隔离,彻底告别 Agent 重启导致的任务中断。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。