提交 f9cccbd9 authored 作者: 陈泽健's avatar 陈泽健

feat(monitor): 添加硬盘空间检测功能

- 新增硬盘空间检测功能,监控根分区使用率峰值和平均值
- 实现磁盘使用率异常警告机制,当使用率超过90%时发出警告
- 更新邮件通知配置,添加新收件人并支持自定义邮件标题
- 优化邮件发送逻辑,改为纯文本格式并改进IP地址获取方式
- 在监测报告中新增硬盘空间检测结果展示
- 更新PRD文档,标记安卓设备自检为已完成状态
上级 cc32739d
...@@ -14,12 +14,20 @@ MYSQL_PASSWORD="dNrprU&2S" ...@@ -14,12 +14,20 @@ MYSQL_PASSWORD="dNrprU&2S"
LOG_FILE="./AutomatedServiceMonitoring.sh.log" LOG_FILE="./AutomatedServiceMonitoring.sh.log"
REPORT_DIR="./monitor_reports" REPORT_DIR="./monitor_reports"
WORD_REPORT_DIR="./monitor_reports_word" WORD_REPORT_DIR="./monitor_reports_word"
HOST_NAME="$(hostname)" HOST_NAME="${HOST_NAME_OVERRIDE:-$(hostname 2>/dev/null || echo localhost)}"
# 邮件通知配置 # 邮件通知配置
MAIL_TO="czj@huazhaochina.com,pgy@huazhaochina.com" MAIL_TO="czj@huazhaochina.com,pgy@huazhaochina.com,zxb@huazhaochina.com,wb@huazhaochina.com"
MAIL_SUBJECT_PREFIX="自动化服务监测报告" MAIL_SUBJECT_PREFIX="自动化服务监测报告"
# ✅ 邮件标题:支持自定义
# 1) 你可以直接改这个默认值
MAIL_SUBJECT_PREFIX="【内部服务器监测】"
# 2) 也可以在运行时通过环境变量覆盖,例如:
# MAIL_SUBJECT_PREFIX="【产线】服务监测报告" ./AutomatedServiceMonitoring.sh
MAIL_SUBJECT_PREFIX="${MAIL_SUBJECT_PREFIX:-自动化服务监测报告}"
# SMTP 服务器配置(QQ 企业邮箱) # SMTP 服务器配置(QQ 企业邮箱)
MAIL_SMTP_HOST="smtp.exmail.qq.com" MAIL_SMTP_HOST="smtp.exmail.qq.com"
MAIL_SMTP_PORT="465" MAIL_SMTP_PORT="465"
...@@ -722,6 +730,102 @@ ensure_sendmail_installed() { ...@@ -722,6 +730,102 @@ ensure_sendmail_installed() {
command -v sendmail >/dev/null 2>&1 && log INFO "[依赖安装] sendmail 安装完成" command -v sendmail >/dev/null 2>&1 && log INFO "[依赖安装] sendmail 安装完成"
} }
#################### 3.6 硬盘空间检测 ####################
DISK_SAMPLES=0
DISK_SUM_USED_PCT=0
DISK_PEAK_USED_PCT=0
DISK_PEAK_TS=0
DISK_LAST_USED_PCT=0
# 额外保存:df -P 原始输出文件(用于解析计算)
DISK_DF_RAW_FILE=""
DISK_DF_FILE=""
# ✅ 新增:避免 set -u 下“未绑定变量”
DISK_LAST_DF_TEXT=""
# 取根分区 / 的使用率(最稳定:不依赖 df 输出文件/复杂解析)
get_root_disk_used_pct() {
if ! command -v df >/dev/null 2>&1; then
return 1
fi
# df -P / 的输出一般为两行:表头 + 数据行,Use% 在第5列
df -P / 2>/dev/null | awk 'NR==2 { gsub(/%/,"",$5); print $5 }'
}
# 返回当前磁盘最大使用率(整数),并把 df 文件保存到 DISK_DF_FILE / DISK_DF_RAW_FILE
get_disk_used_pct_max() {
collect_disk_df_to_file || true
# 读展示文件前 15 行用于日志展示
if [[ -n "${DISK_DF_FILE:-}" && -f "${DISK_DF_FILE:-}" ]]; then
DISK_LAST_DF_TEXT="$(head -n 15 "$DISK_DF_FILE" 2>/dev/null || true)"
else
DISK_LAST_DF_TEXT=""
fi
# 用 df -P 文件解析 Use%(第5列)
if [[ -z "${DISK_DF_RAW_FILE:-}" || ! -f "${DISK_DF_RAW_FILE:-}" ]]; then
return 1
fi
local max
max="$(awk '
# 跳过我们自己写的 3 行头(##/PATH/空行),再跳过 df 表头
/^## / { next }
/^PATH=/ { next }
NF==0 { next }
NR==1 { next }
{
u=$5
gsub(/%/,"",u)
if (u ~ /^[0-9]+$/) {
if (u > m) m=u
}
}
END{
if (m=="") exit 1
print m
}' "$DISK_DF_RAW_FILE" 2>/dev/null || true)"
[[ -n "$max" && "$max" =~ ^[0-9]+$ ]] || return 1
echo "$max"
return 0
}
monitor_disk_once() {
local used_pct
used_pct="$(get_root_disk_used_pct || true)"
if [[ -z "$used_pct" || ! "$used_pct" =~ ^[0-9]+$ ]]; then
log ERROR "[硬盘监测] $HOST_NAME => 获取根分区(/)使用率失败(df 不可用或解析失败)"
return
fi
DISK_LAST_USED_PCT="$used_pct"
DISK_SAMPLES=$((DISK_SAMPLES + 1))
DISK_SUM_USED_PCT=$((DISK_SUM_USED_PCT + used_pct))
if (( used_pct > DISK_PEAK_USED_PCT )); then
DISK_PEAK_USED_PCT="$used_pct"
DISK_PEAK_TS="$(date +%s)"
fi
local avg_used_pct=$(( DISK_SUM_USED_PCT / DISK_SAMPLES ))
local peak_human="N/A"
if [[ -n "${DISK_PEAK_TS}" && "${DISK_PEAK_TS}" != "0" ]]; then
peak_human="$(date -d @"$DISK_PEAK_TS" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S')"
fi
local warn_threshold=90
if (( used_pct >= warn_threshold )); then
log WARN "[硬盘监测] $HOST_NAME 根分区(/)使用率偏高:当前=${used_pct}% 平均=${avg_used_pct}% 峰值=${DISK_PEAK_USED_PCT}%@${peak_human}"
else
log INFO "[硬盘监测] $HOST_NAME 根分区(/)当前使用率=${used_pct}% 平均=${avg_used_pct}% 峰值=${DISK_PEAK_USED_PCT}%@${peak_human}"
fi
}
# 简单把 Markdown 报告转成 HTML(够用:标题/列表/代码/表格) # 简单把 Markdown 报告转成 HTML(够用:标题/列表/代码/表格)
md_to_html_simple() { md_to_html_simple() {
local report_file="$1" local report_file="$1"
...@@ -864,6 +968,18 @@ md_to_html_simple() { ...@@ -864,6 +968,18 @@ md_to_html_simple() {
}' "$report_file" }' "$report_file"
} }
# ✅ 取本机“主IP”(优先走路由默认出口;取不到再 fallback 到 hostname -I)
get_primary_ip() {
local ip=""
if command -v ip >/dev/null 2>&1; then
ip="$(ip route get 1 2>/dev/null | awk '{for(i=1;i<=NF;i++){if($i=="src"){print $(i+1); exit}}}')"
fi
if [[ -z "$ip" ]] && command -v hostname >/dev/null 2>&1; then
ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
fi
echo "${ip:-unknown-ip}"
}
#################### 6. 邮件发送 #################### #################### 6. 邮件发送 ####################
# 尝试发送邮件,把生成的 MD 报告作为正文发送 # 尝试发送邮件,把生成的 MD 报告作为正文发送
send_report_mail() { send_report_mail() {
...@@ -874,71 +990,39 @@ send_report_mail() { ...@@ -874,71 +990,39 @@ send_report_mail() {
return 1 return 1
fi fi
local subject="自动化服务监测报告 - ${HOST_NAME} - $(date '+%Y-%m-%d %H:%M:%S')" if ! command -v mailx >/dev/null 2>&1 && ! command -v mail >/dev/null 2>&1; then
log ERROR "[邮件发送] 未找到 mailx/mail,无法发送邮件"
return 1
fi
# ✅ 标题改成:【内部】+服务器IP+时间
local server_ip
server_ip="$(get_primary_ip)"
local subject="${MAIL_SUBJECT_PREFIX}${server_ip} - $(date '+%Y-%m-%d %H:%M:%S')"
# 收件人:逗号转空格,兼容性更好
local to="${MAIL_TO}" local to="${MAIL_TO}"
to="${to//,/ }" to="${to//,/ }"
local report_html log INFO "[邮件发送] 使用 mailx(SMTP) 发送纯文本报告给:$to"
report_html="$(md_to_html_simple "$report_md")"
# 1) 先尝试:mailx(SMTP) 发 HTML(仅在确认支持 header 的情况下) # 用 mailx 发送纯文本(依赖 /etc/mail.rc 的 smtp 配置)
# 注意:有的系统只有 mail 没有 mailx,因此做二选一
if command -v mailx >/dev/null 2>&1; then if command -v mailx >/dev/null 2>&1; then
if mailx_supports_a_header; then mailx -s "$subject" $to < "$report_md" || {
log INFO "[邮件发送] 使用 mailx(SMTP) 发送 HTML 报告给:$to" log ERROR "[邮件发送] mailx(SMTP) 发送失败"
if printf "%s\n" "$report_html" | mailx -s "$subject" -a "Content-Type: text/html; charset=UTF-8" "$to"; then
return 0
fi
log WARN "[邮件发送] mailx(SMTP) HTML 发送失败,将回退 sendmail HTML"
# 不 return,继续走 sendmail
else
log WARN "[邮件发送] 当前 mailx 不支持 '-a header'(-a 被当附件),跳过 mailx HTML,改用 sendmail HTML"
fi
fi
# 2) 回退:sendmail 发送 HTML(MIME 最可控)
if command -v sendmail >/dev/null 2>&1; then
log INFO "[邮件发送] 使用 sendmail 发送 HTML 报告给:$to"
local boundary="BOUNDARY_$(date +%s)_$$"
{
echo "To: ${to}"
echo "Subject: ${subject}"
echo "MIME-Version: 1.0"
echo "Content-Type: multipart/alternative; boundary=\"${boundary}\""
echo
echo "--${boundary}"
echo "Content-Type: text/plain; charset=utf-8"
echo
echo "本邮件包含 HTML 版本监测报告。若客户端不支持 HTML,请查看落盘 Markdown:$report_md"
echo
echo "--${boundary}"
echo "Content-Type: text/html; charset=utf-8"
echo "Content-Transfer-Encoding: 8bit"
echo
echo "${report_html}"
echo
echo "--${boundary}--"
} | sendmail -t || {
log ERROR "[邮件发送] sendmail 发送失败"
return 1 return 1
} }
return 0 else
fi mail -s "$subject" $to < "$report_md" || {
log ERROR "[邮件发送] mail(SMTP) 发送失败"
# 3) 最后兜底:mailx 纯文本
if command -v mailx >/dev/null 2>&1; then
log WARN "[邮件发送] 未找到 sendmail,回退 mailx 纯文本发送(无法保证 HTML 渲染)"
cat "$report_md" | mailx -s "$subject" "$to" || {
log ERROR "[邮件发送] mailx 纯文本发送失败"
return 1 return 1
} }
return 0
fi fi
log ERROR "[邮件发送] 未找到 mailx/sendmail,无法发送邮件" return 0
return 1
} }
#################### 5. 报告输出(md) #################### #################### 5. 报告输出(md) ####################
write_md_report() { write_md_report() {
mkdir -p "$REPORT_DIR" 2>/dev/null || true mkdir -p "$REPORT_DIR" 2>/dev/null || true
...@@ -974,6 +1058,15 @@ write_md_report() { ...@@ -974,6 +1058,15 @@ write_md_report() {
mysql_peak_time="$(date -d @"$MYSQL_PEAK_TS" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S')" mysql_peak_time="$(date -d @"$MYSQL_PEAK_TS" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S')"
fi fi
# 新增:硬盘统计
local disk_avg_used="N/A"
(( DISK_SAMPLES > 0 )) && disk_avg_used=$(( DISK_SUM_USED_PCT / DISK_SAMPLES ))
local disk_peak_time="N/A"
if [[ -n "${DISK_PEAK_TS:-}" && "${DISK_PEAK_TS:-0}" != "0" ]]; then
disk_peak_time="$(date -d @"$DISK_PEAK_TS" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S')"
fi
{ {
echo "# 自动化服务监测报告" echo "# 自动化服务监测报告"
echo echo
...@@ -1022,24 +1115,35 @@ write_md_report() { ...@@ -1022,24 +1115,35 @@ write_md_report() {
echo "- 最近暴涨判定状态:**${MYSQL_LAST_BURST_STATUS}**" echo "- 最近暴涨判定状态:**${MYSQL_LAST_BURST_STATUS}**"
echo "- 暴涨详情:${MYSQL_LAST_BURST_DESC}" echo "- 暴涨详情:${MYSQL_LAST_BURST_DESC}"
echo echo
echo "## 四、容器信息检测"
# 新增:硬盘空间
echo "## 四、硬盘空间检测"
echo
echo "- 当前硬盘使用率(根分区 / ):${DISK_LAST_USED_PCT}%"
echo "- 硬盘使用率平均值:${disk_avg_used}%"
echo "- 硬盘使用率峰值:${DISK_PEAK_USED_PCT}%"
echo "- 峰值发生时间:${disk_peak_time}"
echo echo
echo "### 4.1 运行中的容器" echo "> 说明:为保证稳定性,当前仅采集根分区(/)的使用率,不再采集/输出 df 详细列表。"
echo
# 原来的容器章节顺延编号
echo "## 五、容器信息检测"
echo
echo "### 5.1 运行中的容器"
echo echo
if [[ -z "${CONTAINER_RUNNING_LIST//[[:space:]]/}" ]]; then if [[ -z "${CONTAINER_RUNNING_LIST//[[:space:]]/}" ]]; then
echo "- 未采集到运行中的容器信息。" echo "- 未采集到运行中的容器信息。"
else else
echo "| 容器ID | 名称 | 镜像 | 状态 | 创建时间 |" echo "| 容器ID | 名称 | 镜像 | 状态 | 创建时间 |"
echo "| ------ | ---- | ---- | ---- | -------- |" echo "| ------ | ---- | ---- | ---- | -------- |"
# CONTAINER_RUNNING_LIST 每行格式:ID \t NAME \t IMAGE \t STATUS \t CREATED_AT
echo "$CONTAINER_RUNNING_LIST" | while IFS=$'\t' read -r cid cname cimg cstatus ctime; do echo "$CONTAINER_RUNNING_LIST" | while IFS=$'\t' read -r cid cname cimg cstatus ctime; do
# 防止空行
[[ -z "${cid}${cname}${cimg}${cstatus}${ctime}" ]] && continue [[ -z "${cid}${cname}${cimg}${cstatus}${ctime}" ]] && continue
echo "| ${cid} | ${cname} | ${cimg} | ${cstatus} | ${ctime} |" echo "| ${cid} | ${cname} | ${cimg} | ${cstatus} | ${ctime} |"
done done
fi fi
echo echo
echo "### 4.2 未运行的容器(Exited/其它非 Up 状态)" echo "### 5.2 未运行的容器(Exited/其它非 Up 状态)"
echo echo
if [[ -z "${CONTAINER_EXITED_LIST//[[:space:]]/}" ]]; then if [[ -z "${CONTAINER_EXITED_LIST//[[:space:]]/}" ]]; then
echo "- 未采集到未运行的容器信息。" echo "- 未采集到未运行的容器信息。"
...@@ -1056,7 +1160,6 @@ write_md_report() { ...@@ -1056,7 +1160,6 @@ write_md_report() {
echo echo
} > "$report_file" } > "$report_file"
# 将报告文件路径打印到 stdout,便于调用方获取
echo "$report_file" echo "$report_file"
} }
...@@ -1095,6 +1198,9 @@ main_run_once() { ...@@ -1095,6 +1198,9 @@ main_run_once() {
monitor_mysql_once monitor_mysql_once
collect_container_info collect_container_info
# 新增:硬盘空间检测(3.6)
monitor_disk_once
# 生成报告,并获取报告文件路径(md) # 生成报告,并获取报告文件路径(md)
local report_file local report_file
report_file="$(write_md_report)" report_file="$(write_md_report)"
......
...@@ -61,6 +61,8 @@ ...@@ -61,6 +61,8 @@
根据平台类型持续监测EMQX连接数量峰值、平均值,以及是否存在暴涨情况,或是判断一直没断开的异常连接。 根据平台类型持续监测EMQX连接数量峰值、平均值,以及是否存在暴涨情况,或是判断一直没断开的异常连接。
3.5、容器信息检测(✅ 已实现): 3.5、容器信息检测(✅ 已实现):
针对当前服务器上存在的运行和未运行的容器进行查询检测,分别记录运行的容器信息和未运行的容器信息,需要列出容器信息。 针对当前服务器上存在的运行和未运行的容器进行查询检测,分别记录运行的容器信息和未运行的容器信息,需要列出容器信息。
3.6、硬盘空间检测(待实现):
针对当前服务器上存在的硬盘空间使用情况进行检测,分别记录硬盘使用率峰值和平均值,以及是否存在硬盘使用率异常过高的情况。详细列出硬盘的分布情况。
##### 4、监测日志审计(✅ 已实现): ##### 4、监测日志审计(✅ 已实现):
需要丰富日志体系,日志需要用中文打印 需要丰富日志体系,日志需要用中文打印
......
...@@ -453,7 +453,7 @@ ...@@ -453,7 +453,7 @@
- 对于高风险操作(如 Redis data 目录清理),自检场景采用非交互模式(--non-interactive --yes), - 对于高风险操作(如 Redis data 目录清理),自检场景采用非交互模式(--non-interactive --yes),
手工运维场景则可直接在服务器上交互式执行 issue_handler.sh,由运维人员确认后再进行删除或修改操作。 手工运维场景则可直接在服务器上交互式执行 issue_handler.sh,由运维人员确认后再进行删除或修改操作。
##### 15、安卓设备的自检(实现): ##### 15、安卓设备的自检(✅ 已实现):
功能描述: 功能描述:
针对连接到服务器的安卓设备,执行一系列自检操作以确保设备状态正常。 针对连接到服务器的安卓设备,执行一系列自检操作以确保设备状态正常。
主要检测项包括: 主要检测项包括:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论