5.3.5 流程控制在运维脚本中的实战应用

流程控制在运维脚本中的实战应用#

本节围绕真实运维场景,展示如何将条件判断、循环与函数组合为可维护、可扩展的自动化脚本。强调幂等性、可观测性与错误处理,所有场景均给出可执行示例、命令解释、排错与练习。

原理草图:流程控制在运维脚本中的位置#

文章图片

场景一:批量巡检与告警摘要#

目标:巡检多台主机关键指标(CPU、内存、磁盘、服务状态),输出统一摘要。
安装/依赖:使用 sshpasscurl(可选)。
安装命令

# RHEL/CentOS
yum install -y sshpass curl
# Ubuntu/Debian
apt-get update && apt-get install -y sshpass curl

脚本示例:/opt/scripts/health_check.sh

#!/usr/bin/env bash
set -euo pipefail

HOSTS_FILE=${1:-/opt/scripts/hosts}
CPU_WARN=80
DISK_WARN=85
OUT=/opt/scripts/health_report.csv

log() { echo "[$(date '+%F %T')] $*"; }

check_cpu() {
  local host=$1
  ssh "$host" "top -bn1 | awk '/Cpu/ {print 100-\$8}'" | awk '{printf "%.0f\n",$1}'
}

check_mem() {
  local host=$1
  ssh "$host" "free -m | awk '/Mem/ {printf \"%.0f\", \$3/\$2*100}'"
}

check_disk() {
  local host=$1
  ssh "$host" "df -P / | awk 'NR==2 {gsub(\"%\",\"\",\$5); print \$5}'"
}

check_service() {
  local host=$1
  local svc=$2
  ssh "$host" "systemctl is-active $svc" 2>/dev/null || echo "unknown"
}

echo "host,cpu%,mem%,disk%,nginx,status" > "$OUT"

while read -r host; do
  [[ -z "$host" || "$host" =~ ^# ]] && continue
  cpu=$(check_cpu "$host")
  mem=$(check_mem "$host")
  disk=$(check_disk "$host")
  nginx=$(check_service "$host" "nginx")

  status="OK"
  (( cpu > CPU_WARN )) && status="CPU_WARN"
  (( disk > DISK_WARN )) && status="${status}|DISK_WARN"

  echo "$host,$cpu,$mem,$disk,$nginx,$status" >> "$OUT"
done < "$HOSTS_FILE"

log "report saved to $OUT"

命令解释
- top -bn1 | awk '/Cpu/ {print 100-$8}':获取 CPU 使用率。
- free -m | awk '/Mem/ {printf "%.0f", $3/$2*100}':内存使用率。
- df -P /:根分区磁盘占用。
- systemctl is-active nginx:服务状态检查。

运行示例

chmod +x /opt/scripts/health_check.sh
/opt/scripts/health_check.sh /opt/scripts/hosts

排错要点
- SSH 失败:检查免密登录或 sshpass 参数。
- 返回空值:检查远端命令路径、systemctl 是否存在。
- CSV 缺列:确认脚本执行中未被 set -e 中断。

练习
1. 增加 ss -lnt 检查端口 80 是否监听。
2. 添加 -s service 参数,允许自定义检查服务名。


场景二:服务滚动重启与健康检查#

目标:对多个节点进行滚动重启,确保每一步健康后再继续。
脚本示例:/opt/scripts/rolling_restart.sh

#!/usr/bin/env bash
set -euo pipefail

HOSTS_FILE=${1:-/opt/scripts/hosts}
SERVICE=${2:-nginx}
RETRY=5
SLEEP=2

log() { echo "[$(date '+%F %T')] $*"; }

health_check() {
  local host=$1
  ssh "$host" "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/ | grep -q 200"
}

while read -r host; do
  [[ -z "$host" || "$host" =~ ^# ]] && continue
  log "restart $SERVICE on $host"
  ssh "$host" "systemctl restart $SERVICE"

  ok=0
  for ((i=1;i<=RETRY;i++)); do
    if health_check "$host"; then
      ok=1; break
    fi
    log "health check failed on $host, retry $i/$RETRY"
    sleep "$SLEEP"
  done

  if [[ $ok -ne 1 ]]; then
    log "abort: $host health check failed"
    exit 1
  fi
  log "$host OK"
done < "$HOSTS_FILE"

命令解释
- curl -s -o /dev/null -w '%{http_code}':获取 HTTP 状态码。
- for ((i=1;i<=RETRY;i++)):限制重试次数,避免死循环。

排错要点
- 健康检查失败:确认服务监听地址是否为 127.0.0.1
- systemctl 失败:检查服务名与权限。

练习
1. 添加 --grace 10 参数,在重启前 sleep 10
2. 失败时写入 /var/log/rolling_restart.err


场景三:日志清理与归档#

目标:按日期归档并删除过期日志,确保磁盘安全。
脚本示例:/opt/scripts/log_archive.sh

#!/usr/bin/env bash
set -euo pipefail

SERVICE=${1:-nginx}
DAYS=${2:-7}
BASE=/var/log
ARCHIVE=/data/log_archive

case "$SERVICE" in
  nginx) LOG_DIR="$BASE/nginx" ;;
  mysql) LOG_DIR="$BASE/mysql" ;;
  redis) LOG_DIR="$BASE/redis" ;;
  *) echo "unknown service"; exit 1 ;;
esac

mkdir -p "$ARCHIVE"

find "$LOG_DIR" -type f -mtime +"$DAYS" -name "*.log" -print0 |
while IFS= read -r -d '' f; do
  tar -czf "$ARCHIVE/$(basename "$f").$(date +%F).tgz" "$f"
  if [[ $? -eq 0 ]]; then
    rm -f "$f"
    echo "archived and removed $f"
  fi
done

命令解释
- find ... -mtime +N:查找 N 天前的文件。
- tar -czf:压缩归档。
- case:根据服务类型切换路径。

排错要点
- 归档目录无权限:检查 $ARCHIVE 权限。
- 未匹配日志:确认日志后缀与路径。

练习
1. 将归档文件按服务分目录存放。
2. 归档前检查磁盘占用超过 80% 才执行清理。


场景四:配置一致性校验与自动修复#

目标:校验配置是否符合标准,不一致则自动修复并重载服务。
脚本示例:/opt/scripts/config_fix.sh

#!/usr/bin/env bash
set -euo pipefail

TEMPLATE=/opt/templates/nginx.conf
TARGET=/etc/nginx/nginx.conf
SERVICE=nginx

if diff -q "$TEMPLATE" "$TARGET" >/dev/null; then
  echo "config OK"
else
  echo "config drift, fixing..."
  cp "$TEMPLATE" "$TARGET"
  systemctl reload "$SERVICE"
  echo "fixed and reloaded"
fi

命令解释
- diff -q:只判断是否有差异。
- systemctl reload:平滑重载配置。

排错要点
- reload 失败:运行 nginx -t 先校验配置。
- 模板缺失:检查 $TEMPLATE 文件路径。

练习
1. 增加 nginx -t 失败时不覆盖配置。
2. 记录变更到 /var/log/config_fix.log


场景五:批量账户与权限治理#

目标:批量创建运维账户、设置权限、保障幂等。
脚本示例:/opt/scripts/user_manage.sh

#!/usr/bin/env bash
set -euo pipefail

USERS_FILE=${1:-/opt/scripts/users}
SSH_KEY=${2:-/opt/scripts/id_rsa.pub}

while read -r u; do
  [[ -z "$u" || "$u" =~ ^# ]] && continue
  if id "$u" &>/dev/null; then
    echo "$u exists, skip"
  else
    useradd -m "$u"
    echo "$u ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/"$u"
    mkdir -p /home/"$u"/.ssh
    cat "$SSH_KEY" >> /home/"$u"/.ssh/authorized_keys
    chown -R "$u":"$u" /home/"$u"/.ssh
    chmod 700 /home/"$u"/.ssh && chmod 600 /home/"$u"/.ssh/authorized_keys
    echo "$u created"
  fi
done < "$USERS_FILE"

命令解释
- id user:判断用户是否存在。
- /etc/sudoers.d/:精细授权,避免修改主文件。
- chmod:权限安全设置。

排错要点
- sudoers 语法错误:使用 visudo -c 检查。
- SSH 无法登录:确认公钥内容与权限。

练习
1. 将 NOPASSWD 改为只允许 /bin/systemctl.
2. 支持 -d 参数删除用户并备份家目录。


通用脚本结构建议(可直接复用)#

模板示例:/opt/scripts/template.sh

#!/usr/bin/env bash
set -euo pipefail

usage() { echo "Usage: $0 -f hosts -s service"; exit 1; }

log_info() { echo "[INFO] $*"; }
log_err() { echo "[ERROR] $*"; }

while getopts "f:s:" opt; do
  case $opt in
    f) HOSTS_FILE=$OPTARG ;;
    s) SERVICE=$OPTARG ;;
    *) usage ;;
  esac
done

[[ -z ${HOSTS_FILE:-} || -z ${SERVICE:-} ]] && usage

# 预检查
command -v ssh >/dev/null || { log_err "ssh not found"; exit 1; }

排错要点
- 参数缺失时退出,避免误操作。
- 预检查依赖命令,避免脚本中途失败。


常见陷阱与规避#

  • 无限循环:加入计数器与超时控制。
  • 错误吞噬:检查关键命令返回值,避免 || true 滥用。
  • 并发冲突:加入锁文件,如 flock /var/lock/task.lock.
  • 环境依赖:明确依赖工具,提供安装命令。

小结#

流程控制在运维脚本中不仅用于“让脚本能跑”,更用于保证执行的可控性与安全性。通过合理的条件判断、循环控制与函数封装,可以显著提升批量任务的可靠性与可维护性,降低人为操作风险。