openjdk-alpine容器中的jvm如何dump

openjdk-alpine容器中的jvm如何dump

微服务架构之后,应用的jvm运行在容器之内,如果系统出现问题,如何对jvm进行dump?

有两种方法:

  1. 开启jmx,使用jvisualvm通过jmx连接jvm,生成dump。
  2. 使用docker命令,进入容器的shell环境,使用jmap命令生成dump。

本文主要介绍第二种方法。在之前的文章中说过,我们的项目使用Google的jib打包成镜像,使用的基础镜像是8-jdk-alpine。容器启动时第一个进程就是java:

1
2
3
4
5
6
[root@VM_16_16_centos pp]# docker exec -it pp_eureka_1 sh
/ # ps
PID USER TIME COMMAND
1 root 0:50 java -Xms256m -Xmx512m ......
94 root 0:00 sh
99 root 0:00 ps

使用alpine镜像会有个问题,如果java进程的pid=1,那么无法执行jdk的各种连接java进程的命令,会报如下错误:

Unable to get pid of LinuxThreads manager thread

其他os镜像未验证,相关issue:https://github.com/docker-library/openjdk/issues/76

解决的方法是:启动一个init进程(pid=1)来接收docker stop and docker kill的信号,它会转发信号给其他进程,负责关闭僵尸进程。java进程由init进程启动。

具体有以下两种做法。

docker run –init

在docker 1.13 之后的版本,可以在docker run时加上 --init 参数来实现。

1
2
3
4
5
6
7
$ docker run --rm -it --init openjdk:8-jdk-alpine
/ # ps
PID USER TIME COMMAND
1 root 0:00 /dev/init -- /bin/sh
7 root 0:00 /bin/sh
8 root 0:00 ps
/ #

如果是docker compse,可以在docker-compose中配置上init参数。

1
2
3
4
5
6
7
8
9
10
11
12
version: '2.2'
services:
web:
image: alpine:latest
init: true
version: '2.2'
services:
web:
image: alpine:latest
init: /usr/libexec/docker-init

需要注意的是,init参数对docker-compose.yml的文件格式版本有要求:

  1. v2版本,version必须配置为2.2或以上版本。
  2. v3版本,version必须配置为3.7或以上版本。
  3. 不同版本的compose文件,有docker版本兼容性要求

krallin/tini

安装Tini,使用tini作为入口进程,配置启动java进程。

1
2
3
RUN apk add --no-cache tini
# Tini is now available at /sbin/tini
ENTRYPOINT ["/sbin/tini", "--", "java", "-Xms256m", "-Xmx512m", ......]

实际上,docker的--init参数也是通过集成Tini实现的。

其他事项

上述两种方式,那种更好?关于这个问题,github上有相关的讨论。个人觉得,各有利弊:

  1. 使用docker --init 参数简单,但是需要运维人员注意,部署时存在人为操作遗漏的风险。
  2. 使用 Tini 不用担心人为失误,但用jib打包镜像会增加配置工作,需要为每个项目配置entrypoint参数。

另外需要注意的是,在centos下,docker必须是docker-1.13.1-88.git07f3374.el7.centos.x86_64之后的发行版本,之前版本有bug。这个问题耽误了我一天时间,在本地windows下都好使,部署到docker不好使,试了好久,最后发现是centos下docker版本bug,更新后就好了。