欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

在Docker(K8s)环境下,Java应用的 heapdump 和 jstack 快照问题解决方案探析

最编程 2024-02-10 14:15:04
...

场景

最近在基于kubernetes程序运行的时候遇到了一个OOM(OutOfMemoryException)异常,尝试着使用jstack dump 堆栈的时候,发现jstack无法dump PID=1 的进程。

原因

在Linux系统中,当内核初始化完毕后,会启动一个init进程,这个进程是整个操作系统的的第一个用户进程,所以它的进程ID为1,也就是我们常说的PID1进程。在这之后,所有的用户态进程都是该进程的后代进程,由此我们可以看出,整个系统的用户进程,是一棵由init进程作为根的进程树。

在Dockers中,虽然在容器中被标识为PID 1的进程在操作系统中是一个普通的用户进程,但是因为Linux 内核提供了PID Namespaces功能,如果宿主机的所有用户进程构成了一个完整的树形结构,那么PID Namespaces实际上就是将这个ENTRYPOINT进程(包括它的子进程)从系统的大树上裁剪下来,在容器中,它就是一个以它本身为PID1 的完整树。

在本例中,由于使用的是alpine+Java镜像以直接启动Java的方式启动docker,因此容器中Application的PID=1,由于jstack的限制

# jstack 1
1: Unable to open socket file: target process not responding or HotSpot VM not loaded

我们需要另想办法使得image的ENVTRYPOINT(PID1)的进程不能为 java 程序。

解决方案

1. 直接用 docker run启动

docker run --init my-app

2. 非docker run启动

2.1 制作基础镜像

在镜像中添加init功能,通过tini进程来运行java进程, 因为在项目中不止一个Java项目,所以,我们可以把该功能添加到基础镜像之中, 具体的Dockfile如下:

FROM openjdk:8-alpine
ARG JAR_FILE
ARG PROJECT_NAME
RUN apk update && apk --no-cache tini add bash curl  && rm -rf /var/cache/*/*

2.2 使用JIB方式打包

因为在本项目中使用的是JIB 打包,传统的通过jvmFlagsmainClass方式设置镜像入口的方式就要有所改变。 具体pom.xml配置如下:

<plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.2.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>build</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <from>
                        <image>adaptation-engine-local.fpisoj70.americas.net/openjdk:8-alpine-bash</image>
                        <auth>
                            <username>xxx</username>
                            <password>AKCp5btpAnsuZSx1kdE8kwJateLChSQTfNW93JjghAge1u1Vst3cA7QrRiKVL6HsYvQeD6CQw
                            </password>
                        </auth>
                    </from>
                    <to>
                        <image>adaptation-engine-local.fpisoj70.americas.net/fp-portal/data-processor</image>
                        <auth>
                            <username>xxx</username>
                            <password>AKCp5btpAnsuZSx1kdE8kwJateLChSQTfNW93JjghAge1u1Vst3cA7QrRiKVL6HsYvQeD6CQw
                            </password>
                        </auth>
                        <tags>
                            <tag>${docker.image.tag}</tag>
                        </tags>
                    </to>
                    <container>
                        <entrypoint>
                            <shell>/sbin/tini</shell>
                            <option>--</option>
                        </entrypoint>
                        <args>
                            <arg>java</arg>
                            <arg>-Xms512m</arg>
                            <arg>-Xmx2048m</arg>
                            <arg>-Dsun.net.inetaddr.ttl=0</arg>
                            <arg>-XX:+HeapDumpOnOutOfMemoryError</arg> <!--OOM时快照-->
                            <arg>-cp</arg>
                            <arg>/app/resources/:/app/classes/:/app/libs/*</arg>
                            <arg>com.fcm.oss.fp.DataProcessorApplication</arg>
                            <arg>--spring.profiles.active=k8s</arg>
                        </args>
                    </container>
                </configuration>
            </plugin>

到此,即可使用maven 构建可dump jstack的docker镜像了。

容器操作

使用docker或者kubernetes 命令进入容器

# Docker 
/ $  docker exec -ti <container_id> sh 
# K8s
/ $ kubectl exec -ti <pod_id> -- sh

# 查找Java pid

/ $ ps -ef |grep java
    1 root      0:08 /sbin/tini -- java -Xms512m -Xmx2048m -Dsun.net.inetaddr.ttl=0 -XX:+HeapDumpOnOutOfMemoryError -cp /app/resources/:/app/classes/:/app/libs/* com.fcm.oss.fp.DataProcessorApplication --spring.profiles.active=k8s
    6 root      6h51 java -Xms512m -Xmx2048m -Dsun.net.inetaddr.ttl=0 -XX:+HeapDumpOnOutOfMemoryError -cp /app/resources/:/app/classes/:/app/libs/* com.fcm.oss.fp.DataProcessorApplication --spring.profiles.active=k8s
 3463 root      0:00 grep java

# 如上图,pid =6 的进程即为java程序进程
/ $ jstack -l 1 > java_dump.tdump 

# 退出docker命令行,拷贝刚才生成的文件

/ $ docker cp <container_id>:/java_pid6.hprof .
/ $ docker cp <container_id>:/java_dump.tdump .

至此,就可以使用Jprofiler 或者是Eclipse 的MAT工具分析快照文件了。