pod 按需连接到 arthas tunnel server.
动机
arthas 虽然提供了便捷的访问控制台,但是他有2个地方比较麻烦,一个是他没有权限控制,一个是要在 pod 里面去执行,让 agent 连接到 arthas ,这就比较让人头疼了,因为平常肯定不会把进入容器的权限给到开发,我们也不可能每次都帮开发进去里面执行指令,不然他每次重新发版都要给他执行一次就很麻烦,于是我们就需要一个平台来对接 pod 和 arthas。
虽然可以让开发将 agent 集群到代码里面,但是这样就很浪费资源,很多时候根本不需要用的情况,他也一直连着。而且还会在真需要用的时候,出现 arthas 连不上或者黑屏的情况。所以我们干脆自己搞一个平台,然后权限也可控,操作也方便。
效果展示
pod管理
arthas管理
实现
实现原理:
先将 k8s 集群信息录入到数据库(后续还要添加集群管理的前端),然后使用 client-go 从数据库读取集群信息并把所有的集群 clientset 记录到内存里面,方便我们可以在任何时候调用。
连接 arthas 的过程利用 client-go 进入到容器里面获取进程 id ,然后再组合命令,再通过 client-go 远程调用容器执行 arthas 的 as.sh 脚本。
需要注意的点就是容器需要 bash
环境,基础镜像要用带 bash 的,不然 arthas 的脚本会无法执行。
执行完成后,将 agent_id 返回给前端,前端弹窗展示,如果不小心把弹窗关了,重新执行一次即可。
然后跳转或者手动选 arthas 环境查看,目前我们环境比较割裂,就只能通过不同的菜单来区分不同的环境,如果都是在同一个网络且用同一套 arthas 就不用那么麻烦了。
核心代码
后端代码片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
func ( p * PodService ) podExecCommand ( req * vo . PodListRequest ) ( string , string , error ) {
// 使用bytes.Buffer变量接收标准输出和标准错误
var (
containerName string
getAgentIdCmd string
getAgentIdCmdList [] string
arthasServer string = p . K8sClients . ArthasServersMap [ req . Env + req . Name ]
)
_ , ok := p . K8sClients . ClientsMap [ req . Env + req . Name ]
if ! ok {
return "" , "" , errors . New ( "所选环境或集群不存在" )
}
// 获取容器名,这个需要规范,我们都是容器名等于服务名,同时也等于工作负载名
pod , err := p . K8sClients . ClientsMap [ req . Env + req . Name ]. CoreV1 (). Pods ( req . Namespace ). Get ( context . Background (), req . PodName , metav1 . GetOptions {})
if err != nil {
return "" , "" , err
}
for _ , c := range pod . Spec . Containers {
r := regexp . MustCompile ( `\S+` )
res := ( r . FindAllString ( c . Name , - 1 ))
containerName = res [ 0 ]
break
}
var (
getPidCmd string = "ps -ef|grep " + containerName + "|grep -v grep|awk '{print $2}'"
getPidCmdList = [] string { "bash" , "-c" , getPidCmd }
)
// 获取容器内java程序的pid
pid , _ , err := execCommand ( p . K8sClients , req , containerName , getPidCmdList )
if err != nil {
return "" , "" , err
}
// 连接到arthas
getAgentIdCmd = "/opt/tools/arthas/as.sh --tunnel-server '" + arthasServer + "' -c 'session' " + strings . TrimSpace ( pid ) + "|grep AGENT_ID"
getAgentIdCmdList = [] string { "bash" , "-c" , getAgentIdCmd }
stdout , stderr , err := execCommand ( p . K8sClients , req , containerName , getAgentIdCmdList )
if err != nil {
return "" , "" , err
}
// 返回数据
return stdout , stderr , nil
}
前端代码片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
async function onAttachArthas ( row ) {
loading . value = true ;
// 深拷贝
const obj = JSON . parse ( JSON . stringify ( row ));
// 给proxy对象赋值
const pod = reactive ({
env : obj . env ,
name : obj . name ,
podName : obj . podName ,
namespace : obj . namespace
});
await podAttachArthas ( pod )
. then ( res => {
if ( res . success && res . data ) {
agent_id . value = res . data . result ;
ElMessageBox . alert ( "请复制id:\n" + agent_id . value , "连接成功" , {
customStyle : { "max-width" : "35%" },
// if you want to disable its autofocus
// autofocus: false,
confirmButtonText : "OK"
});
} else if ( res . data ) {
message ( res . data . cause , {
type : "error"
});
}
})
. catch ( err => {
message ( "连接失败,请重试 " + err , {
type : "warning"
});
})
. finally (() => {
loading . value = false ;
});
}
难点
前端在做 arthas ui 转发的时候,会碰到 https 跨域的问题,这个比较好解决,arthas UI 前面再加一层代理解决,还有就是 ws 变成 wss ,如果是自建的 nginx ,需要做一下配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
upstream darthas_server {
server 192.168.2.217:30358;
}
upstream darthas_server_ws {
server 192.168.2.217:32102;
}
map $http_upgrade $connection_upgrade {
default Upgrade;
'' close;
}
server {
listen 443 ssl;
server_name arthas.yourdomain.com.cn;
ssl_certificate ssl/yourssl.pem;
ssl_certificate_key ssl/yourssl.key;
location / {
proxy_set_header Host $host ;
proxy_set_header X-Forwarded-Proto $scheme ;
proxy_set_header X-Forwarded-Port $server_port ;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ;
proxy_pass http://darthas_server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade ;
proxy_set_header Connection $connection_upgrade ;
# 此项允许执行的 shell 窗口保持开启,最长可达15分钟。不使用此参数的话,默认1分钟后自动关闭。
proxy_read_timeout 900s;
proxy_buffering off;
}
}
server {
listen 7777 ssl;
server_name arthas.yourdomain.com.cn;
ssl_certificate ssl/yourssl.pem;
ssl_certificate_key ssl/yourssl.key;
location /ws {
proxy_set_header Host $host ;
proxy_set_header X-Forwarded-Proto $scheme ;
proxy_set_header X-Forwarded-Port $server_port ;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ;
proxy_pass http://darthas_server_ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade ;
proxy_set_header Connection $connection_upgrade ;
# 此项允许执行的 shell 窗口保持开启,最长可达15分钟。不使用此参数的话,默认1分钟后自动关闭。
proxy_read_timeout 900s;
proxy_buffering off;
}
}
server {
listen 80;
server_name arthas.yourdomain.com.cn;
return 301 https://$server_name$request_uri ;
}
最后
代码目前不进行开源,本人技术也有限,就自己玩玩,大家有兴趣的话可以自己尝试一下。