!! 大家好,我是乔克,一个爱折腾的运维工程,一个睡觉都被自己丑醒的云原生爱好者。
作者:乔克
公众号:运维开发故事
博客:https://jokerbai.com
基于 Arthas 的多集群在线诊断系统设计与实现
当你的 Java 应用散落在十几个 Kubernetes 集群里,线上突然出了问题,你会怎么做?SSH 登录?跳板机?还是一个个 kubectl exec?
Arthas 是阿里巴巴开源的 Java 应用诊断利器,几乎成了 Java 开发者的"线上救命稻草"。但在云原生时代,微服务分散在多个隔离的 K8s 集群中,传统的"登录主机 → 执行 arthas"的方式面临三大难题:
-
1. 网络不通:业务集群在隔离网络中,运维人员无法直接 SSH 登录 -
2. 安全失控:谁连了哪台机器?执行了什么命令?完全没有审计 -
3. 效率低下:集群多、应用多,逐个 kubectl exec 费时费力
本文将完整介绍我们如何设计并实现一个统一的 Arthas 在线诊断平台,打通跨集群、免登录、全审计的 Java 诊断全链路,并深入剖析背后的核心代码实现。
一、我们到底要做什么?
1.1 一句话概括
在管理集群(A 集群)部署一个管理端,通过 Agent 代理组件接管各业务集群(B/C/…集群),运维人员在 Web 页面选择目标 Pod,点击"连接终端",即可获得一个完整的 Arthas 交互式终端——全程无需 SSH,无需 kubectl,所有操作有审计。
1.2 控制平面与数据平面分离
整个平台采用经典的 控制平面(Master)+ 数据平面(Agent) 架构:
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。核心代码结构如下:
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 创建集群时,系统自动生成凭证:
// 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.GVADB.Create(dc).Error
return plaintextSecretKey, err // 明文凭证仅在创建时返回一次!
}
凭证生成的底层实现:
// 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
// api/v1/diagPlatform/diagcluster.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 隧道时,执行挑战-响应握手协议,防止重放攻击和中间人攻击:
Master Agent
│ │
│ ◄──── WebSocket Connect ──────────│ 携带 ClusterID
│ │
│──── Challenge (16字节随机码) ──────►│ 30秒过期
│ │
│ ◄──── HMAC-SHA256 Response ───────│ HMAC(SecretKey, Challenge)
│ │
│──── 验证 bcrypt(Response, Hash) ──►│ 验证通过,返回 SessionID
│ │
核心实现:
// 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 签名:
// 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-Timestamp 和 X-Signature 传递,Agent 端校验通过后才处理请求。健康检查路径(/health)豁免签名,保证心跳探测不受影响。
四、WebSocket 隧道引擎
4.1 消息协议设计
Master 与 Agent 之间的通信使用统一的 JSON 帧格式,支持四种消息类型:
// 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 stringjson:"msg_id"// UUID,请求-响应匹配
MsgType stringjson:"msg_type"// 消息类型
Action stringjson:"action"// 动作类型
ClusterID stringjson:"cluster_id"// 集群标识
SessionID stringjson:"session_id"// 终端会话标识
}
这套协议覆盖了完整的应用场景:
|
|
|
|
|---|---|---|
|
|
auth_challenge |
|
|
|
auth_response |
|
|
|
HEARTBEAT |
|
|
|
STREAM |
|
4.2 连接池:sync.Map 双索引管理
连接池是隧道的核心数据结构,使用 sync.Map 实现无锁并发读写,维护 ClusterID 和 SessionID 的双向索引:
// 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 秒未心跳的僵尸连接 -
• 优雅断开:连接断开时从连接池移除,数据库状态置为离线
// 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 秒:
// 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.GVAREDIS.Expire(ctx, key, RouteTTL)
}
这套设计实现了无状态化——任何 Master 节点都可以通过 Redis 查询路由,将请求转发到持有 Agent 连接的节点。
五、三级数据通路的实现
5.1 整体数据通路
诊断终端的数据流是一条三级链路:
浏览器 xterm.js ◄──────► Master StreamingManager ◄──────► Agent ◄──────► Arthas 进程
(前端 WebSocket) (会话桥接 + 双 goroutine) (K8s exec)
5.2 连接信息下发
前端在连接终端前,先通过 REST API 获取连接参数:
// 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 签名参数:
// service/agent/http_client.go
funcDeriveWebSocketURL(httpAddr, secretKey string)string {
path := "/ws/v1/arthascmd"
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 的数据流,实现双向透传:
// 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 方向(用户键盘输入):
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 终端输出):
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 下载地址:
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 类型存储授权树:
// 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 uintgorm:"uniqueIndex"
ClusterAuthorizations ClusterAuthorizations MARKDOWN_HASHbe97d37a24479144439d61268c5e373eMARKDOWNHASH
}
一个权限规则的 JSON 示例:
{
"userId":1001,
"clusterAuthorizations":{
"cluster-beijing-prod":{
"default":["order-service","payment-service"],
"kube-system":[]
},
"cluster-shanghai-staging":{
"default":[""]
}
}
}
6.2 数据过滤:非超级用户全链路拦截
权限检查贯穿每一个资源查询 API。以获取 Namespace 列表为例:
// 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 自动从数据库解密注入:
// 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("clusterid = ?", 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
# 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 心跳监控定时任务
后台定时任务每轮对所有集群执行健康检查,自动更新在线状态:
// task/cluster_heartbeat_monitor.go
funcCheckClusterHeartbeat() {
var clusters []diagPlatform.DiagCluster
global.GVADB.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 终端连接与数据绑定
// 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,如下:
同时,支持给用户进行多集群的授权,授权之后的效果如下:
8.3.2 集群管理页面
创建集群的时候需要输入 Arthas下载地址和Agent地址,其中Arthas下载地址是为了目标容器下载Arthas所用,Agent地址用户管理端和目标集群进行交互,如下:
整体的集群管理页面如下:
可以在该页面进行集群添加、凭据重置以及获取在目标集群的部署脚本。
8.3.4 终端诊断页面
终端诊断页面的功能比较简单,就是选择集群、命名空间、应用建立链接进行诊断,如下:
当然,我们也在基于AI+MCP的方式实现对话诊断,避免不知道Arthas命令以及看不懂Arthas诊断的输出,提升诊断效率。只是目前这个功能还在初步开发和调试阶段。
九、写在最后
这个云原生 Java 诊断平台的核心思路其实并不复杂——用 WebSocket 隧道打通网络隔离,用 HMAC 签名保障通信安全,用 RBAC 控制操作边界。但把这三件事做扎实、做完善,需要大量工程细节的打磨:
-
• 凭证永远不明文落盘,数据库里存的是哈希和密文 -
• 每一个 HTTP 请求都带签名,每一个 WebSocket 连接都经过挑战握手 -
• 权限过滤不在一个点做,而是在每一层资源查询都做 -
• 连接池、会话池、路由表都有定时清理,杜绝资源泄漏
Arthas 本身是一个强大的工具,而我们的平台让它变得更安全、更易用、更可控。希望本文的架构设计和代码实现分析,能为构建类似平台的技术同学提供一些参考。
如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!
你还可以把我的公众号设为「星标」,这样当公众号文章更新时,你会在第一时间收到推送消息,避免错过我的文章更新。
我是 乔克,《运维开发故事》公众号团队中的一员,一线运维农民工,云原生实践者,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!








