ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux运维自动化脚本实战:从巡检采集到告警通知的完整闭环

Linux运维自动化脚本实战:从巡检采集到告警通知的完整闭环 简介面向Linux运维工程师的自动化脚本合集聚焦系统运维中高频重复的部署、升级与备份操作适用于服务器批量维护、环境初始化与故障恢复等场景压缩包共19个文件大小4.51MB包含9个sh脚本、4个md文档、3个rpm安装包、1个txt说明、1个gz源码包及1个keep占位文件结构简洁。sh脚本是核心执行文件md文档提供对应教程rpm与gz则补充了wget、OpenSSH等软件依赖和源码。特别值得一提的是OpenSSH一键升级脚本支持CentOS 6、CentOS 7与CentOS 8并配有详细升级说明此外还收录Oracle数据库的安装与增量备份脚本、Zabbix 5一键安装脚本、Nginx升级脚本、Docker安装与镜像加速脚本覆盖常见运维场景。已有2485人学习下载读者可直接修改适配环境大幅减少重复手工操作快速完成系统组件升级、数据库部署与监控搭建是Linux运维实战中值得留存的工具箱。1. Linux运维自动化脚本能解决什么先分清“脚本”和“自动化”再动手凌晨一点半被电话叫醒登上服务器一看磁盘满了业务停了四十分钟而这种“叫醒服务”你已经接过好几回或者每天早上一台台 SSH 上去敲 df、free、uptime敲完还要截图汇报。这两个场景你只要占一个就是该上自动化脚本的时候了。所谓 Linux 运维自动化脚本不是背一份 Linux 常用命令大全运维而是把巡检、告警、清理、拉起这套动作写成机器能准时执行的流程让“人肉盯着”变成“脚本兜底”。它适合刚接手三五台服务器就忙不过来的组也适合用第一套脚本给团队立规矩。下面这套做法是我自己在生产环境摸出来的照着改就能落地。2. 从零攒一套巡检脚本磁盘、内存、负载与关键进程的采集逻辑2.1 先定巡检清单指标怎么选、阈值怎么定我不建议一上来就找现成大脚本。先把你环境里“出过事”的指标列出来通常就是磁盘、内存、CPU 负载、关键端口和进程、系统时钟这几类。巡检脚本第一版不要追求全把下面这张表里的项抓准就够了指标获取命令预警阈值严重阈值磁盘使用率df -P80%90%inode 使用率df -i80%90%内存可用量free -m 或 /proc/meminfo可用 20%可用 10%CPU 负载cat /proc/loadavg 核心数 × 1.5 核心数 × 3关键进程pgrep -x找不到进程找不到且重试也失败时间偏差chronyc tracking 500ms 5s阈值不要照抄得按你的机器配置调四核机器和十六核机器的负载阈值差着好几倍数据库服务器磁盘用到 80% 就该处理备份机用到 90% 还能扛。这里关键是脚本只负责把“不正常”报出来别试图替你做诊断诊断是收到告警之后人的事。确定了清单再动手写脚本结构会清晰很多。很多人跳过了这步结果脚本越加越长成了没人敢改的黑匣子最后反而比人肉巡检还脆弱。先清单后代码这是自动化运维里最有用的习惯。2.2 一个能直接改用的巡检脚本从指标采集到分级告警下面这段脚本是一个日常巡检的骨架我一般会放在 /usr/local/sbin/check_server.sh然后把可执行权限配好#!/bin/bash # check_server.sh - 基础巡检脚本 # 用法: ./check_server.sh [--warn-threshold80 --crit-threshold90] export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin set -o pipefail WARN80 CRIT90 LOG_FILE/var/log/server_check/server_check.log HOST$(hostname) TS$(date %Y-%m-%d %H:%M:%S) # 参数解析 while [[ $# -gt 0 ]]; do case $1 in --warn-threshold*) WARN${1#*} ;; --crit-threshold*) CRIT${1#*} ;; *) echo 未知参数: $1; exit 2 ;; esac shift done mkdir -p $(dirname $LOG_FILE) log() { echo $TS [$HOST] $* $LOG_FILE; } # 磁盘使用率检查 df -P | awk NR1 {print $5, $6} | tr -d % | while read -r USE MOUNT; do if [ $USE -ge $CRIT ]; then log CRIT 磁盘 $MOUNT 使用率 ${USE}% elif [ $USE -ge $WARN ]; then log WARN 磁盘 $MOUNT 使用率 ${USE}% fi done # 内存可用率检查 ava$(awk /MemAvailable/{print $2} /proc/meminfo) total$(awk /MemTotal/{print $2} /proc/meminfo) ava_per$((ava * 100 / total)) if [ $ava_per -lt 10 ]; then log CRIT 内存可用 ${ava_per}% elif [ $ava_per -lt 20 ]; then log WARN 内存可用 ${ava_per}% fi # 负载检查 cores$(nproc) load$(awk {print $1} /proc/loadavg) load_limit1$(echo $cores * 1.5 | bc) load_limit2$(echo $cores * 3 | bc) if [ $(echo $load $load_limit2 | bc) 1 ]; then log CRIT 负载 $load 超过 ${load_limit2} elif [ $(echo $load $load_limit1 | bc) 1 ]; then log WARN 负载 $load 超过 ${load_limit1} fi # 关键进程检查 for p in nginx mysqld rsyslogd; do if ! pgrep -x $p /dev/null; then log CRIT 进程 $p 不存在 fi done log 巡检完成这里解释几个关键点脚本开头 export PATH 是为了抵御 crontab 里残缺的 PATH很多定时执行报错都是没写这行导致找不到命令set -o pipefail 保证 df | awk 里 awk 出错时整个管道能暴露失败而不是静默过关。磁盘检查我用 df -P 的 POSIX 输出格式避免不同发行版 df 列宽不一样导致 awk 取错列。内存不要看 free 的 used 列要看 MemAvailable它才是内核实际认为“还能给新程序用”的数字很多旧脚本因为看 used 会在缓存高的机器上误报。负载这一段的阈值我用了 bc 做小数比较因为 bash 自带的 [[ ]] 不认小数。如果你的系统没装 bc可以简单改成整数判断if [ $load -gt $((cores * 3)) ]精度略差但够用。参数解析只做了--warn-threshold这一种写法这样你在 crontab 里可以按机器差别传不同阈值比如备份机用--warn-threshold85。2.3 把输出留成日志时间戳、目录权限与后续排查脚本跑完要能查到历史记录告警信息才有价值。上面代码里我把日志写到了 /var/log/server_check/ 目录这有个容易被忽略的坑默认情况下系统日志目录只能 root 写如果你的 crontab 是用普通用户跑的日志目录要先建好并授权sudo mkdir -p /var/log/server_check sudo chown $USER:$USER /var/log/server_check日志文件命名我建议直接带日期方便按天回溯server_check_$(date \%Y\%m\%d).log。这种“一个文件归一天”的做法比单文件追加好排查配合日志轮转也省事。写入日志时echo 里的$TS是脚本开头取的一次时间不是每行实测时间如果要精确到每步执行耗时得把 date 放进 log 函数里重新取。生产上我见过有人为了省事把 date 写在 log 函数外结果日志里几十条时间戳全是同一秒排查耗时问题的时候直接被误导。巡检脚本写到这里就已经具备雏形。接下来要把它挂到定时任务里并且把告警真正推出去——这一步才是“自动化”的开始。3. 把巡检串成定时任务crontab、日志轮转与告警通知的完整闭环3.1 crontab 调度的正确写法环境变量、执行频率和日志重定向脚本写好了接下来要解决“谁去定时跑它”的问题。常见做法是用 crontab它最适合“每隔几分钟收集一次状态”这类固定节奏的任务。这里我不建议用 systemd timer不是它不好而是 crontab 在所有 Linux 发行版上都默认存在团队排障成本低。先看一个推荐写法# 每天 8 点和 20 点各巡检一次 0 8,20 * * * /usr/local/sbin/check_server.sh --warn-threshold80 --crit-threshold90 /var/log/server_check/cron.log 21这一行的讲究在于第一脚本用的是绝对路径不要写相对路径crontab 的工作目录不是你 ssh 登录时那个目录第二和21必不可少否则脚本里向标准输出写的任何内容会变成系统邮件而你大概率不会去看那玩意等于把错误吞进黑洞第三如果多个机器共用同一个 crontab 模板最好把阈值参数显式写在 crontab 里而不是写死在脚本里这样全局调阈值时不用逐个改文件。另一个绕不开的问题是环境变量。crontab 执行时 PATH 通常被重置为/usr/bin:/bin而你的脚本可能依赖 /usr/local/bin 下的命令。解决办法有两个在脚本开头 export PATH或者干脆把命令的全路径写进脚本。我一般做组合拳脚本开头 export 一段完整的 PATH脚本内部再用command -v探测依赖是否齐全for cmd in bc curl awk; do command -v $cmd /dev/null || echo 缺少 $cmd $LOG_FILE done这样定时任务就算环境再奇葩也只会告警“缺命令”而不是干巴巴地报 command not found排查时一眼就能看出问题在哪。还有时区问题如果你的服务器分布在多个机房系统时区不一致同一份 crontab 配置会在不同绝对时间执行。新环境一律把时区统一并且明确 crontab 用的是哪个时区的本地时间。crontab 不支持秒级调度最小粒度是分钟如果某个任务需要 30 秒一次用 crontab 硬拼不如改成 systemd timer或者用 shell 循环加 sleep 自己控制节奏。3.2 告警通知别用邮件钉钉/企业微信 Webhook 的 curl 片段与去重巡检脚本就算把所有异常都写进日志没人去看等于白跑。所以要把“异常”变成“打扰”。现在团队里最常用的通知方式不是邮件而是钉钉或企业微信的群机器人 Webhook因为它不需要搭邮件服务器一个 curl 就能推送到手机。下面这段是一个通用的告警发送函数放到你的脚本里之后所有 CRIT 级别信息都可以通过它发出去# 告警 webhook 配置建议从环境变量读取不要写死在脚本里 WEBHOOK_URL${WEBHOOK_URL:-} send_alert() { local msg$1 if [ -z $WEBHOOK_URL ]; then return 0 fi local payload payload$(printf {msgtype:text,text:{content:[%s] %s}} $HOST $msg) curl -s -m 5 -X POST -H Content-Type: application/json \ -d $payload $WEBHOOK_URL /dev/null || \ echo 告警发送失败: $msg $LOG_FILE } # 在 CRIT 判断里调用 log CRIT 磁盘 /data 使用率 91% send_alert 磁盘 /data 使用率 91%请立即处理这里有几个点要特别提醒WEBHOOK_URL从环境变量读是为了不把 webhook 地址这种敏感信息散落在每台机器的脚本里你可以在 cron 的调用行前面加WEBHOOK_URLxxx环境变量也可以放到 /etc/profile.d/ 下面为全局定义。curl 的-m 5是超时时间很关键——告警发送一旦卡住不该让整个巡检脚本跟着挂起。我有一次就是没加超时内网 DNS 抖动把 curl 卡了 30 秒巡检任务全部堆积最后靠这个参数才消停。Webhook 推送最常见的问题是“告警轰炸”磁盘满这一个故障每次巡检都发一条一晚上手机被刷几十条。要想不被同事拉黑得给告警加一个去重状态。做法用状态文件记录“当前哪些项处于告警中”只有新出现或级别升级才推送STATE_FILE/var/run/check_server.state # 告警项 a 如果在状态文件里已存在则不再推送否则推送并写入状态文件 a磁盘 /data 使用率 91% if ! grep -qxF $a $STATE_FILE 2/dev/null; then send_alert $a echo $a $STATE_FILE fi故障恢复后记得清掉状态文件里对应项否则下次真出问题告警会永远被静默。3.3 日志轮转别等磁盘报警再处理logrotate 配置与验证方法自动化脚本跑久了日志会变成新的麻烦。最典型的就是巡检脚本每次追加写同一个文件写上半年就能到几个 GB。所以定时任务上线时日志轮转必须同步配上否则过几个月你会上演“自动化脚本把磁盘写满”的黑色幽默。logrotate 配置通常放在 /etc/logrotate.d/ 下。我给 server_check 建了一个/var/log/server_check/*.log { daily rotate 30 missingok notifempty compress delaycompress copytruncate create 0644 root root }这段配置我逐个解释一下daily表示每天轮转一次日志按天积累的场景别用size因为大小不可控rotate 30是保留最近 30 个归档也就是一个月的痕迹missingok让轮转器在日志不存在时不报错notifempty表示空日志不轮转compress和delaycompress配合延迟一天压缩防止应用还在往旧文件写数据时就被压缩掉copytruncate是关键——它先拷贝当前日志再清空原文件适合像巡检脚本这样保持文件句柄持续追加写入的场景。如果用create方案就必须让脚本重新打开文件句柄而我们的脚本不是守护进程做不到所以这里必须用copytruncate。配置好别急着让它自然生效先用 debug 模式验证语法sudo logrotate -d /etc/logrotate.d/server_check-d是 dry-run会打印出它打算做的事。看到日志路径和轮转动作正确再手动触发一次真轮转确认归档文件生成正常sudo logrotate -f /etc/logrotate.d/server_check ls -lh /var/log/server_check/还有一点容易漏logrotate 并不会把你已经写坏的日志救回来它只对之后的文件生效。如果服务器上已经有大文件等着处理轮转前先用 truncate 或手动归档解决掉别指望轮转器帮你删东西。生产上我见过一个案例运维以为配了 logrotate 就可以对已有的大文件放手不管结果日志文件 5GB 在那躺了一个月每次写日志都触发大量磁盘 IO。所以日志轮转配置完成之后第一件事是检查现有日志大小把超大的文件手动清掉一轮。到这里巡检脚本、定时调度、告警通知、日志轮转就闭环了。接下来这一章是踩坑记录很多都是我在生产环境里实打实栽过的写出来给你省点力气。4. 运维脚本避坑手册5 个让老手也翻车的高频问题4.1 脚本单独执行正常crontab 定时执行却报错现象你在终端里执行 /usr/local/sbin/check_server.sh 一切正常把它写进 crontab 后日志里出现一堆 command not found或者脚本根本没跑。原因crontab 执行时的环境是精简的PATH 通常只剩 /usr/bin:/bin而且不加载 /etc/profile。你终端里能用某个命令是因为登录 shell 加载了完整的用户环境。如果脚本依赖了 /usr/local/bin 下的工具在 crontab 环境里就会直接找不到。解决脚本开头固定写#!/bin/bash然后在第二行 export 一个完整的 PATH至少包含 /usr/local/sbin、/usr/local/bin、/usr/sbin、/usr/bin、/sbin、/bin。再用command -v探测一遍关键依赖缺失当下告警。上面 2.2 节示例里已经给了这两步剩下的就是把 crontab 里的调用路径也改成绝对路径双保险才算到位。4.2 磁盘明明还有空间脚本却报磁盘满了现象df -h 显示 / 分区的 Used 只有 60%但脚本里 df -P 的告警触发了或者某个应用突然写入失败提示 No space left on device可 df 看磁盘还一大堆余量。原因按顺序排查三类情况。第一inode 耗尽df -h 看不到要 df -i 看第二某些已删除的文件还被进程占用空间一直没释放lsof L1 能看到 deleted 文件第三脚本统计口径写错比如把 df 输出第一行的表头也算进去了或者对 NFS 挂载点重复统计。我在 2.2 节用NR1过滤表头就是防这一类错。解决脚本里同时检查 df -P 和 df -i遇到告警先把两个输出都贴出来收到告警先跑df -h df -i再看有没有 deleted 文件占空间。注意告警脚本本身别在磁盘满时因为写日志失败而退出日志写不进去时至少要保留一份 stderr 可读的输出。4.3 自动化脚本的日志把磁盘写满现象巡检脚本正常运行了半年某一天 / 分区突然满了一查发现 /var/log/server_check/ 下有两个几十 GB 的原始日志文件轮转配置明明配了却不生效。原因logrotate 是按 /etc/logrotate.d/ 下的配置轮转的但 cron.daily 里 logrotate 的执行时间和你脚本写日志的时间可能有冲突更常见的是配置里路径写错比如脚本写的是 /var/log/server_check/check.log而 logrotate 配置写的是 /var/log/server_check/*.log通配符没覆盖到带后缀的文件还有种情况是日志文件在 logrotate 执行之后被脚本创建当天错过轮转窗口就一直不转。解决logrotate 配置里直接写全路径而非通配符必要时并列写两个文件路径配好之后跑一次sudo logrotate -f强制轮转确认过能转的文件列表留个记录避免“以为配了其实没配”的误会。另外在脚本里给日志加个大小自检日志超过 200MB 就自动 truncate防止 logrotate 失效时无人看守。4.4 同一脚本跑第二遍结果把服务弄挂了现象磁盘告警脚本写得好好的手动重跑一遍做测试结果进程列表里 nginx 出现了两份或者备份脚本跑第二次把昨天的备份覆盖了。原因脚本没有幂等设计。启动类操作没有先探测进程是否已存在直接又拉一遍写文件的脚本用覆盖而非或者清理旧文件的逻辑把正跑着的进程关联文件误删了。这类问题在变更类脚本里特别常见巡检类还好一旦脚本带“动作”就要高度警惕。解决所有启动类操作前先pgrep -f查一遍已存在就直接跳过所有写文件操作尽量用带日期的方式命名覆盖前先备份旧文件删除操作加保护条件——需要删除的路径必须以配置的目录前缀开头比如case $path in /data/backup/*) rm -rf $path ;; *) echo 拒绝删除 ;; esac。养成“脚本能重复执行且结果一致”的习惯以后连 runbook 都能省一半。4.5 从 Windows 编辑后传上来的脚本跑起来全是乱码和花式报错现象脚本在本机写好后传到 Linux 服务器执行时提示$\r: command not found或者明明有执行权限却报/bin/bash^M: bad interpreter。这是新手最容易被吓到的 Linux 运维故障案例之一。原因Windows 记事本等编辑器会把换行符写成 CRLF文件被保存成带 BOM 或带 \r 的格式Linux 的 shell 把 \r 当成普通字符于是命令名后面被拼上一个不可见字符。解决传上去后统一处理一遍——sed -i s/\r$// 脚本名或者dos2unix 脚本名之后别再用 Windows 编辑器改在服务器上编辑用 vim并在 .vimrc 里写set fileformatunix。还要记得chmod x给可执行位这个和换行是同一批“新手三件套”问题每一样都能让脚本跑不起来且有得查。处理完可以用file 脚本名查看文件类型确认显示 UTF-8 Unicode text 而不是 with CRLF line terminators。5. 让脚本能被团队长期维护参数化、静态检查与只读演练5.1 参数化与退出码把脚本做成可配置的小工具巡检脚本如果只在你手上一台机器跑参数化可以随便一点一旦脚本要分发到多台服务器或者要交给同事维护就得把“机器相关的值”全部挪出来。我习惯的做法是脚本开头集中放配置区然后用变量默认值支持外部覆盖# 配置区脚本内不要到处出现硬编码 HOST_NAME${HOST_NAME:-$(hostname)} LOG_ROOT${LOG_ROOT:-/var/log/server_check} WARN${WARN:-80} CRIT${CRIT:-90}这里用${VAR:-默认值}的语法外部通过环境变量传入即可覆盖默认值。crontab 里调用时可以写成WARN75 CRIT85 /usr/local/sbin/check_server.sh这样阈值调整不用改脚本。另一个容易被低估的是退出码。脚本用退出码约定结果0 为成功1 为脚本内部错误2 为参数错误。这个约定要让所有脚本一致方便监控平台或外层编排脚本通过$?判断下游要不要接着跑。如果你的调度用的是批量执行工具退出码不规范会导致故障被吞。关于很多人喜欢在脚本开头加set -euo pipefail我的看法是可以用但要区分场景。巡检脚本这种“多条检测命令顺序执行”的场景set -e 会导致某条命令探测失败时脚本立刻退出后面的检测全部跳过反而丢失信息。我的习惯是保证变量不空、管道能暴露错误但不全局开着 -e在需要“一票否决”的环节单独用 if 判断。5.2 shellcheck 和 bash -x发布前把低级错误拦下来在把脚本交给 crontab 之前我每次都会多做两道静态检查。第一个是bash -n它只做语法解析不做执行能立刻发现括号不配对这类低级错误第二个是shellcheck它是静态分析工具专门挑 shell 脚本的常见坑。bash -n /usr/local/sbin/check_server.sh shellcheck /usr/local/sbin/check_server.shshellcheck 的输出里我最常遇到也最值得重视的是 SC2086变量引用没加双引号导致路径含空格时被拆成多个参数、SC2164cd 之后没检查是否成功目录不存在时下面的操作全跑偏、SC2034定义了却没用的变量通常是参数拼写错误。修复建议命令里的变量一律加双引号cd /path || exit 1每一段逻辑转成函数并声明变量作用域。把 shellcheck 接到编辑器的保存钩子里之后脚本质量提升很直接。对 shell 脚本入门阶段的同学还有一点值得注意shellcheck 默认对 POSIX sh 和 bash 的差异非常敏感。如果你脚本开头写的解释器是#!/bin/sh而你又用了[[ ]]这种 bash 特性shellcheck 会直接标错。建议脚本里如果是 bash 特性解释器就明写#!/bin/bash别做那种“sh 也能跑就懒得改”的活。这个细节在分发给不同发行版时尤其重要Ubuntu 的 dash 和 CentOS 的 bash 对同一段脚本解释结果可能完全不同。5.3 只读演练模式先让它“说”要干什么再让它“做”脚本涉及删文件、重启服务这种破坏性动作时直接拿到生产环境试心理压力很大。我的办法是给脚本加一个“只读演练”模式默认只把将要执行的命令打到日志里不真正执行手工确认无误后加一个--apply参数才真正动手。DRY_RUN${DRY_RUN:-1} # 默认 1 为演练模式 if [ $DRY_RUN 1 ]; then echo [DRY-RUN] rm -rf /data/backup/old_$(date \%Y\%m\%d) else rm -rf /data/backup/old_$(date %Y%m%d) fi注意DRY_RUN 模式应该默认开启只有显式指定环境变量才真正执行。千万别把默认值设成 0否则一个写错参数的 crontab 就会把灰度假戏变真戏。这个模式配合灰度上线思路很实用头一星期开着 DRY_RUN 跑日志里记录它“想去做什么”每天观察有没有出格动作一星期后切换成正式执行。切换动作不要改代码只要改环境变量DRY_RUN0回退也就一句话的事。很多团队推广自动化脚本失败不是因为脚本没有价值是因为没有给负责人反悔的机会——DRY_RUN 就是你那粒后悔药。6. 把零散脚本升级成工具箱状态文件与 select 菜单的组合技巧最后一章我分享两个用得最多的进阶技巧。第一个是把告警去重做严状态文件不只是记一句话而是把“故障特征 首次告警时间 次数”记成 keyvalue 的格式这样你收到告警时能判断它抖动多久了也能防止夜晚短时抖动刷屏。第二个技巧是把所有自动化脚本包在一个入口脚本里用 Bash 的 select 命令做一个菜单。日常手动操作时不用记一堆脚本名生产环境里也方便交接#!/bin/bash select action in server_check backup clean_log quit; do case $action in server_check) /usr/local/sbin/check_server.sh ;; backup) /usr/local/sbin/backup.sh ;; clean_log) /usr/local/sbin/clean_old_log.sh ;; quit) break ;; *) echo 无效选项 ;; esac done这两个技巧背后是同一个思路自动化脚本不能只解决“跑一次”要解决“每天跑、多人跑、跑错了能回头”。我自己在这个点上吃过亏——早期我把磁盘清理逻辑直接写在巡检脚本末尾半年后磁盘又满了一次才发现清理逻辑把通配符写错了把另一个目录的文件清掉了。从那以后我规定清理类动作必须独立成脚本、独立命名并且带 DRY_RUN 才允许加入菜单。后来团队每加一个运维动作都先问三个问题能不能重跑跑错了能不能恢复有没有留日志这三个问题过滤下来粗制滥造的脚本少了很多。希望这一套思路对你有帮助祝你在服务器运维这条路上少踩坑多睡几个安稳觉。本文还有配套的精品资源点击获取
返回列表