基于 Arthas 的多集群在线诊断系统设计与实现


!! 大家好,我是乔克,一个爱折腾的运维工程,一个睡觉都被自己丑醒的云原生爱好者。 作者:乔克

公众号:运维开发故事

博客:jokerbai.com

基于 Arthas 的多集群在线诊断系统设计与实现

当你的 Java 应用散落在十几个 Kubernetes 集群里,线上突然出了问题,你会怎么做?SSH 登录?跳板机?还是一个个 kubectl exec?

Arthas 是阿里巴巴开源的 Java 应用诊断利器,几乎成了 Java 开发者的"线上救命稻草"。但在云原生时代,微服务分散在多个隔离的 K8s 集群中,传统的"登录主机 → 执行 arthas"的方式面临三大难题:

    1. 网络不通:业务集群在隔离网络中,运维人员无法直接 SSH 登录
    1. 安全失控:谁连了哪台机器?执行了什么命令?完全没有审计
    1. 效率低下:集群多、应用多,逐个 kubectl exec 费时费力

本文将完整介绍我们如何设计并实现一个统一的 Arthas 在线诊断平台,打通跨集群、免登录、全审计的 Java 诊断全链路,并深入剖析背后的核心代码实现。


一、我们到底要做什么?

1.1 一句话概括

在管理集群(A 集群)部署一个管理端,通过 Agent 代理组件接管各业务集群(B/C/...集群),运维人员在 Web 页面选择目标 Pod,点击"连接终端",即可获得一个完整的 Arthas 交互式终端------全程无需 SSH,无需 kubectl,所有操作有审计。

1.2 控制平面与数据平面分离

整个平台采用经典的 控制平面(Master)+ 数据平面(Agent) 架构:

e943f73bf366d8c345c55a9963485932 MD5

Master(管理端后端) 的核心职责:

  • • 集群注册与凭证管理
  • • 通过 HMAC 签名的 HTTP 请求与 Agent 通信(资源查询)
  • • 通过 WebSocket 隧道进行终端 I/O 透传
  • • 三级 RBAC 权限控制(集群 → 命名空间 → 工作负载)
  • • 心跳监控与审计日志

Agent(业务集群代理) 的核心职责:

  • • 暴露 HTTP API(查询 Namespace / Deployment / Pod / Container)
  • • 执行 Arthas 下载与 JVM Attach
  • • 建立终端 WebSocket 双向流
  • • 仅持有本集群的只读 K8s ServiceAccount Token

二、技术栈与代码结构

后端基于 Go + Gin 框架(gin-vue-admin 脚手架),前端基于 Vue 3 + Element Plus + xterm.js。核心代码结构如下:

bash 复制代码
server/
├── model/diagPlatform/           # 数据模型层
│   ├── diag_cluster.go           # 集群配置模型
│   └── diag_permission_rule.go   # 权限规则模型(三级授权)
├── service/diagPlatform/         # 业务逻辑层
│   └── diag_cluster.go           # 集群 CRUD、凭证管理、部署脚本生成
├── service/agent/                # Agent 通信层
│   └── http_client.go            # HMAC 签名 HTTP 客户端
├── core/websocket/               # WebSocket 核心引擎
│   ├── handler.go                # Agent 连接处理器(握手 + 消息分发)
│   ├── pool.go                   # 连接池管理(sync.Map 双索引)
│   ├── client.go                 # Master→Agent 的 WebSocket 客户端
│   ├── redis_routing.go          # Redis 路由表(多节点扩展)
│   ├── auth/handshake.go         # 挑战-响应认证协议
│   └── streaming/streaming.go    # 终端会话管理器(I/O 双向透传)
├── model/websocket/message.go    # WebSocket 消息协议定义
├── api/v1/diagPlatform/          # API 控制器
│   ├── diag_cluster.go           # 集群管理 API
│   ├── websocket.go              # 诊断操作 API(资源查询 + 连接信息)
│   └── diag_permission_rule.go   # 权限管理 API
├── router/diagPlatform/          # 路由注册
└── task/cluster_heartbeat_monitor.go  # 心跳监控定时任务

三、三道防线

在线诊断平台最敏感的问题不是"能不能连通",而是"会不会被滥用"。我们从凭证管理、双向认证、传输加密三个层面构建了纵深防御体系。

3.1 第一道防线:预注册凭证机制

放弃"客户端自主注册"的模式,采用管理端预生成,Agent 持证上岗。管理员在 UI 创建集群时,系统自动生成凭证:

scss 复制代码
// service/diagPlatform/diag_cluster.go
func(dcService *DiagClusterService) CreateDiagCluster(dc *diagPlatform.DiagCluster) (plaintextSecretKey string, err error) {
// 1. 生成集群唯一标识(Snowflake 算法,有序且全局唯一)
    dc.ClusterId = utils.GenerateClusterID()

// 2. 生成 32 字节强随机凭证(crypto/rand)
    plaintextSecretKey = utils.GenerateSecretKey()

// 3. bcrypt 哈希存储(抗彩虹表,用于 Agent→Master 握手验证)
    dc.SecretHash = utils.BcryptHash(plaintextSecretKey)

// 4. AES 加密存储明文(用于 Master→Agent 的 HMAC 签名)
    encryptedSecretKey, err := utils.Encrypt([]byte(plaintextSecretKey))
    dc.SecretKeyEncrypted = encryptedSecretKey

// 5. 初始状态为离线
    onlineStatus := 0
    dc.OnlineStatus = &onlineStatus

    err = global.GVA_DB.Create(dc).Error
return plaintextSecretKey, err  // 明文凭证仅在创建时返回一次!
}

凭证生成的底层实现:

go 复制代码
// utils/credential.go
funcGenerateSecretKey()string {
    randomBytes := make([]byte, 32)
if _, err := rand.Read(randomBytes); err != nil {
panic("failed to generate random secret key: " + err.Error())
    }
return base64.StdEncoding.EncodeToString(randomBytes)
}

关键设计

  • • 明文 SecretKey 仅在创建时返回一次,后续永远不可查询
  • • 数据库中同时存储 SecretHash(bcrypt)和 SecretKeyEncrypted(AES),前者用于验证,后者用于 Master→Agent 签名
  • • API 层强制禁止手动输入 ClusterId 和 SecretKey
bash 复制代码
// api/v1/diagPlatform/diag_cluster.go ------ 安全校验
if dc.ClusterId != "" {
    response.FailWithMessage("禁止手动输入 ClusterId,系统将自动生成", c)
return
}
if dc.SecretHash != "" {
    response.FailWithMessage("禁止手动输入凭证,系统将自动生成 SecretKey", c)
return
}

凭证重置功能让旧 Agent 立即失效(重置后新 SecretKey 覆盖旧值,旧 Agent 的 HMAC 签名将无法通过验证)。

3.2 第二道防线:挑战-响应握手协议

Agent 连接 Master 的 WebSocket 隧道时,执行挑战-响应握手协议,防止重放攻击和中间人攻击:

scss 复制代码
Master                              Agent
  │                                    │
  │  ◄──── WebSocket Connect ──────────│  携带 ClusterID
  │                                    │
  │──── Challenge (16字节随机码) ──────►│  30秒过期
  │                                    │
  │  ◄──── HMAC-SHA256 Response ───────│  HMAC(SecretKey, Challenge)
  │                                    │
  │──── 验证 bcrypt(Response, Hash) ──►│  验证通过,返回 SessionID
  │                                    │

核心实现:

go 复制代码
// core/websocket/auth/handshake.go

// 生成 16 字节随机挑战码
funcGenerateChallenge() (string, error) {
    randomBytes := make([]byte, ChallengeLength)  // 16 bytes
if _, err := rand.Read(randomBytes); err != nil {
return"", err
    }
return base64.StdEncoding.EncodeToString(randomBytes), nil
}

// Master 验证 Agent 的响应
func(hm *HandshakeManager) VerifyResponse(clusterID, response string) (*diagPlatform.DiagCluster, error) {
    state, ok := hm.pendingHandshakes[clusterID]
// 挑战码 30 秒过期,防止重放攻击
if time.Now().After(state.ExpiresAt) {
delete(hm.pendingHandshakes, clusterID)
returnnil, ErrChallengeExpired
    }
// 使用 bcrypt 校验响应(时序安全)
if !utils.BcryptCheck(response, state.ClusterInfo.SecretHash) {
delete(hm.pendingHandshakes, clusterID)
returnnil, ErrInvalidResponse
    }
return state.ClusterInfo, nil
}

安全特性

  • • 每次连接生成唯一挑战码,30 秒过期
  • • 使用 crypto/rand 生成强随机数
  • • bcrypt 内置 salt,无需额外存储
  • • 过期的握手状态会被定时清理

3.3 第三道防线:HMAC 请求签名

Master 向 Agent 发送 HTTP 请求(如查询 Pod 列表)时,每个请求都携带 HMAC-SHA256 签名:

go 复制代码
// service/agent/http_client.go

funcSignRequest(method, path string, body []byte, secretKey string) (timestamp, signature string) {
    timestamp = fmt.Sprintf("%d", time.Now().Unix())

// 请求体的 SHA-256 摘要
    bodyHashSum := sha256.Sum256(body)
    bodyHashHex := hex.EncodeToString(bodyHashSum[:])

// 签名串:时间戳\n方法\n路径\n体摘要
    signingString := fmt.Sprintf("%s\n%s\n%s\n%s", timestamp, method, path, bodyHashHex)

// HMAC-SHA256 计算签名
    mac := hmac.New(sha256.New, []byte(secretKey))
    mac.Write([]byte(signingString))
    signature = hex.EncodeToString(mac.Sum(nil))

return timestamp, signature
}

签名通过 HTTP 头 X-TimestampX-Signature 传递,Agent 端校验通过后才处理请求。健康检查路径(/health)豁免签名,保证心跳探测不受影响。


四、WebSocket 隧道引擎

4.1 消息协议设计

Master 与 Agent 之间的通信使用统一的 JSON 帧格式,支持四种消息类型:

go 复制代码
// model/websocket/message.go

const (
    MsgTypeREQ       = "REQ"// 请求消息,等待响应
    MsgTypeRSP       = "RSP"// 响应消息,匹配 msg_id
    MsgTypeSTREAM    = "STREAM"// 流式数据,终端 I/O
    MsgTypeHEARTBEAT = "HEARTBEAT"// 心跳消息,维持连接
)

type Message struct {
    Header MessageHeader `json:"header"`
    Body   MessageBody   `json:"body"`
}

type MessageHeader struct {
    MsgID     string`json:"msg_id"`// UUID,请求-响应匹配
    MsgType   string`json:"msg_type"`// 消息类型
    Action    string`json:"action"`// 动作类型
    ClusterID string`json:"cluster_id"`// 集群标识
    SessionID string`json:"session_id"`// 终端会话标识
}

这套协议覆盖了完整的应用场景:

方向 Action 用途
Master → Agent auth_challenge 下发挑战码
Agent → Master auth_response 返回 HMAC 响应
Agent → Master HEARTBEAT 心跳保活
双向 STREAM 终端 I/O 数据流

4.2 连接池:sync.Map 双索引管理

连接池是隧道的核心数据结构,使用 sync.Map 实现无锁并发读写,维护 ClusterID 和 SessionID 的双向索引:

scss 复制代码
// core/websocket/pool.go

type ConnectionPool struct {
    connections sync.Map  // clusterID -> *AgentConnection
    sessionMap  sync.Map  // sessionID -> *AgentConnection
}

func(cp *ConnectionPool) AddConnection(clusterID string, conn *AgentConnection) {
    cp.connections.Store(clusterID, conn)
    cp.sessionMap.Store(conn.SessionID, conn)
}

连接的生命周期管理:

  • 注册:Agent 握手成功后加入连接池,状态置为在线
  • 心跳维持 :每收到 HEARTBEAT 更新 LastHeartbeat 时间戳
  • 超时清理:定时器每 30 秒扫描,清理超过 60 秒未心跳的僵尸连接
  • 优雅断开:连接断开时从连接池移除,数据库状态置为离线
scss 复制代码
// core/websocket/handler.go

funconAgentConnected(clusterID, sessionID string) {
// 更新数据库状态为在线
    global.GVA_DB.Model(&diagPlatform.DiagCluster{}).
        Where("cluster_id = ?", clusterID).
        Update("online_status", 1)

// 多节点部署时写入 Redis 路由表
if GlobalRedisRoutingManager.IsEnabled() {
        GlobalRedisRoutingManager.WriteRoute(clusterID, sessionID)
    }
}

funcStartHeartbeatMonitor() {
    ticker := time.NewTicker(30 * time.Second)
forrange ticker.C {
        cleaned := GlobalConnectionPool.CleanupStaleConnections(HeartbeatTimeout)
        wsAuth.GlobalHandshakeManager.CleanupExpiredHandshakes()
// ...Redis 路由一致性校验
    }
}

4.3 Redis 路由:支持多节点水平扩展

当 Master 水平扩展为多节点时,需要知道某个 Agent 连接在哪个节点上。Redis 路由表以 cluster:{id} 为 Key,存储 SessionID、连接时间、心跳时间,TTL 为 90 秒:

go 复制代码
// core/websocket/redis_routing.go

func(rrm *RedisRoutingManager) WriteRoute(clusterID, sessionID string) error {
    key := rrm.buildKey(clusterID)  // "cluster:{clusterID}"
    err := global.GVA_REDIS.HSet(ctx, key, map[string]interface{}{
"session_id":    sessionID,
"connected_at":  now,
"last_heartbeat": now,
    }).Err()
// TTL 90 秒,心跳续期
    global.GVA_REDIS.Expire(ctx, key, RouteTTL)
}

这套设计实现了无状态化------任何 Master 节点都可以通过 Redis 查询路由,将请求转发到持有 Agent 连接的节点。


五、三级数据通路的实现

5.1 整体数据通路

诊断终端的数据流是一条三级链路:

scss 复制代码
浏览器 xterm.js  ◄──────►  Master StreamingManager  ◄──────►  Agent  ◄──────►  Arthas 进程
   (前端 WebSocket)          (会话桥接 + 双 goroutine)        (K8s exec)

5.2 连接信息下发

前端在连接终端前,先通过 REST API 获取连接参数:

go 复制代码
// api/v1/diagPlatform/websocket.go

func(wsApi *WebSocketApi) GetAgentConnectInfo(c *gin.Context) {
    clusterID := c.Param("clusterId")

// 1. 获取 Agent 地址
    agentAddr, _ := agentSvc.GlobalAgentClient.GetAgentAddress(clusterID)

// 2. 获取 Arthas 下载地址(支持不同集群配置不同源)
    arthasUrl, _ := agentSvc.GlobalAgentClient.GetArthasUrl(clusterID)

// 3. 解密获取 SecretKey,生成 HMAC 签名的 WebSocket URL
    secretKey, _ := agentSvc.GlobalAgentClient.GetSecretKey(clusterID)
    wsURL := agentSvc.DeriveWebSocketURL(agentAddr, secretKey)

// 4. 生成 Arthas 密码(基于 SecretKey 派生)
    arthasPassHash := sha256.Sum256([]byte("arthasPassword" + secretKey))

    response.OkWithData(gin.H{
"websocketUrl":         wsURL,       // HMAC 签名的 WS 地址
"arthasTarDownloadUrl": arthasUrl,   // Arthas 下载源
"arthasPassword":       arthasPassword,
    }, c)
}

WebSocket URL 通过 DeriveWebSocketURL 自动加上 HMAC 签名参数:

go 复制代码
// service/agent/http_client.go

funcDeriveWebSocketURL(httpAddr, secretKey string)string {
    path := "/ws/v1/arthas_cmd"
    timestamp, signature := SignRequest(http.MethodGet, path, nil, secretKey)
    query := fmt.Sprintf("?timestamp=%s&signature=%s", timestamp, signature)
// http:// → ws://,https:// → wss://
    ...
}

5.3 会话桥接引擎

当前端建立 WebSocket 连接后,StreamingManager 创建一个 TerminalSession,用两个 goroutine 分别监听前端和 Agent 的数据流,实现双向透传:

go 复制代码
// core/websocket/streaming/streaming.go

type TerminalSession struct {
    SessionID    string
    ClusterID    string
    FrontendConn *gorillaWs.Conn              // 浏览器 WebSocket
    AgentConn    *wsCore.AgentClientConnection // Agent WebSocket
    Active       bool
}

func(sm *StreamingManager) ActivateSession(sessionID string, frontendConn *gorillaWs.Conn) error {
    session.FrontendConn = frontendConn
    session.Active = true

// 启动两个 goroutine 实现双向数据转发
go sm.handleFrontendStream(session)  // 前端 → Agent
go sm.handleAgentStream(session)      // Agent → 前端
}

前端 → Agent 方向(用户键盘输入):

scss 复制代码
func(sm *StreamingManager) handleFrontendStream(session *TerminalSession) {
for {
        _, messageBytes, err := conn.ReadMessage()    // 读取前端消息
        msg, _ := websocket.ParseMessage(messageBytes)
        msg.Header.ClusterID = session.ClusterID
        msg.Header.SessionID = session.SessionID

        forwardBytes, _ := msg.ToJSON()
        agentConn.WriteMessage(TextMessage, forwardBytes)  // 转发给 Agent
    }
}

Agent → 前端方向(Arthas 终端输出):

scss 复制代码
func(sm *StreamingManager) handleAgentStream(session *TerminalSession) {
for {
        _, messageBytes, err := agentConn.ReadMessage()   // 读取 Agent 消息
        msg, _ := websocket.ParseMessage(messageBytes)

if msg.Header.MsgType == websocket.MsgTypeSTREAM {
            conn.WriteMessage(TextMessage, messageBytes)  // 转发给前端
        }
    }
}

会话的自动生命周期管理

  • • 创建时设置 30 分钟过期
  • • 定时器每 5 分钟清理过期会话
  • • 任意一端断开时自动关闭另一端连接并清理资源

5.4 Arthas 注入流程

当 StreamingManager 连接 Agent 后,会发送注入请求,携带目标 Pod 信息和 Arthas 下载地址:

go 复制代码
func(sm *StreamingManager) RequestArthasAttachViaAgent(...) error {
// 连接 Agent 的 WebSocket
    agentConn, err := wsCore.ConnectToAgent(wsURL, session.ClusterID, WebSocketConnectTimeout)

// 发送注入请求
    connectReq := websocket.AgentConnectRequest{
        Type: "connect",
        Data: websocket.AgentConnectDataBody{
            Namespace:            namespace,
            Deployment:           deployment,
            Pod:                  podName,
            Container:            container,
            ArthasTarDownloadUrl: arthasUrl,  // 差异化下载源
        },
    }
    agentConn.WriteMessage(TextMessage, msgBytes)

// 等待 Agent 确认注入成功
    _, respBytes, err := agentConn.ReadMessage()
var resp websocket.AgentConnectResponse
    json.Unmarshal(respBytes, &resp)
}

六、权限管控:三级 RBAC 精细授权

6.1 权限模型设计

平台实现了 集群 → 命名空间 → 工作负载 三级权限粒度,使用 JSON 类型存储授权树:

go 复制代码
// model/diagPlatform/diag_permission_rule.go

// 最终授权结构:clusterId → namespace → []deployments
type ClusterAuthorizations map[string]NamespaceDeployments
type NamespaceDeployments map[string][]string

type DiagPermissionRule struct {
    global.GVA_MODEL
    UserId                uint`gorm:"uniqueIndex"`
    ClusterAuthorizations ClusterAuthorizations `gorm:"type:json"`
}

一个权限规则的 JSON 示例:

ruby 复制代码
{
"userId":1001,
"clusterAuthorizations":{
"cluster-beijing-prod":{
"default":["order-service","payment-service"],
"kube-system":[]
},
"cluster-shanghai-staging":{
"default":["*"]
}
}
}

6.2 数据过滤:非超级用户全链路拦截

权限检查贯穿每一个资源查询 API。以获取 Namespace 列表为例:

go 复制代码
// api/v1/diagPlatform/websocket.go

func(wsApi *WebSocketApi) GetNamespaceList(c *gin.Context) {
    clusterID := c.Param("clusterId")
    userId := utils.GetUserID(c)
    authorityId := utils.GetUserAuthorityId(c)

// 超级用户直接放行
if !pcService.IsSuperuser(authorityId) {
// 普通用户查询可访问的 Namespace
        accessibleNamespaces, _ := pcService.GetAccessibleNamespaces(userId, clusterID)
iflen(accessibleNamespaces) == 0 {
            c.JSON(http.StatusForbidden, gin.H{"msg": "No permission to access this cluster"})
return
        }
    }

// 调用 Agent 获取完整列表
    namespaces, _ := agentSvc.GlobalAgentClient.GetNamespaces(clusterID)

// 过滤掉无权限的 Namespace
if !pcService.IsSuperuser(authorityId) {
        filtered := make([]agentSvc.NamespaceItem, 0)
for _, ns := range namespaces {
if accessibleMap[ns.Name] {
                filtered = append(filtered, ns)
            }
        }
        namespaces = filtered
    }

    response.OkWithData(gin.H{"namespaces": namespaces}, c)
}

同样的过滤逻辑也应用在 Deployment 列表、Pod 列表和集群列表的查询中,确保用户看到的资源都是其有权限访问的


七、集群生命周期:一键部署与心跳监控

7.1 部署脚本自动生成

管理员创建集群后,可以一键生成完整的 K8s 部署 YAML,AUTH_SECRET_KEY 自动从数据库解密注入:

go 复制代码
// service/diagPlatform/diag_cluster.go

func(dcService *DiagClusterService) GenerateDeployScript(clusterId string, image string) (script string, err error) {
var cluster diagPlatform.DiagCluster
    global.GVA_DB.Where("cluster_id = ?", clusterId).First(&cluster)

// 解密获取明文凭证
    plaintextBytes, _ := utils.Decrypt(cluster.SecretKeyEncrypted)
    authSecretKey := string(plaintextBytes)

// 镜像优先级:参数 > 全局配置 > 默认值
    resolvedImage := image
if resolvedImage == "" {
        resolvedImage = global.GVA_CONFIG.Agent.Image
    }
if resolvedImage == "" {
        resolvedImage = defaultAgentImage
    }

    script = generateK8sDeployYAML(authSecretKey, resolvedImage)
return script, nil
}

生成的 YAML 包含完整的 K8s 资源链:

  • Deployment:双副本 + 反亲和调度(跨可用区)+ 健康探针 + 优雅停机
  • Service + Ingress:暴露 Agent 的 HTTP/WebSocket 端口
  • ServiceAccount + ClusterRole + ClusterRoleBinding:最小权限 RBAC
less 复制代码
# RBAC 精简到仅必要的操作
rules:
-apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "delete"]
-apiGroups: [""]
resources: ["pods/exec"]      # 执行 exec 所必需
verbs: ["create"]
-apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list"]
-apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list"]

7.2 心跳监控定时任务

后台定时任务每轮对所有集群执行健康检查,自动更新在线状态:

scss 复制代码
// task/cluster_heartbeat_monitor.go

funcCheckClusterHeartbeat() {
var clusters []diagPlatform.DiagCluster
    global.GVA_DB.Find(&clusters)

    heartbeatTimeoutThreshold := 60 * time.Second

for _, cluster := range clusters {
// 未配置 Agent 地址的集群直接标记离线
if cluster.AgentAddress == "" {
            diagClusterService.UpdateClusterOnlineStatus(cluster.ClusterId, 0)
continue
        }

// 执行健康检查(调用 Agent 的 /health 接口)
        err := agentSvc.GlobalAgentClient.HealthCheck(cluster.ClusterId)

if err != nil {
// 健康检查失败 → 标记离线
            diagClusterService.UpdateClusterOnlineStatus(cluster.ClusterId, 0)
        } else {
// 健康检查成功 → 标记在线 + 更新心跳时间
            diagClusterService.UpdateClusterOnlineStatus(cluster.ClusterId, 1)
            global.GVA_DB.Model(&diagPlatform.DiagCluster{}).
                Where("cluster_id = ?", cluster.ClusterId).
                Update("last_heartbeat", time.Now())
        }

// 心跳超时检测
if cluster.LastHeartbeat != nil {
if time.Since(*cluster.LastHeartbeat) > heartbeatTimeoutThreshold {
                diagClusterService.UpdateClusterOnlineStatus(cluster.ClusterId, 0)
            }
        }
    }
}

八、前端实现:xterm.js 终端集成

前端使用 Vue 3 + xterm.js 实现 Web 终端,核心流程:

8.1 资源选择链

用户依次选择 集群 → 命名空间 → Deployment → Pod → 容器,每一步选择都会触发下一级的资源列表加载。非 Running 状态的 Pod 会被禁用。

8.2 终端连接与数据绑定

less 复制代码
// view/diagPlatform/webTerminal/index.vue(核心逻辑精简)

import { Terminal } from'xterm'
import { FitAddon } from'xterm-addon-fit'

// 初始化 xterm 终端
constinitTerminal = () => {
  terminal.value = newTerminal({
theme: { background: '#1a1a2e', foreground: '#e0e0e0', cursor: '#ffd700' }
  })

// 用户输入 → WebSocket → Agent → Arthas
  terminal.value.onData((data) => {
if (ws.value.readyState === WebSocket.OPEN) {
      ws.value.send(JSON.stringify({
type: 'stdin',
data: { content: btoa(unescape(encodeURIComponent(data))) }  // Base64 编码
      }))
    }
  })
}

// 建立 WebSocket 连接
constconnectWebSocket = () => {
  ws.value = newWebSocket(connectInfo.value.websocketUrl)

  ws.value.onopen = () => {
// 发送连接请求,携带 Pod 信息和 Arthas 下载地址
    ws.value.send(JSON.stringify({
type: 'connect',
data: {
namespace: selectorForm.value.namespace,
pod: selectorForm.value.podName,
container: selectorForm.value.containerName,
arthasTarDownloadUrl: connectInfo.value.arthasTarDownloadUrl,
cols: terminal.value.cols,
rows: terminal.value.rows
      }
    }))
  }

  ws.value.onmessage = handleWebSocketMessage  // Arthas 输出 → xterm 渲染
}

8.3 效果展示

8.3.1 授权页面

为了安全,可以给用户细粒度的授权到某个Deployment,如下:

9154ddc04616c564d91c495ac53a39c1 MD5

同时,支持给用户进行多集群的授权,授权之后的效果如下:

3b262a7c897a5ea247fd8ded6ed5ecf6 MD5

8.3.2 集群管理页面

创建集群的时候需要输入 Arthas下载地址和Agent地址 ,其中Arthas下载地址 是为了目标容器下载Arthas所用,Agent地址用户管理端和目标集群进行交互,如下:

7f31dc9d9555949af0c75448bb3c1b74 MD5

整体的集群管理页面如下:

e48e08832a8ddd540c7e979563f7b62f MD5

可以在该页面进行集群添加凭据重置 以及获取在目标集群的部署脚本

8.3.4 终端诊断页面

终端诊断页面的功能比较简单,就是选择集群、命名空间、应用建立链接进行诊断,如下:

4ed1dd261811710701ce5583348b4828 MD5

当然,我们也在基于AI+MCP的方式实现对话诊断,避免不知道Arthas命令以及看不懂Arthas诊断的输出,提升诊断效率。只是目前这个功能还在初步开发和调试阶段。



九、写在最后

这个云原生 Java 诊断平台的核心思路其实并不复杂------用 WebSocket 隧道打通网络隔离,用 HMAC 签名保障通信安全,用 RBAC 控制操作边界。但把这三件事做扎实、做完善,需要大量工程细节的打磨:

  • • 凭证永远不明文落盘,数据库里存的是哈希和密文
  • • 每一个 HTTP 请求都带签名,每一个 WebSocket 连接都经过挑战握手
  • • 权限过滤不在一个点做,而是在每一层资源查询都做
  • • 连接池、会话池、路由表都有定时清理,杜绝资源泄漏

Arthas 本身是一个强大的工具,而我们的平台让它变得更安全、更易用、更可控。希望本文的架构设计和代码实现分析,能为构建类似平台的技术同学提供一些参考。

最后,求关注。如果你还想看更多优质原创文章,欢迎关注我们的公众号「运维开发故事」。


我是 乔克,《运维开发故事》公众号团队中的一员,一线运维农民工,云原生实践者,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!

相关推荐
Patrick_Wilson2 天前
从「改个端口」到 502:Next.js on k8s 的容器端口、Service 映射与 env 覆盖
docker·kubernetes·next.js
探索云原生2 天前
K8s 1.36 这个 GA 特性,把 initContainer 拉模型的 hack 干掉了
ai·云原生·kubernetes
Java之美3 天前
一次k8s升级引发的DevicePlugin注册失败
云原生·kubernetes
java_cj10 天前
深入kube-apiserver认证机制:从Bearer Token到mTLS的完整认证链解析
linux·运维·服务器·云原生·容器·kubernetes
qq_4523962310 天前
第十三篇:《K8s 安全基础:RBAC、ServiceAccount、Pod Security》
java·安全·kubernetes
睡不醒男孩03082310 天前
云原生运维实战:高并发架构下的云原生可观测性、韧性降级与自动化干预体系
数据库·kubernetes·高并发·prometheus·devops·sre·缓存调优
qq_4523962310 天前
第十四篇:《K8s 网络模型与 CNI 插件(Calico、Flannel、Cilium)》
网络·kubernetes·php
Hadoop_Liang10 天前
Kubernetes 应用 HTTPS 安全访问配置实践
https·kubernetes