openjdk-alpine容器中的jvm如何dump
微服务架构之后,应用的jvm运行在容器之内,如果系统出现问题,如何对jvm进行dump?
有两种方法:
- 开启jmx,使用jvisualvm通过jmx连接jvm,生成dump。
- 使用docker命令,进入容器的shell环境,使用jmap命令生成dump。
本文主要介绍第二种方法。在之前的文章中说过,我们的项目使用Google的jib打包成镜像,使用的基础镜像是8-jdk-alpine。容器启动时第一个进程就是java:
1 | [root@VM_16_16_centos pp]# docker exec -it pp_eureka_1 sh |
使用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 | docker run --rm -it --init openjdk:8-jdk-alpine |
如果是docker compse,可以在docker-compose中配置上init参数。
1 | version: '2.2' |
需要注意的是,init参数对docker-compose.yml的文件格式版本有要求:
- v2版本,version必须配置为2.2或以上版本。
- v3版本,version必须配置为3.7或以上版本。
- 不同版本的compose文件,有docker版本兼容性要求
krallin/tini
安装Tini,使用tini作为入口进程,配置启动java进程。
1 | RUN apk add --no-cache tini |
实际上,docker的--init
参数也是通过集成Tini实现的。
其他事项
上述两种方式,那种更好?关于这个问题,github上有相关的讨论。个人觉得,各有利弊:
- 使用
docker --init
参数简单,但是需要运维人员注意,部署时存在人为操作遗漏的风险。 - 使用
Tini
不用担心人为失误,但用jib打包镜像会增加配置工作,需要为每个项目配置entrypoint参数。
另外需要注意的是,在centos下,docker必须是docker-1.13.1-88.git07f3374.el7.centos.x86_64
之后的发行版本,之前版本有bug。这个问题耽误了我一天时间,在本地windows下都好使,部署到docker不好使,试了好久,最后发现是centos下docker版本bug,更新后就好了。