#!/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