97·实战项目高级

实战:批量运维自动化脚本

automationscripting

第97课 - 实战:批量运维自动化脚本

学习目标

通过本课学习,你将能够:

  1. 理解自动化运维的价值:认识到手动重复操作的局限性,并理解脚本自动化如何提升效率与可靠性。
  2. 掌握批量操作的核心方法:学习使用 for 循环结合 SSH、scprsync 等工具,在多台服务器上批量执行命令或传输文件。
  3. 编写结构健壮的自动化脚本:学会使用配置文件管理目标主机列表,添加日志记录和错误处理机制,使脚本更易于维护和排错。
  4. 实现错误处理与日志记录:在脚本中捕获错误,并将执行过程的关键信息写入日志文件,便于事后审计和问题追踪。
  5. 安全地部署和执行脚本:了解如何通过密钥认证、sudo配置等方式,安全、无交互地运行批量任务。

核心概念

在实际运维工作中,我们经常需要对一批服务器执行相同的操作,例如:更新软件包、分发配置文件、重启某个服务、收集系统信息等。如果服务器数量较多,手动逐一登录操作不仅耗时,而且极易出错。

自动化脚本就是为了解决这个问题而生的。它就像是一个“万能遥控器”,你可以提前设定好要执行的命令和目标主机列表,然后一键让所有机器同步执行。

本课我们将主要使用 Bash 脚本作为自动化工具,因为它功能强大、几乎所有 Linux 系统都内置支持。我们的核心思路是:

  1. 配置管理:将目标服务器地址、用户名等信息存放在一个单独的配置文件中(如 hosts.txt)。
  2. 循环控制:使用 for 循环遍历配置文件中的每一台主机。
  3. 远程执行:通过 ssh 命令在远程主机上执行命令。
  4. 错误处理与日志:检查每一步的执行结果($?),将成功或失败的信息记录到日志文件,方便排查。

代码示例:一个综合性的批量运维脚本

以下是一个名为 update_servers.sh 的脚本,它演示了如何批量更新一组服务器的系统软件包,并记录执行结果。

#!/bin/bash
# 脚本名称: update_servers.sh
# 功能:批量更新指定服务器的软件包,并记录日志。

# === 配置区 ===
# 目标主机列表文件,每行格式: 主机地址 用户名
HOSTS_FILE="./hosts.txt"
# SSH 私钥文件路径(建议使用免密钥登录)
SSH_KEY="~/.ssh/id_rsa"
# 日志文件
LOG_FILE="./update_$(date +%Y%m%d_%H%M%S).log"
# 要执行的远程命令(以 root 权限更新软件包)
REMOTE_CMD="apt update && apt upgrade -y"

# === 函数定义 ===
# 日志函数:将消息同时输出到屏幕和日志文件
log() {
    local message="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
    echo "$message" | tee -a "$LOG_FILE"
}

# 远程执行函数:在一台主机上执行命令
execute_on_host() {
    local host=$1
    local user=$2
    log ">>> 开始处理主机: ${user}@${host}"
    
    # 执行远程命令,将标准输出和错误输出都重定向到日志(追加模式)
    ssh -i ${SSH_KEY} -o ConnectTimeout=10 -o BatchMode=yes ${user}@${host} "${REMOTE_CMD}" >> "${LOG_FILE}" 2>&1
    local exit_code=$?
    
    if [ $exit_code -eq 0 ]; then
        log "<<< 主机 ${host} 更新成功。"
    else
        log "<<< [错误] 主机 ${host} 更新失败,退出码: ${exit_code}"
    fi
    
    # 返回命令执行结果,供主循环判断
    return $exit_code
}

# === 主流程 ===
log "===== 批量更新任务开始 ====="
log "主机列表文件: ${HOSTS_FILE}"

# 检查主机列表文件是否存在
if [ ! -f "$HOSTS_FILE" ]; then
    log "[严重错误] 主机列表文件 ${HOSTS_FILE} 不存在!"
    exit 1
fi

# 统计变量
total_hosts=0
success_hosts=0
failed_hosts=0

# 遍历主机列表文件
while IFS=' ' read -r host user; do
    # 跳过空行和注释行(以#开头)
    [[ -z "$host" || "$host" =~ ^# ]] && continue
    
    total_hosts=$((total_hosts + 1))
    
    # 调用函数在主机上执行命令
    if execute_on_host "$host" "$user"; then
        success_hosts=$((success_hosts + 1))
    else
        failed_hosts=$((failed_hosts + 1))
    fi
done < "$HOSTS_FILE"

# === 结果汇报 ===
log "===== 批量更新任务结束 ====="
log "总计主机数: ${total_hosts}"
log "成功主机数: ${success_hosts}"
log "失败主机数: ${failed_hosts}"
log "详细日志请查看: ${LOG_FILE}"

# 如果有任何一台主机失败,则脚本以非零状态退出
if [ $failed_hosts -gt 0 ]; then
    exit 1
else
    exit 0
fi

配套的 hosts.txt 文件示例:

# 这是一个注释行
192.168.1.101 admin
192.168.1.102 ops
# 192.168.1.103 root  # 这一行被注释掉了,不会执行
10.0.0.5 deploy

实践练习

练习 1:基础 - 添加新的服务器

  1. hosts.txt 文件中添加两台你管理的测试服务器(或虚拟机)的地址和用户名。
  2. 修改 update_servers.sh 中的 REMOTE_CMD 变量,将命令改为 "echo 'Hello from $(hostname)' && uptime",即打印主机名和运行时间。
  3. 运行脚本,观察日志文件,确认在两台新服务器上成功执行了命令。

练习 2:进阶 - 增加磁盘空间检查

基于 update_servers.sh 脚本,编写一个新的脚本 check_disk.sh

  • 要求
    1. 脚本功能:检查 hosts.txt 中所有主机的根分区(/)磁盘使用率。
    2. 远程命令使用 df -h / | tail -1 | awk '{print $5}' 来获取使用率百分比。
    3. 如果某台主机的磁盘使用率超过 80%,则在日志中特别标出警告。
    4. 最终汇总报告中,列出所有磁盘使用率超过 80% 的主机。
  • 预期输出(在日志和屏幕上):
    >>> 开始检查主机: [email protected]
    磁盘使用率: 45%
    <<< 主机 192.168.1.101 正常。
    >>> 开始检查主机: [email protected]
    磁盘使用率: 85%
    [警告] 主机 192.168.1.102 磁盘使用率超过 80%!
    <<< 主机 192.168.1.102 检查完成。
    ===== 磁盘检查任务结束 =====
    警告主机列表:
    - 192.168.1.102 (85%)
    

练习 3:挑战 - 实现带条件的服务重启

编写脚本 restart_service_if_down.sh

  • 要求
    1. 为脚本增加一个输入参数,用于指定要检查的服务名称(如 nginx)。
    2. hosts.txt 中的每台主机上,检查该服务的运行状态。
    3. 如果服务未运行(状态为 inactive (dead)),则尝试重启它。
    4. 记录每台主机的服务初始状态、是否执行了重启、以及重启后的最终状态。
    5. 如果服务未安装,则记录为“未找到”,不尝试重启。
  • 使用方式./restart_service_if_down.sh nginx

常见错误

  1. SSH 连接问题

    • 错误Permission denied (publickey,password)Connection refused
    • 解决:确保 hosts.txt 中的用户名正确;确保目标主机的 SSH 服务已开启并允许该用户登录;最佳实践是提前配置好基于 SSH 密钥的免密登录。
  2. 脚本权限问题

    • 错误-bash: ./update_servers.sh: Permission denied
    • 解决:使用 chmod +x update_servers.sh 为脚本添加可执行权限。
  3. 变量引用错误

    • 错误:远程命令中的变量(如 $())被本地 shell 解析,而不是在远程执行。
    • 解决:对整个远程命令使用单引号包裹,例如 ssh host 'echo $(hostname)'。单引号内的内容会被原样传递给远程 shell。
  4. 路径问题

    • 错误:脚本中引用了相对路径(如 ./hosts.txt),但在其他目录运行时找不到文件。
    • 解决:在脚本中,先使用 cd "$(dirname "$0")" 切换到脚本所在目录,或者使用绝对路径。
  5. 错误处理不当

    • 错误:脚本遇到错误继续执行,最终报告“全部成功”,但实际上部分任务失败。
    • 解决:像示例中那样,使用 $? 捕获每条命令的退出码,并根据退出码进行条件判断和记录。使用 set -e 可以让脚本在遇到第一个错误时立即退出(需谨慎使用,可能不适用于所有场景)。

小结

本节课我们通过一个实战案例,学习了批量运维自动化脚本的核心要素:

  • 配置化:将可变部分(如主机列表)从脚本逻辑中分离出来,使脚本更灵活。
  • 模块化:用函数封装重复逻辑(如日志、执行),让代码更清晰、易维护。
  • 健壮性:通过错误检查($?)和日志记录,让脚本的执行过程和结果可观测、可追溯。
  • 安全性:强调使用密钥认证、BatchMode=yes 等参数进行安全的非交互式连接。

掌握编写自动化脚本的能力,是从“手动运维”迈向“高效运维”的关键一步。它不仅能将你从重复劳动中解放出来,更能确保操作的一致性和可靠性,是高级运维工程师的必备技能。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「实战:搭建 Prometheus + Grafana 监控」 以巩固所学知识。