WEBKT

K8s Java 应用线上排查:无侵入挂载 Arthas 的四种硬核姿势

18 0 0 0

在实际的 Kubernetes 生产环境中,Java 应用出现 CPU 飙高、内存泄漏或接口响应慢(RT 极高)是家常便饭。很多时候,本地测试好好的代码,上线后在特定的并发流量下才会暴露问题。

这时候,阿里巴巴开源的诊断利器 Arthas 就派上用场了。但在 K8s 环境下,我们往往会面临很多限制:为了安全和镜像体积,生产镜像通常是精简过的(比如基于 Alpine 或 Distroless),没有安装 JDK(只有 JRE),更没有 tarwgetcurl 甚至 bash

如何在不重启 Pod、不重新打包镜像的前提下,优雅、快速地把 Arthas 挂载到目标容器中进行排查?本文分享四种实战中沉淀下来的硬核挂载姿势,按适用场景从易到难排列。


姿势一:常规容器直接注入(最简单,需容器内有 curl/wget)

如果你的基础镜像比较完整,或者在测试环境中,容器内置了 curlwget,且拥有 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
  • 极简镜像没有 wgetcurl

姿势二:利用 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(终极无侵入方案)

如果你的基础镜像是极其精简的 alpinedistroless,甚至连 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 后,直接执行 jpsps -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

诊断结束后,切记执行 stopshutdown 释放 Arthas 占用的资源,尤其是它对字节码做出的修改(Weave)。

stop

如果直接强行关闭连接,可能导致目标 JVM 的内存中残留未恢复的字节码,造成性能损耗。

运维架构说 KubernetesArthasJava

评论点评