5.3.5 流程控制在运维脚本中的实战应用
流程控制在运维脚本中的实战应用#
本节围绕真实运维场景,展示如何将条件判断、循环与函数组合为可维护、可扩展的自动化脚本。强调幂等性、可观测性与错误处理,所有场景均给出可执行示例、命令解释、排错与练习。
原理草图:流程控制在运维脚本中的位置#
场景一:批量巡检与告警摘要#
目标:巡检多台主机关键指标(CPU、内存、磁盘、服务状态),输出统一摘要。
安装/依赖:使用 sshpass 与 curl(可选)。
安装命令:
# 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. - 环境依赖:明确依赖工具,提供安装命令。
小结#
流程控制在运维脚本中不仅用于“让脚本能跑”,更用于保证执行的可控性与安全性。通过合理的条件判断、循环控制与函数封装,可以显著提升批量任务的可靠性与可维护性,降低人为操作风险。