alpine容器无法启动arthas
最近在容器环境中,发现在 Java 进程是 1 号进程的情况下,无法使用 [arthas]。
提示 AttachNotSupportedException: Unable to get pid of LinuxThreads manager thread。具体操作和报错如下:
bash-4.4# java -jar arthas-boot.jar
Picked up JAVA_TOOL_OPTIONS: -Xms256m -Xmx256m
[INFO] JAVA_HOME: /usr/lib/jvm/java-1.8-openjdk/jre
[INFO] arthas-boot version: 3.6.7
[INFO] JAVA_TOOL_OPTIONS: -Xms256m -Xmx256m
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 1 /usr/cpp/ccreate-new-procurement-cloud-annex-1.0-SNAPSHOT.jar
1
[INFO] arthas home: /root/.arthas/lib/3.6.7/arthas
[INFO] Try to attach process 1
Picked up JAVA_TOOL_OPTIONS:
[ERROR] Start arthas failed, exception stack trace:
com.sun.tools.attach.AttachNotSupportedException: Unable to get pid of LinuxThreads manager thread
at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:86)
at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:78)
at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:250)
at com.taobao.arthas.core.Arthas.attachAgent(Arthas.java:102)
at com.taobao.arthas.core.Arthas.<init>(Arthas.java:27)
at com.taobao.arthas.core.Arthas.main(Arthas.java:161)
[INFO] Attach process 1 success.
[INFO] arthas-client connect 127.0.0.1 3658
Connect to telnet server error: 127.0.0.1 3658
java.net.ConnectException: Connection refused (Connection refused)
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
at org.apache.commons.net.SocketClient.connect(SocketClient.java:188)
at org.apache.commons.net.SocketClient.connect(SocketClient.java:209)
at com.taobao.arthas.client.TelnetConsole.process(TelnetConsole.java:306)
at com.taobao.arthas.client.TelnetConsole.main(TelnetConsole.java:166)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.taobao.arthas.boot.Bootstrap.main(Bootstrap.java:629)
正常的 attach 流程是什么样子的?
如下是在排查问题中,梳理出来的 jvm Attach 流程:
1. 查找 /tmp/.java_pid${pid} 这个 unix socket,如果存在则检查权限,然后建立连接。
2. 如果不存在则先创建 /proc/${pid}/cwd/.attach_pid${pid},开始通知 jvm 线程。
3. 首先判断是不是 LinuxThread如果是 LinuxThread则找到 LinuxThreadsManager,然后给其所有子进程发送 SIGQUIT.
4. 如果不是 LinuxThread,则直接给目标进程发送 SIGQUIT。
5. 目标进程收到信号后,创建 Attach Listener,监听 /tmp/.java_pid${pid}。
6. 开始正常的 socket 通信,根据通信的具体内容,可以是 dumpThread(jstack),也可以是加载 JavaAgent,比如上面提到的 arthas。
解决方法一: shell 模拟
pid=1 ;\
touch /proc/${pid}/cwd/.attach_pid${pid} && \
kill -SIGQUIT ${pid} && \
sleep 2 &&
ls /proc/${pid}/root/tmp/.java_pid${pid}
# 接下来就可以正常 java -jar arthas-boot.jar 挂arthas了
通过上面的操作后,Attach Listener 已经启动并且监听了路径,第二次 attach 就直接可以连接了;就可以按照正常的方式使用 arthas 了。
其中有一点需要注意,**一定需要提前创建 .attach_pid${pid} 文件,不然 jvm 会将这个信号交给默认的 sigaction 处理,对于 pid 1 来说,会导致容器退出!**
解决方法二: 设置启动参数
注:这种方式需要调整启动参数或者环境变量,需要重启应用/容器,可能会丢失业务现场。
Jvm 支持设置 -XX:+StartAttachListener,这样就能在启动 Jvm 的时候,自动启动 Attach Listener 线程并监听,也可以正常使用 arthas。
对于容器环境下,更加容易的做法是给容器添加环境变量 JAVA_TOOL_OPTIONS=-XX:+StartAttachListener,这样不用修改启动脚本也能达到效果。
解决方法三:上游优先,修改镜像
注:这种方式需要修改镜像。
OpenJDK 8 官方没有修复这个问题,所以如果直接使用 openjdk:8-jdk-alpine,是避免不了这个问题的。Docker 镜像仓库也有人讨论这个问题[6]。
OpenJDK 11 就已经解决了这个问题了(见源码[7]),不再对古旧的 LinuxThread 模型做判断,这样 arthas 也能工作。
不过 Alpine 官方仓库中的 OpenJDK 8 已经通过自己打 patch 的方式,修复了这个问题:
https://gitlab.alpinelinux.org/alpine/aports/-/issues/13032
作为比较知名的 JDK 发行版,也在 eclipse-temurin:8-jdk-alpine 中修复了这个问题,可以直接使用这个镜像。相关讨论见:https://github.com/adoptium/jdk8u/pull/8
参考: https://blog.csdn.net/yunqiinsight/article/details/129080195
评论区