K8s Java 应用线上排查:无侵入挂载 Arthas 的四种硬核姿势
在实际的 Kubernetes 生产环境中,Java 应用出现 CPU 飙高、内存泄漏或接口响应慢(RT 极高)是家常便饭。很多时候,本地测试好好的代码,上线后在特定的并发流量下才会暴露问题。
这时候,阿里巴巴开源的诊断利器 Arthas 就派上用场了。但在 K8s 环境下,我们往往会面临很多限制:为了安全和镜像体积,生产镜像通常是精简过的(比如基于 Alpine 或 Distroless),没有安装 JDK(只有 JRE),更没有 tar、wget、curl 甚至 bash。
如何在不重启 Pod、不重新打包镜像的前提下,优雅、快速地把 Arthas 挂载到目标容器中进行排查?本文分享四种实战中沉淀下来的硬核挂载姿势,按适用场景从易到难排列。
姿势一:常规容器直接注入(最简单,需容器内有 curl/wget)
如果你的基础镜像比较完整,或者在测试环境中,容器内置了 curl 或 wget,且拥有 JDK 环境,可以直接通过 kubectl exec 一键下载并运行。
# 进入 Pod 并下载运行 Arthas
kubectl exec -it <pod-name> -n <namespace> -c <container-name> -- sh -c "wget https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar"
进入交互界面后,选择对应的 Java 进程 PID(通常是 1),即可开始排查。
痛点:
- 生产环境往往禁用了外网访问,容器内无法直接下载
arthas-boot.jar。 - 极简镜像没有
wget或curl。
姿势二:利用 kubectl cp 离线传输(无外网环境首选)
如果生产集群是处于安全隔离的私有云或局域网,无法访问互联网,可以通过本地中转的方式,将下载好的 Arthas 压缩包拷贝到容器中。
1. 本地下载 Arthas 完整包
在一台能上网的机器上下载离线包并解压:
wget https://arthas.aliyun.com/download/arthas-packaging-3.7.1-bin.zip
unzip arthas-packaging-3.7.1-bin.zip -d arthas
2. 将文件拷贝至 Pod 临时目录
K8s 容器的 /tmp 目录通常是可写的,我们可以将解压后的目录整体拷贝过去:
kubectl cp ./arthas <namespace>/<pod-name>:/tmp/arthas -c <container-name>
3. 进入容器运行
kubectl exec -it <pod-name> -n <namespace> -c <container-name> -- sh
cd /tmp/arthas
java -jar arthas-boot.jar
注意:如果容器内运行 Java 的用户不是 root,kubectl cp 过去的文件可能需要修改权限(如 chown -R 1000:1000 /tmp/arthas),否则 Arthas 无法附加(attach)到目标 JVM 进程上。
姿势三:使用 K8s 1.25+ 临时容器 Ephemeral Containers(终极无侵入方案)
如果你的基础镜像是极其精简的 alpine、distroless,甚至连 sh 都没有,上述两种方法都会失效。这时可以利用 K8s 原生支持的 Ephemeral Containers(临时容器)。
临时容器共享目标容器的 PID 命名空间,我们可以在临时容器中运行带有 Arthas 和完整 JDK 工具链的镜像,去诊断目标容器。
1. 确保集群开启了 ShareProcessNamespace
为了能看到目标容器的 JVM 进程,目标 Pod 的 Spec 必须开启进程空间共享:
spec:
shareProcessNamespace: true
2. 使用 kubectl debug 注入临时容器
使用官方或自定义的包含 Arthas 的镜像(例如 hengyunabc/arthas:latest)挂载到目标 Pod:
kubectl debug -it <pod-name> \
-n <namespace> \
--image=hengyunabc/arthas:latest \
--target=<target-container-name>
--target参数非常关键,它能让临时容器与目标容器共享相同的 Process Namespace。
3. 在临时容器内诊断
进入临时容器的 shell 后,直接执行 jps 或 ps -ef,你会发现能看到目标容器的 Java 进程。此时运行:
as.sh
选择对应的进程,即可直接开始诊断。这种方式对原容器没有任何污染,诊断完退出临时容器,临时容器自动销毁。
姿势四:Pod 共享 Volume 挂载(适合平台化、DevOps 自动化)
如果你负责公司 K8s 诊断平台的建设,希望在排查页面点击“一键诊断”就能自动挂载 Arthas,可以采用 InitContainer + 共享 Volume + ShareProcessNamespace 的设计模式。
1. YAML 配置模板
在应用的 Deployment 模板中预先埋入共享 Volume 和辅助诊断容器:
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-app
spec:
template:
spec:
shareProcessNamespace: true # 关键:共享进程命名空间
volumes:
- name: arthas-volume
emptyDir: {}
initContainers:
- name: arthas-agent
image: hengyunabc/arthas:latest
command: ["sh", "-c", "cp -R /opt/arthas /data"] # 将镜像内的 arthas 拷贝到共享目录
volumeMounts:
- name: arthas-volume
mountPath: /data
containers:
- name: main-app
image: my-java-app:latest
volumeMounts:
- name: arthas-volume
mountPath: /opt/arthas # 挂载共享目录
2. 自动化触发排查
当需要排查时,运维系统或开发人员直接通过 exec 执行挂载在共享目录下的 Arthas:
kubectl exec -it <pod-name> -c main-app -- /opt/arthas/as.sh
这种方案的优势在于一次配置,处处可用,且不需要 Pod 具备外网访问能力,所有镜像文件在 Pod 启动时就已经通过私有镜像仓库准备完毕。
附:K8s 环境下 Arthas 常用排查指令备忘
挂载成功后,以下几个命令在解决 K8s Pod 级故障时最为高效:
1. 快速定位 CPU 飙高的线程
thread -n 3
展示当前 CPU 占用率最高的前 3 个线程,并打印出堆栈信息,可直接定位到具体代码行。
2. 监控方法执行耗时
如果接口变慢,可以用 trace 命令追踪链路耗时:
trace com.example.controller.UserController getUserInfo '#cost > 200'
上面这条命令表示:只有当 getUserInfo 方法执行耗时大于 200ms 时,才打印出该方法内部每一步的调用耗时。
3. 查看/修改线上配置
很多时候我们需要临时打开 DEBUG 日志查看报错,可以使用 logger 动态调整日志级别,避免重启 Pod:
logger --name com.example.dao --level debug
4. 退出 Arthas
诊断结束后,切记执行 stop 或 shutdown 释放 Arthas 占用的资源,尤其是它对字节码做出的修改(Weave)。
stop
如果直接强行关闭连接,可能导致目标 JVM 的内存中残留未恢复的字节码,造成性能损耗。