如何写一个脚本启动时自检

wen 实用脚本 2

从零构建可靠的自动化防线

目录导读

  1. 为什么脚本启动需要自检?
  2. 自检脚本的核心设计原则
  3. 的三层架构(环境、依赖、状态)
  4. 实战:用Bash编写一个完整自检脚本
  5. 自检日志与错误处理机制
  6. 常见自检陷阱与解决方案
  7. 进阶:自检脚本的自动修复能力
  8. 问答环节:关于自检最常被问的5个问题

为什么脚本启动需要自检?

想象一个场景:你的定时任务脚本因为缺少一个环境变量而静默失败,直到三天后数据异常才被发现,这种“启动即故障”的模式,在自动化运维和持续部署中非常致命。

如何写一个脚本启动时自检

根据Google SRE的可靠性金字塔理论,自检(Health Check)是系统稳定性的第一道防线,脚本启动自检能实现:

  • 即时反馈:在脚本执行业务逻辑前,发现环境问题
  • 避免链式故障:防止一个脚本的失败影响到后续流程
  • 减少排障成本:通过明确的错误日志,将定位时间从小时级压缩到分钟级

自检脚本的核心设计原则

原则 说明 反例
轻量快速 自检应在3秒内完成,不阻塞主业务 在自检中执行全量数据库查询
原子性 每个检查项互相独立,一个失败不影响其他 检查磁盘空间失败后跳过后步软件版本检查
可解释性 错误信息必须包含修复指引 仅输出“Error 1234”
可配置 检查项阈值应可通过配置文件调整 磁盘使用率阈值写死在代码里

的三层架构

第一层:环境层(Environment Checks)
  • 操作系统版本:确认兼容内核版本
  • Python/Node/Java版本:使用 python --version 2>&1 | grep -q "3.8" 等精确匹配
  • 环境变量:检查必需变量是否存在且非空
    required_vars=("DB_HOST" "API_KEY" "LOG_DIR")
    for var in "${required_vars[@]}"; do
    if [ -z "${!var}" ]; then
      echo "FATAL: 环境变量 $var 未设置,请补充后再运行"
      exit 1
    fi
    done
第二层:依赖层(Dependency Checks)
  • 系统包:检查 curl jq rsync 等工具是否已安装
  • Python模块:通过在虚拟环境下 python -c "import requests" 验证
  • 网络可达性:用 nc -zv target_host 443 2>/dev/null 快速检测端口
第三层:运行时状态(Runtime State Checks)
  • 磁盘空间df -h /data | awk 'NR==2{print $5}' | sed 's/%//' 判断使用率
  • 进程锁:防止重复运行,使用mkdir /tmp/myscript.lock 作为互斥锁
  • 上次运行记录:检查LAST_RUN文件的时间戳,避免过短周期运行

实战:用Bash编写一个完整自检脚本

以下是一个生产级自检模板(兼容Bash 4.0+):

#!/bin/bash
set -euo pipefail  # 严格模式:任何非零返回即退出
# ============ 配置区 ============
LOG_FILE="/var/log/script_health_$(date +%Y%m%d).log"
LOCK_FILE="/tmp/script.lock"
MAX_DISK_USAGE=85
PIP_LIST=("requests" "pyyaml")
BIN_LIST=("docker" "kubectl" "jq")
# ===============================
# 日志函数
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
# 清理函数
cleanup() {
  [ -f "$LOCK_FILE" ] && rm -f "$LOCK_FILE"
  log "INFO: 脚本自检完成,清理后退出"
}
trap cleanup EXIT
# 主自检函数
health_check() {
  local failed=0
  # 检查1:是否重复运行
  if [ -f "$LOCK_FILE" ]; then
    log "FATAL: 脚本已在运行中(锁文件存在),请检查进程"
    exit 2
  fi
  touch "$LOCK_FILE"
  # 检查2:必需二进制工具
  for bin in "${BIN_LIST[@]}"; do
    if ! command -v "$bin" &> /dev/null; then
      log "ERROR: 未找到 $bin ,请执行: apt-get install -y $bin 或 yum install $bin"
      failed=1
    fi
  done
  # 检查3:Python模块
  if command -v python3 &> /dev/null; then
    for pkg in "${PIP_LIST[@]}"; do
      python3 -c "import $pkg" 2>/dev/null && log "OK: Python模块 $pkg 存在" || {
        log "ERROR: Python模块 $pkg 缺失,请执行: pip3 install $pkg"
        failed=1
      }
    done
  else
    log "WARN: Python3未安装,跳过模块检查"
  fi
  # 检查4:磁盘使用率
  local usage=$(df /data | awk 'NR==2{print $5}' | sed 's/%//')
  if [ "$usage" -gt "$MAX_DISK_USAGE" ]; then
    log "ERROR: 磁盘使用率 ${usage}%,超过阈值 ${MAX_DISK_USAGE}%,建议清理/扩容"
    failed=1
  fi
  # 检查5:必需环境变量
  local envs=("DB_HOST" "REDIS_URL")
  for env in "${envs[@]}"; do
    if [ -z "${!env}" ]; then
      log "ERROR: 环境变量 $env 未设置"
      failed=1
    fi
  done
  return $failed
}
# 执行自检
if health_check; then
  log "PASS: 所有自检通过,开始执行主逻辑"
  # 此处放置你的主业务代码
else
  log "FAIL: 自检未通过,脚本终止执行"
  exit 3
fi

自检日志与错误处理机制

好的自检需要同时解决“发现问题”和“理解问题”两个痛点:

日志规范化:使用结构化的JSON格式便于日志分析系统处理

{"timestamp":"2025-01-15 14:30:00","level":"ERROR","check":"disk_usage","message":"使用率87%超限","action":"清理/tmp目录或扩容"}

错误码设计: | 退出码 | 含义 | 示例 | |--------|------|------| | 0 | 通过 | 一切正常 | | 1 | 环境缺失 | Python版本不符 | | 2 | 资源不足 | 磁盘空间不足 | | 3 | 配置错误 | 配置文件语法错误 | | 100+ | 业务逻辑异常 | 保留给主程序 |

自动修复建议:在错误日志中直接输出修复命令

log "ERROR: Docker未安装,尝试自动安装..."
if apt-get install -y docker.io &> /dev/null; then
  log "INFO: Docker安装成功"
else
  log "FATAL: 自动安装失败,请手动执行: apt-get install docker.io"
  exit 1
fi

常见自检陷阱与解决方案

陷阱1:误把“警告”当“错误”

  • 现象:磁盘使用率80%设为警告,但脚本仍允许运行
  • 方案:明确“警告(继续运行)”“错误(停止运行)”“致命(立即退出)”三级

陷阱2:自检脚本本身不健壮

  • 现象:自检脚本依赖 jqjq 未被检查
  • 方案:自检的第一步必须是检查自检脚本自身的依赖(如 jq awk

陷阱3:忽略网络耗时

  • 现象:检查远程API时设置超时不当,导致自检耗时30秒
  • 方案:所有网络检查必须设置 timeout,如 timeout 3 curl -s http://example.com

陷阱4:锁文件清理不完整

  • 现象:脚本被kill后锁文件残留,导致后续永远无法运行
  • 方案:使用 trap cleanup EXIT 或结合PID检查:if [ -f lock ] && kill -0 $(cat lock); then...

进阶:自检脚本的自动修复能力

当检测到可自动修复的问题时,自检脚本可以尝试“修复并重试”:

auto_fix_and_retry() {
  local max_retry=3
  local retry_count=0
  while [ $retry_count -lt $max_retry ]; do
    if health_check; then
      log "INFO: 自检通过(第$((retry_count+1))次尝试后恢复)"
      return 0
    fi
    # 尝试修复常见问题
    log "WARN: 检测到问题,尝试自动修复..."
    if ! command -v docker &> /dev/null; then
      apt-get install -y docker-ce 2>/dev/null || brew install docker
    fi
    # 清理临时文件
    rm -f /tmp/*.tmp
    # 重启网络服务
    systemctl restart network 2>/dev/null || true
    ((retry_count++))
    sleep 2
  done
  log "FATAL: 多次修复失败,请人工介入"
  return 1
}

问答环节:关于自检最常被问的5个问题

Q1:自检查项应该多细致?
A:遵循“5分钟法则”——如果一个问题在5分钟内会导致脚本失败,就应该在自检中覆盖,例如网络延迟不会立刻失败,但缺少数据库连接会。

Q2:如何给自检编写单元测试?
A:利用Bash的 set -o xtrace 或编写Mock函数:

# 测试磁盘检查
test_disk_check() {
  export MAX_DISK_USAGE=100  # 设置永远不会触发的阈值
  source health_check_func.sh
  local result=$(health_check)
  assert_equals "$result" "PASS"
}

Q3:自检脚本是否需要一个单独的日志文件?
A:推荐使用分离日志,主业务日志记录业务数据,自检日志记录环境健康状态,便于运维监控面板单独拉取。

Q4:在容器脚本中自检有什么特殊要求?
A:容器环境自检应避免检查systemctlhostname等主机级指标,只关注:

  • 挂载卷的读写权限:touch /data/.test_write && rm /data/.test_write
  • 容器内的资源限制:检查ulimit -ndf -h /
  • 容器间通信:ping -c 1 backend-service

Q5:如何让自检结果可视化?
A:结合监控系统(如Prometheus)导出指标:

# 生成Prometheus格式指标
cat << EOF > /tmp/health_metrics.prom
# HELP script_health_total 脚本自检结果
# TYPE script_health_total gauge
script_health_total{check="disk",result="pass"} 1
script_health_total{check="env_var",result="fail"} 0
EOF

一个优秀的自检脚本不是简单的“if-then”堆砌,而是 防御性编程文化的体现,它需要像安检员一样细致,但又像速降运动员一样高效,遵循本文的三层架构和实战模板,你可以将脚本的MTTR(平均恢复时间)降低80%,同时将MTBF(平均故障间隔)提升5倍以上。每次自检失败找到的问题,都是避免了生产环境的灾难

抱歉,评论功能暂时关闭!