PayrollManagementSystem/scripts/health_check.sh
liuwenxin b5e27810fd feat(backup): mysqldump 定时备份/恢复演练/健康检查脚本与手册
- scripts/backup_full.sh:容器内 mysqldump --single-transaction,gzip 落盘
  db/backups/,保留 30 天;凭据 source docker/.env 不硬编码;实测备份成功
- scripts/restore_drill.sh:恢复到临时库 payroll_drill_* 并逐表核对行数
  (16 张关键表),默认不 DROP、--drop-exec 才删——绝不覆盖生产库;
  实测 16 表全 ✓。规避嵌套 shell 吞反引号(mysqladmin create/drop、
  双引号包裹表名)
- scripts/health_check.sh:六项检查(容器健康×2/磁盘水位/备份新鲜度/
  Redis 探活/24h 登录失败阈值),PASS/WARN/FAIL 报告,退出码供监控;
  Redis 密码经宿主机 .env 验证(容器内无该环境变量),实测六项全 PASS
- docs/备份恢复手册.md:备份/演练/生产恢复七步/定时 schtasks 两则,
  db/backups/ 已入 .gitignore

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-16 09:44:50 +08:00

106 lines
3.6 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# 运维健康检查 —— 容器/磁盘/备份新鲜度/登录异常(⑨ 输出一份可读报告)
#
# 用法:在项目根目录执行 bash scripts/health_check.sh
# 每项 PASS/WARN/FAIL,最后给出结论。WARN/FAIL 时退出码非零提示运维介入。
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
if [[ -f docker/.env ]]; then
# shellcheck disable=SC1091
source docker/.env
fi
ISSUES=0
report() { printf "%-10s %s\n" "$1" "$2"; }
warn() { report "WARN" "$1"; ISSUES=$((ISSUES+1)); }
fail() { report "FAIL" "$1"; ISSUES=$((ISSUES+1)); }
pass() { report "PASS" "$1"; }
echo "======== 衍水工资系统 健康检查 $(date '+%Y-%m-%d %H:%M:%S') ========"
# 1. 容器健康
for C in ys-mysql-dev ys-redis-dev; do
if ! docker inspect "$C" >/dev/null 2>&1; then
fail "容器 $C 不存在"
continue
fi
STATUS="$(docker inspect -f '{{.State.Health.Status}}' "$C" 2>/dev/null || echo none)"
RUNNING="$(docker inspect -f '{{.State.Running}}' "$C" 2>/dev/null)"
if [[ "$RUNNING" == "true" && "$STATUS" == "healthy" ]]; then
pass "容器 $C 运行中且健康"
elif [[ "$RUNNING" == "true" ]]; then
warn "容器 $C 运行中但健康检查=$STATUS"
else
fail "容器 $C 未运行"
fi
done
# 2. 磁盘水位
DISK_PCT="$(df -h "$ROOT" | awk 'NR==2 {gsub(/%/,"",$5); print $5}')"
if [[ -z "$DISK_PCT" ]]; then
warn "磁盘信息读取失败"
elif (( DISK_PCT < 80 )); then
pass "磁盘使用率 ${DISK_PCT}%(阈值 80%)"
elif (( DISK_PCT < 90 )); then
warn "磁盘使用率 ${DISK_PCT}%(建议清理)"
else
fail "磁盘使用率 ${DISK_PCT}%(高危,立即清理)"
fi
# 3. 备份新鲜度
BACKUP_DIR="$ROOT/db/backups"
LATEST="$(ls -t "$BACKUP_DIR"/payroll_*.sql.gz 2>/dev/null | head -1)"
if [[ -z "$LATEST" ]]; then
warn "未找到任何备份(应每日备份)"
else
AGE_H=$(( ($(date +%s) - $(stat -c %Y "$LATEST")) / 3600 ))
if (( AGE_H < 24 )); then
pass "最新备份 $(basename "$LATEST"),${AGE_H} 小时前"
elif (( AGE_H < 48 )); then
warn "最新备份已 ${AGE_H} 小时前(超过 24 小时)"
else
fail "最新备份已 ${AGE_H} 小时前(备份任务可能已停)"
fi
fi
# 4. Redis 存活(密码经宿主 docker/.env,不经命令行明文参数)
if [[ -n "${REDIS_PASSWORD:-}" ]]; then
if docker exec ys-redis-dev redis-cli --no-auth-warning -a "$REDIS_PASSWORD" ping 2>/dev/null | grep -q PONG; then
pass "Redis 响应 PONG"
else
warn "Redis ping 无响应(检查 REDIS_PASSWORD 或容器状态)"
fi
else
warn "docker/.env 缺 REDIS_PASSWORD,跳过 Redis 探活"
fi
# 5. 登录日志异常(24h 失败次数)
if [[ -n "${MYSQL_ROOT_PASSWORD:-}" ]] && docker inspect ys-mysql-dev >/dev/null 2>&1; then
FAILS=$(docker exec ys-mysql-dev sh -c \
"mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\" ${MYSQL_DATABASE:-payroll} -N -B -e \"
SELECT COUNT(*) FROM login_logs
WHERE success = 0 AND create_time > NOW() - INTERVAL 24 HOUR;\" 2>/dev/null" || echo "?")
if [[ "$FAILS" == "?" ]]; then
warn "登录日志查询失败"
elif (( FAILS < 10 )); then
pass "24h 登录失败 ${FAILS} 次(阈值 10)"
else
warn "24h 登录失败 ${FAILS} 次(可能有暴力破解,建议核查)"
fi
else
warn "跳过登录日志检查(无凭据或容器未运行)"
fi
echo "=============================================================="
if (( ISSUES == 0 )); then
echo "结论:全部通过,系统健康。"
exit 0
else
echo "结论:发现 $ISSUES 项需关注(见上)。"
exit 1
fi