目录

Docker

在 Docker 里使用 JDK

很多时候,应用在 docker 里出现 arthas 无法工作的问题,是因为应用没有安装 JDK ,而是安装了 JRE 。如果只安装了 JRE,则会缺少很多 JAVA 的命令行工具和类库,Arthas 也没办法正常工作。下面介绍两种常见的在 Docker 里使用 JDK 的方式。

使用公开的 JDK 镜像

  • https://hub.docker.com/_/openjdk/

比如:

FROM openjdk:8-jdk

或者:

FROM openjdk:8-jdk-alpine

通过包管理软件来安装

比如:

# Install OpenJDK-8
RUN apt-get update && \
    apt-get install -y openjdk-8-jdk && \
    apt-get install -y ant && \
    apt-get clean;

# Fix certificate issues
RUN apt-get update && \
    apt-get install ca-certificates-java && \
    apt-get clean && \
    update-ca-certificates -f;

# Setup JAVA_HOME -- useful for docker commandline
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64/
RUN export JAVA_HOME

或者:

RUN yum install -y \
   java-1.8.0-openjdk \
   java-1.8.0-openjdk-devel

ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk/
RUN export JAVA_HOME

通过 Docker 快速入门

  1. 删除本地已有的math-game docker container(非必要)

    $ docker stop math-game || true && docker rm math-game || true
    
  2. 启动math-game

    $ docker run --name math-game -it hengyunabc/arthas:latest /bin/sh -c "java -jar /opt/arthas/math-game.jar"
    
  3. 启动arthas-boot来进行诊断

    $ docker exec -it math-game /bin/sh -c "java -jar /opt/arthas/arthas-boot.jar"
    * [1]: 9 jar
    
    [INFO] arthas home: /opt/arthas
    [INFO] Try to attach process 9
    [INFO] Attach process 9 success.
    [INFO] arthas-client connect 127.0.0.1 3658
    ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
    /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
    |  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
    |  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
    `--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'
    
    
    wiki: https://arthas.aliyun.com/doc
    version: 3.0.5
    pid: 9
    time: 2018-12-18 11:30:36
    

诊断 Docker 里的 Java 进程

docker exec -it  ${containerId} /bin/bash -c "wget https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar"

诊断 k8s 里容器里的 Java 进程

如果应用容器已经包含 shell、完整 JDK 和下载工具,可以直接进入应用容器启动 Arthas。

kubectl exec -it ${pod} --container ${containerName} -- /bin/bash -c "wget https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar"

从临时 debug 容器 attach

4.3.4在新窗口打开 开始,Arthas 可以从 kubectl debug 创建的临时容器 attach 应用容器里的 JVM。应用镜像使用 distroless,或者没有 shell、JDK 时,可以用这个方式把诊断工具临时加入正在运行的 Pod。

使用前需要满足下面的条件。

  • Pod 运行在 Linux 上。
  • Kubernetes 集群支持临时容器,容器运行时支持 kubectl debug --target。进入临时容器后必须能看到目标 JVM 的 PID。
  • 临时容器提供完整 JDK,并使用 Arthas 4.3.4 或更高版本。
  • 临时容器与目标 JVM 的 UID 不同时,临时容器需要以 root 身份运行,并能访问目标进程的 /proc/<pid>/root。目标 HotSpot 还必须允许 UID 0 的 attach 客户端。部分旧 JDK 只接受相同 UID/GID,此时需要让两个容器使用相同 UID/GID。

注意

下面的 --profile=sysadmin 会创建 privileged 临时容器。请只在获得授权且集群安全策略允许时使用。应用 Pod 强制以非 root 用户运行时,需要由集群管理员提供具备相应权限的调试配置。

先把临时容器加入正在运行的 Pod。${containerName} 是目标应用容器的名称。

kubectl debug -it ${pod} \
  --target=${containerName} \
  --image=amazoncorretto:8-alpine-jdk \
  --profile=sysadmin \
  -- sh

--target 让临时容器进入目标容器的 PID namespace。不同 Kubernetes 版本的配置方式可以参考 Kubernetes 调试运行中的 Pod在新窗口打开

进入临时容器后,下载并解压完整的 Arthas 4.3.4 发行包。

ARTHAS_VERSION=4.3.4
ARTHAS_HOME=/tmp/arthas-${ARTHAS_VERSION}

wget -qO /tmp/arthas.zip \
  "https://repo1.maven.org/maven2/com/taobao/arthas/arthas-packaging/${ARTHAS_VERSION}/arthas-packaging-${ARTHAS_VERSION}-bin.zip"
mkdir -p "${ARTHAS_HOME}"
unzip -q /tmp/arthas.zip -d "${ARTHAS_HOME}"

ps 查找目标 JVM 的 PID。下面以 PID 1 为例。

ps -ef

TARGET_PID=1
java -jar "${ARTHAS_HOME}/arthas-boot.jar" "${TARGET_PID}"

Arthas 会自动比较临时容器和目标 JVM 的 mount namespace。检测到两者不同时,Arthas 会暴露目标 JVM 的 attach socket,并把完整的 Arthas Home 复制到目标容器的临时目录。整个过程不需要两个容器挂载共享目录。

跨 mount namespace attach 成功后,目标 JVM 的 arthas.log 会记录下面的结构化日志。

event=arthas_attach status=success mode=cross-mount-namespace targetPid=... arthasHome=... network=... telnet=... http=...

执行 stop 会停止 Arthas,并清理复制到目标容器里的临时 Arthas Home。quitexit 只断开当前连接,不会停止 Arthas 或清理该目录。

如果临时容器中的 ps 看不到目标 JVM,说明容器运行时没有按 --target 共享目标进程,当前方式无法 attach。如果 attach 后的服务启动检查失败,终端会提示查看目标 JVM 的 arthas.log,可以从临时容器中查找日志。

find /proc/${TARGET_PID}/root -name arthas.log 2>/dev/null

从 sidecar 容器 attach

也可以把完整 JDK 和 Arthas 预装在常驻 sidecar 容器中,再从 sidecar attach 同一个 Pod 里的应用 JVM。创建 Pod 时必须设置 shareProcessNamespace: true在新窗口打开,否则 sidecar 看不到应用容器的 PID。这个字段只共享 PID namespace。两个容器仍使用独立的 mount namespace,Arthas 4.3.4 会通过 /proc/<pid>/root 处理文件访问,不需要共享 volume。Pod 内的容器已经共享 network namespace,因此也不需要 NET_ADMIN

优先让 sidecar 和应用 JVM 使用相同的 Linux UID 和 GID。普通 JVM、默认 /proc 安全策略和可写临时目录下,这条路径不需要增加 Linux capability。

spec:
  shareProcessNamespace: true
  containers:
    - name: app
      securityContext:
        runAsUser: 1000
        runAsGroup: 1000
    - name: arthas-sidecar
      image: your-registry/arthas-sidecar:4.3.4
      securityContext:
        runAsUser: 1000
        runAsGroup: 1000
        runAsNonRoot: true
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]

如果 sidecar 和应用 JVM 的 UID 不同,sidecar 必须以 UID 0 运行,并且目标 HotSpot 必须允许 root 客户端。较新的 OpenJDK attach listener在新窗口打开 接受相同 UID/GID 或 UID 0。上游 OpenJDK 8u 的实现在新窗口打开 仍要求 UID/GID 完全一致,部分厂商构建也可能有不同策略。这个校验发生在目标 JVM 内部,增加 Linux capability 无法绕过。目标 JVM 使用这类实现时,只能采用相同 UID/GID 的配置。

目标 HotSpot 支持 root 客户端时,为了覆盖首次 attach、不同权限的临时目录和 stop 清理,建议显式保留下面四项 capability。

配置或 capability用途
shareProcessNamespace: true让 sidecar 能看到应用 JVM 的 PID 和 /proc/<pid>
runAsUser: 0在目标 HotSpot 支持 root 客户端时通过跨 UID 校验
SYS_PTRACE通过 ptrace access check 读取 /proc/<pid>,并访问 /proc/<pid>/root 和 mount namespace 信息
KILLUID 不同时向尚未启动 attach listener 的 JVM 发送 SIGQUIT
DAC_OVERRIDE连接目标用户拥有的 0600 attach socket,并写入目标容器的临时目录
CHOWN把复制到目标容器的 Arthas Home 交给目标用户,使 stop 可以清理该目录

下面是跨 UID 场景的 securityContext 片段。它不需要 privileged: trueSYS_ADMINhostPIDhostPath

spec:
  shareProcessNamespace: true
  containers:
    - name: arthas-sidecar
      image: your-registry/arthas-sidecar:4.3.4
      securityContext:
        runAsUser: 0
        runAsGroup: 0
        allowPrivilegeEscalation: false
        seccompProfile:
          type: RuntimeDefault
        capabilities:
          drop: ["ALL"]
          add:
            - SYS_PTRACE
            - KILL
            - DAC_OVERRIDE
            - CHOWN

容器运行时的默认 capability 集合通常已经包含 KILLDAC_OVERRIDECHOWN,所以保留默认集合时往往只需要额外添加 SYS_PTRACE。上面的写法先 drop ALL 再逐项添加,结果不依赖运行时默认值。

注意

Kubernetes 的 Baseline 和 Restricted Pod Security Standards在新窗口打开 都不允许增加 SYS_PTRACE,Restricted 还禁止 UID 0。使用跨 UID 配置时,需要由集群管理员为这个受信任的工作负载提供例外或专用 namespace。共享 PID namespace 也会让 sidecar 看到同一 Pod 内其他进程的参数、环境变量和 /proc/<pid>/root,不要把这个 sidecar 开放给不受信任的用户。

Arthas attach 本身不调用 Kubernetes API,因此 sidecar 的 ServiceAccount 不需要额外 RBAC 权限。通过 kubectl exec 进入 sidecar 的操作人员仍需对应的 pods/exec 权限。

sidecar 镜像需要包含完整 JDK 和 Arthas 4.3.4 或更高版本。启用共享 PID namespace 后,应用进程通常不再是 PID 1,请先用 ps 查找实际 PID。

kubectl exec -it ${pod} --container arthas-sidecar -- sh

ps -ef
TARGET_PID=<java-pid>
java -jar /opt/arthas/arthas-boot.jar "${TARGET_PID}"

如果相同 UID/GID 的配置仍然无法 attach,检查目标 JVM 是否设置了 -XX:+DisableAttachMechanism,以及 seccomp、AppArmor、SELinux 或 /proc 挂载策略是否阻止了进程访问。目标 JVM 使用自定义 java.io.tmpdir 时,该目录也必须允许 attach 客户端写入。

把 Arthas 安装到基础镜像里

可以很简单把 Arthas 安装到你的 Docker 镜像里。

FROM openjdk:8-jdk-alpine

# copy arthas
COPY --from=hengyunabc/arthas:latest /opt/arthas /opt/arthas

如果想指定版本,可以查看具体的 tags:

https://hub.docker.com/r/hengyunabc/arthas/tags在新窗口打开