实现思路
由于Prometheus 监控可以看到具体项目在某一阶段的状态指标。却没法详细说明具体是何种原因导致的对应指标异常,而当我们通过外部监控检测到异常情况时,再进入容器执行线程打印等任务往往可能无法完成,例如:容器内cpu或者负载飙升时我们进入容器往往异常困难,并且无法正常执行相应命令。较为稳妥的方式则是在容器内部实现定时指标收集,若出现异常情况则自动保存其线程或线程状态,我们需要做的则是再收到报警后进入容器相应的文件内找到异常信息即可
步骤
- 构建时在镜像内安装cron,实现定时启动监控脚本
- 实现异常情况脚本自动输出线程文件
- 监测到线程文件产出时发出报警
镜像构建
由于本文是针对的是docker化的java项目,所以构建镜像使用的基础镜像是java
以下为常见的java基础镜像
- java:8-jre-alpine
- openjdk:8u222-jre
- java:8
这里选择使用 java:8,经过测试,只有这个版本的镜像是包含完成的jdk环境和命令的
构建准备
需要准备以下文件
load-minor.sh(核心监控脚本)
用于打印线程和线程日志,这里使用的jvm中自带命令 jstack
#!/bin/bash
# 所有文件每小时才会记录一次,否则文件过大,并且服务器首次打印的线程信息才有参考价值
FILE_BASE_PATH='/home/logs/monitors/'
JSTACK_FILE_NAME=$FILE_BASE_PATH$(echo `hostname`_$(date +%Y-%m-%d-%H))_jstack.log
JSTACK_PROCESS_FILE_NAME=$FILE_BASE_PATH$(echo `hostname`_$(date +%Y-%m-%d-%H))_jstack_process.log
{
# 若文件夹不存在则先行创建
if [ ! -d $FILE_BASE_PATH ]
then
mkdir -p "$FILE_BASE_PATH"
fi
}
#02. 监控系统负载
{
HOSTNAME=`hostname`
cpu_num=`grep -c 'model name' /proc/cpuinfo`
count_uptime=`uptime |wc -w`
load_15=`uptime | awk '{print $'$count_uptime'}'`
average_load=`echo "scale=2;a=$load_15/$cpu_num;if(length(a)==scale(a)) print 0;print a" | bc`
average_int=`echo $average_load | cut -f 1 -d "."`
if [ $average_int -gt 0 ]
then
# 系统负载过高
# 线程信息
if [ ! -f $JSTACK_FILE_NAME ]
then
jstack -l "$(top -b -n 1 | grep java | awk '{print $1}')" >> $JSTACK_FILE_NAME
fi
# 打印线程附带的线程
if [ ! -f $JSTACK_PROCESS_FILE_NAME ]
then
top -b -n 1 -H -p "$(top -b -n 1 | grep java | awk '{print $1}')" >> $JSTACK_PROCESS_FILE_NAME
fi
fi
}
#02. 监控cpu使用率
{
cpu_idle=`top -b -n 1 | grep Cpu | awk '{print $8}' | cut -f 1 -d "."`
if [ $cpu_idle -lt 20 ]
then
# cpu使用率超过80%
if [ ! -f $JSTACK_FILE_NAME ]
then
jstack -l "$(top -b -n 1 | grep java | awk '{print $1}')" >> $JSTACK_FILE_NAME
fi
# 打印线程附带的线程
if [ ! -f $JSTACK_PROCESS_FILE_NAME ]
then
top -b -n 1 -H -p "$(top -b -n 1 | grep java | awk '{print $1}')" >> $JSTACK_PROCESS_FILE_NAME
fi
fi
}
sources.list.jessie(用于基础镜像更换安装源)
deb http://mirrors.163.com/debian/ jessie main non-free contrib
#deb http://mirrors.163.com/debian-archive/debian/ jessie-backports main non-free contrib
deb-src http://mirrors.163.com/debian/ jessie main non-free contrib
#deb-src http://mirrors.163.com/debian-archive/debian/ jessie-backports main non-free contrib
deb http://mirrors.163.com/debian-security/ jessie/updates main non-free contrib
deb-src http://mirrors.163.com/debian-security/ jessie/updates main non-free contrib
entrypoint.sh(命令脚本)
#!/bin/bash
cron
java -jar /app.jar
crontabfile(cron定时配置)
* * * * * sh /scripts/load-minor.sh
* * * * * sleep 5 && sh /scripts/load-minor.sh
* * * * * sleep 10 && sh /scripts/load-minor.sh
* * * * * sleep 15 && sh /scripts/load-minor.sh
* * * * * sleep 20 && sh /scripts/load-minor.sh
* * * * * sleep 25 && sh /scripts/load-minor.sh
* * * * * sleep 30 && sh /scripts/load-minor.sh
* * * * * sleep 35 && sh /scripts/load-minor.sh
* * * * * sleep 40 && sh /scripts/load-minor.sh
* * * * * sleep 45 && sh /scripts/load-minor.sh
* * * * * sleep 50 && sh /scripts/load-minor.sh
* * * * * sleep 55 && sh /scripts/load-minor.sh
提示:该文件请勿在非linux环境下编辑
构建
FROM java:8
ENV LANG C.UTF-8
ENV TZ=Asia/Shanghai
COPY target/*.jar app.jar
COPY build-material/sources.list.jessie /etc/apt/sources.list
COPY build-material/load-minor.sh /scripts/load-minor.sh
COPY build-material/crontabfile /etc/cron.d/minor-cron
COPY build-material/entrypoint.sh entrypoint.sh
# 必要参数 ENTRYPOINT 若存在多个指令,仅最后一个生效
ENTRYPOINT ["/bin/bash","/entrypoint.sh"]
# 如果你使用的k8s容器,那么可以使用此行命令,并且在k8s编排中配置启动后指定cron即可
ENTRYPOINT ["java","-jar","/app.jar"]
# 本次打包镜像将集成实时监控环境
# 把Arthas安装到你的Docker镜像中 java -jar /opt/arthas/arthas-boot.jar
COPY --from=hengyunabc/arthas:latest /opt/arthas /opt/arthas
# 用于更换软件安装源
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& mv /etc/apt/sources.list.d/jessie-backports.list /etc/apt/sources.list.d/jessie-backports.list.bak \
&& apt-get -o Acquire::Check-Valid-Until=false update \
&& apt-get install -y bc cron vim \
&& chmod 0644 /etc/cron.d/minor-cron \
&& crontab /etc/cron.d/minor-cron
可以按照本文档的继续进行补充或者调整。
建议
以上则是监控主要文件构成,建议将文件从容器内挂在出来,用于归档和回溯,以免docker重启导致丢失关键信息
之后的监控脚本可以以任何你熟悉的工具实现即可,思路很简单,监控线程输出的日志目录,如果出现新文件产生则报警即可
最后简述以下日志的使用
该文件会打印两个内容,当前的线程详细信息(_jstack.log)和线程列表(_jstack_process.log)
通过 _jstack_process.log 打印线程列表找到占用过高的,将其线程id转为16进制后加上 0x 在 _jstack.log文件中查找即可,即可找到对应内容即可定位问题