
我见过最刺激的生产事故其实不是人干的而是一个 AI 智能体在终端里“自由发挥”。你跟它说“看看这台服务器最近怎么这么卡”它看完负载、内存、缓存之后转头就给你来一句“我准备重启一下服务释放内存”。那一刻你才会真正意识到它拿到的是 shell 权限不是建议权限。Codex 是 OpenAI 出的命令行编程智能体干这类活非常顺手读日志、看指标、写脚本、改代码甚至直接在 shell 里执行命令。它能帮你把运维效率拉高一个量级但如果不划清边界“效率”和“事故”之间往往就隔一个回车键。这次我做的实验很明确用 Codex 生成一份服务器运维巡检脚本不暴力运维不直接重启服务器所有动作只读、可留痕、可审计人只需要做两件事审代码、按计划执行。这篇文章不是教你“怎么把 Codex 当 root 用”而是讲怎么把 AI 放到一个既能干活、又不容易闯祸的位置。我会从需求拆解、提示词设计、脚本实现、运行审计、常见踩坑五个部分串起来工具是 Codex CLI目标是 Linux 服务器。无论你是想引入 AI 做运维的工程师还是刚接触巡检脚本的新人这套思路应该都能直接用。1. 别让 AI 直接操作生产服务器先解决“权限失控”问题1.1 传统终端操作本来就高风险AI 让风险变成了概率问题人手动操作服务器出事故通常是因为疲劳、手滑、误判。比如凌晨三点接到告警脑子里想的是看内存手上却敲错了命令这类故事每个运维都能讲出几个。但人工操作有一个隐藏优点人知道自己不知道什么遇到拿不准的命令会停下来会犹豫会去看文档甚至会上网查一圈再动手。AI Agent 不一样。它给出下一条命令的过程本质上是一个基于历史数据生成的预测而不是“经过完整推演的确定性结论”。它看到负载高会自然地推断“清理缓存、重启某个服务”因为在它训练的语料里这种操作很常见。问题是生产环境最讨厌的就是“常见的操作”你永远不会知道哪一次常见的重启会带走一个没保存的连接或者让某个依赖服务起不来。更要命的是终端里一旦允许 AI 自动执行命令它不会像人那样有“生理性紧张”。它不会因为这台机器上有三百个在线用户就犹豫也不会因为这条命令从未在这个环境里验证过而手抖。它只会按照对话上下文里的意图一步步把“应该做的事”做出来直到某个命令产生不可逆影响。所以我的结论很直接不要让 AI 直接操作生产服务器。不是说 AI 不行而是说目前阶段任何模型都无法保证自己在生产环境里的每一步判断都百分之百正确。正确的做法是给 AI 一个“安全的输出形式”也就是代码。1.2 Codex 的正确用法让 AI 生产“方案”人负责执行方案Codex CLI 有两个很常见的使用场景第一时间想让它直接跑第二时间想让它生成可交付的代码。我强烈建议运维类任务默认选第二个。把 AI 的产出限定成脚本本质上是把“执行权”和“影响力”从 AI 手里重新收回人类手里。脚本是可阅读的是可以做 code review 的是可以先在测试环境跑一遍再上生产的也是可以纳入 Git 做版本管理的。服务器真正执行的是一份经过审批和验证的“提案”而不是 AI 临场发挥的随意命令。这就像你不会让一个实习生直接在生产环境敲命令但你会让实习生先把操作步骤写成文档由你审核后再执行。Codex 在运维里的定位就应该是这个实习生它负责把巡检思路、检查命令、告警规则整理成可执行脚本人来确认每一行有没有问题确认完之后再让脚本去跑。我在这次实战里给 Codex 立的规矩就一句话你可以读任何文件、看任何指标、写任何脚本但所有有副作用的操作必须经过我确认脚本默认只读禁止重启服务禁止修改系统配置。结果就是它产出的巡检脚本不仅能用还可以留下来作为长期运维资产反复跑、反复审查。2. 巡检清单先行到底该让脚本检查哪些东西2.1 单台 Linux 服务器的五层巡检模型很多人写巡检脚本习惯“想到哪写到哪”今天加一个 CPU明天补一个磁盘最后脚本几百行逻辑混乱还不知道哪些检查才是关键的。我的做法是先分层把巡检对象拆成五个维度再逐层确认。分层检查项数据源/命令重点关注资源层CPU 负载、内存、磁盘、inode/proc/loadavg、/proc/meminfo、df、free负载是否超过核数内存是否耗尽磁盘/inode 是否接近满系统层运行时间、内核版本、时间同步uptime、uname、timedatectl内核是否过旧NTP 是否同步系统是否长时间未重启服务层关键 systemd 服务状态、进程数量systemctl、pgrep业务进程是否存活依赖服务是否正常网络层监听端口、TCP 连接数、丢包错包ss、netstat、ip -s端口是否按预期监听连接是否异常积压安全与备份证书到期时间、登录失败记录、备份文件时间戳openssl、lastb、stat证书是否即将过期是否有异常登录备份是否还在更新之所以把“证书到期”和“备份文件时间戳”也放进巡检脚本是因为这两类问题平时不冒烟一旦出事就是大事。证书过期会导致线上接口突然全部报错备份失效意味着数据恢复根本无从谈起。巡检脚本如果只盯 CPU 和内存那它和裸奔区别不大。2.2 脚本设计三约束只读、幂等、可审计确定检查项之后还要给脚本定三条硬约束。第一只读优先。巡检脚本的本职是观察和记录不是修东西。脚本执行过程中不应该修改系统配置、不应该删除文件、不应该对服务做任何启停操作。唯一允许的写入是往自己的日志目录写报告。把脚本设计成只读等于给所有潜在风险加了一道天然防火墙。第二幂等可重复。同一份脚本今天跑和明天跑除了时间戳和具体数值整体流程必须完全一致。不能因为上次执行留下某个状态文件就改变行为也不能依赖交互式输入。这样才能保证定时任务可以反复执行也能保证出问题时随时手动补跑一次。第三输出可审计。脚本必须把检查结果分成“正常、警告、异常”几档并且把每次运行的时间、主机、各项指标、结论完整记录下来。审计不是只给一个人看还要能在出问题时让其他同事也能通过日志还原当时的现场。3. 用 Codex 生成巡检脚本提示词设计是关键3.1 给 Codex 的提示词模板Codex 生成代码的质量很大程度上取决于你给它输入的约束条件。尤其是运维脚本提示词里如果不明确“只读、不许重启”它很容易按自己的理解补上一堆危险操作。我这次使用的提示词大致如下你可以直接复制改一改再用你现在是一位有十年经验的 SRE请帮我在一台 Linux 服务器上编写一份运维巡检脚本。 要求 1. 只读检查不得修改任何系统配置不得重启服务不得删除文件 2. 检查范围包括CPU 负载、内存使用率、磁盘和 inode 使用率、关键 systemd 服务状态、监听端口、最近 1 小时错误日志、SSL 证书到期时间、NTP 同步状态 3. 输出到控制台的同时把完整结果写入 /var/log/server_inspect/ 目录下带时间戳的日志文件 4. 每类检查结果必须区分“正常 / 警告 / 异常”三档存在异常时最终退出码不为 0 5. 脚本使用 bash 实现尽量兼容主流 Linux 发行版 6. 只生成脚本内容不用真正执行命令。在 Codex CLI 里可以直接这样跑codex exec 你现在是一位有十年经验的 SRE请帮我编写一份运维巡检脚本……也可以先进入交互式对话再把提示词粘贴进去。我比较推荐交互式的方式因为你可以在生成初稿后追加问题比如“为什么这里用 grep 而不是 awk”“这个端口匹配会不会误伤”让 Codex 现场解释代码逻辑。3.2 人工审查 Codex 输出的四个重点Codex 生成的脚本不一定完美它甚至可能在脚本里加入你没要求的操作。所以拿到初稿后第一件事不是执行而是审查。审查维度具体检查我这次的实际观察高危命令搜索 restart、reboot、shutdown、rm -rf、mkfs、kill 等关键词初版脚本里就出现过重启 sshd 的操作必须删掉写操作搜索 /etc、 /dev/sd、tee /etc、chmod、chown正确版本只允许往日志目录写文件交互风险是否包含 ssh 远程执行、是否可能 hang 住等待输入初版有 ssh 检查被我改成纯本地检查变量与引用未初始化变量、反引号嵌套、缺少双引号个别路径没加引号遇到空格目录会出错我那次让 Codex 初版生成后它在脚本尾部顺手加了一句类似systemctl restart sshd的操作理由是“为了确保配置生效”。可问题是我根本没有让它改任何配置它自己脑补了一个重启。这段要是没人审查直接跑影响面就完全失控了。所以请记住Codex 输出的脚本是“草稿”不是“成品”。AI 写代码再快人工审查这一步也绝对不能省。4. 巡检脚本核心实现与审计设计4.1 脚本骨架头部、日志函数、结果汇总在跟 Codex 来回迭代了好几轮之后我保留了一份可以直接上生产用的脚本基准版。这里把核心结构拆开讲方便你理解为什么每一段要这么写。脚本头部和公共函数是整个巡检脚本的地基#!/usr/bin/env bash # # server_inspect.sh # 用途服务器日常巡检只读检查输出审计日志 # 用法bash server_inspect.sh # set -uo pipefail REPORT_DIR${REPORT_DIR:-/var/log/server_inspect} STAMP$(date %Y%m%d_%H%M%S) REPORT_FILE${REPORT_DIR}/inspect_${STAMP}.log mkdir -p $REPORT_DIR FAILED0 say() { printf \033[1;32m[OK]\033[0m %s\n $* | tee -a $REPORT_FILE; } warn() { printf \033[1;33m[!!]\033[0m %s\n $* | tee -a $REPORT_FILE; } fail() { printf \033[1;31m[XX]\033[0m %s\n $* | tee -a $REPORT_FILE; FAILED1; }第一行 shebang 必须用#!/usr/bin/env bash而不是#!/bin/sh因为脚本里用了 bash 的进程替换和数组特性sh 跑起来会报语法错误。set -uo pipefail也是有讲究的。我没有用set -e因为巡检脚本里很多检查命令本来就可能返回非零状态比如“grep 没匹配到”“journalctl 没日志”如果开了set -e脚本会在第一次“没查出问题”时直接退出后面的检查全部被跳过。-u是为了抓住未定义变量pipefail是为了防止管道命令的失败被掩盖。say和fail里用了tee -a这样每一条输出既出现在屏幕上又写入日志文件。最终通过FAILED变量汇总所有异常决定退出码。4.2 各检查模块是怎么实现的资源层的检查是最基础也最关键的。CPU 负载不能只看数值要结合核数算百分比LOAD1$(awk {print $1} /proc/loadavg) CORES$(nproc 2/dev/null || echo 1) LOAD_PCT$(awk -v a$LOAD1 -v c$CORES BEGIN{printf %d, a/c*100}) if (( LOAD_PCT 80 )); then fail CPU 1分钟负载偏高: $LOAD1 / $CORES 核 else say CPU 1分钟负载: $LOAD1 / $CORES 核 fi这里用 1 分钟负载而不是 5 或 15 分钟是因为巡检脚本通常按小时或半小时跑一次1 分钟负载更能反映当前是否已经有异常趋势。内存检查我推荐直接读取/proc/meminfo里的 MemAvailable 字段它比 free 命令里的“可用内存”更贴近真实可用量read -r MEM_TOTAL_KB MEM_AVAIL_KB (awk /MemTotal/{t$2}/MemAvailable/{a$2}END{print t, a} /proc/meminfo) MEM_USED_PCT$(( (MEM_TOTAL_KB - MEM_AVAIL_KB) * 100 / MEM_TOTAL_KB )) if (( MEM_USED_PCT 90 )); then fail 内存使用率 ${MEM_USED_PCT}% else say 内存使用率 ${MEM_USED_PCT}% fi磁盘检查要同时看容量和 inode两个/根分区都是必查项while read -r fs used avail; do if (( ${used%\%} 85 )); then fail 磁盘 $fs 使用率 ${used} else say 磁盘 $fs 使用率 ${used} 剩余 ${avail} fi done (df -hP / | awk NR2{print $6, $5, $4}) INODE_PCT$(df -iP / | awk NR2{print $5}) if (( ${INODE_PCT%\%} 90 )); then fail inode 使用率 $INODE_PCT else say inode 使用率 $INODE_PCT fi很多新手只看磁盘容量不看 inode。inode 满了以后即使磁盘还有空间也无法创建任何新文件这种故障一旦发生会非常难排查。服务状态检查用 systemctl 是最标准的做法关键服务可以根据业务改for svc in ssh cron rsyslog; do if systemctl is-active --quiet $svc 2/dev/null; then say 服务 ${svc} 运行中 else fail 服务 ${svc} 未运行 fi done网络层检查我不建议一股脑把所有端口全打出来那样日志会很乱。只检查你关心的几个端口即可for port in 22 80 443; do if ss -tln 2/dev/null | awk {print $4} | grep -q :${port}$; then say 端口 ${port} 正在监听 else warn 端口 ${port} 未监听 fi done注意 grep 里的正则一定要写成:${port}$否则会误匹配像 :2222、:8080 这类包含相同前缀的端口。证书到期检查也是运维巡检里的刚需CERT_FILES$(find /etc/ssl/certs /etc/pki/tls/certs -maxdepth 2 -name *.pem -o -name *.crt 2/dev/null | head -5) for cert in $CERT_FILES; do end$(openssl x509 -enddate -noout -in $cert 2/dev/null | cut -d -f2) [[ -z $end ]] continue days$(( ( $(date -d $end %s) - $(date %s) ) / 86400 )) if (( days 30 )); then warn 证书 ${cert} 剩余 $days 天到期 else say 证书 ${cert} 剩余 $days 天 fi done日志错误检查使用 journalctl 即可最近 1 小时有 error 级别日志就提示if command -v journalctl /dev/null 21; then err_count$(journalctl --since 1 hour ago -p err --no-pager 2/dev/null | wc -l) if (( err_count 0 )); then fail 最近 1 小时错误日志 ${err_count} 条 else say 最近 1 小时无错误日志 fi fi脚本结尾统一汇总echo echo 巡检报告: $REPORT_FILE exit $FAILED如果FAILED是 1脚本退出码为 1。定时任务和监控系统可以基于退出码判断是否需要告警。4.3 日志文件为什么要按时间戳命名审计的核心其实是日志的可追溯性。我让 Codex 把报告文件命名为inspect_YYYYmmdd_HHMMSS.log每跑一次就生成一份独立的巡检快照。这样做有三个显而易见的好处。第一出问题时可以直接翻到故障时间点前后那一份日志看当时的系统状态是什么样不需要从一堆混杂输出里大海捞针。第二不同时间点的日志可以做对比比如内存使用率从上周的 60% 涨到这周的 85%趋势立刻就能看出来。第三日志不会互相覆盖历史记录天然保留这对合规审计也很有价值。想要快速找到历次巡检里的异常项可以用一条命令grep \[XX\] /var/log/server_inspect/inspect_*.log这个设计的核心思路就是巡检日志不是给人看的流水账它是可以被检索、被对比、被追溯的审计轨迹。5. 让脚本稳定跑进定时任务执行、排错与闭环5.1 首次上线前的三个验证步骤脚本写出来只是完成了三分之一验证才是重头戏。第一步先在测试环境跑一遍。我自己的习惯是先在本地虚拟机或容器里执行用bash -n server_inspect.sh做语法检查再实际运行一遍确认输出格式和退出码符合预期。测试环境跑通了再谈生产环境。第二步用普通用户执行一次。巡检脚本不一定要 root 才能跑很多信息/proc下普通用户也能读。但 journalctl 和 systemctl 的部分操作在非 root 环境下可能拿不到完整数据。我在普通用户下跑了一次发现 journalctl 直接报没有权限于是把用户加到了 systemd-journal 组同时确认 systemctl is-active 即使非 root 也能正常返回结果。第三步连续跑三到五次检查输出是否有随机波动。如果发现某次运行多了一条 “磁盘使用率警告”另一次又消失了多半是脚本里有竞态条件或者依赖了上次运行留下的临时文件。巡检脚本应该是确定的同样的状态应该得到同样的结论。5.2 巡检脚本常见问题速查表这一节是基于我这几次踩坑经历整理出来的问题清单强烈建议你贴到自己的运维笔记里。问题现象根本原因解决方案脚本执行到一半就退出使用了 set -e某个检查命令返回非零改用 set -uo pipefail不要全局开 -ejournalctl 报没有权限非 root 用户不在 systemd-journal 组将用户加入 systemd-journal 组或使用 sudo 执行df 命令长时间卡住服务器挂了 NFS网络文件系统无响应对远程挂载点单独处理给 df 添加 timeout 限制inode 使用率显示异常只看了磁盘容量没看文件系统 inode 数在检查中同时加入 df -iP 的 inode 输出端口匹配出问题grep 正则没有加结尾符误匹配 2222使用 :22$ 这类精确匹配或改用 ss 的 state 过滤日志颜色串全是转义符tee 直接把 ANSI 色码写入了日志文件不做处理也可以或用 sed 去掉控制字符再落盘cron 里跑不正常但手动正常cron 的 PATH 环境与终端不同脚本开头 export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin自动执行时重复跑多份上一次脚本还没结束下一次任务又开始了使用 flock 文件锁防止脚本并发执行磁盘满了导致日志写不进去日志目录没有做轮转和清理对 /var/log/server_inspect 配置 logrotate 或定期清理策略AI 生成脚本里多出意外操作模型脑补了人类没有要求的步骤上线前务必 grep 高危关键词逐行做 code review第 10 条其实是最容易忽略的我单独提一下。Codex 生成脚本再快它也只是按照概率补全上下文它不知道这台服务器上跑的是什么业务也不知道你对配置变更的容忍度。任何由 AI 生成的运维脚本第一版都必须过一次人工审查重点就是查那些你并没有要求它做但它自己“顺手”加进去的操作。5.3 配置定时任务时的细节巡检脚本要发挥价值必须周期性地跑。我的习惯是每天至少跑一次默认放在凌晨或业务低峰期。cron 配置可以写成这样30 3 * * * /usr/local/bin/server_inspect.sh /dev/null 21这条表示每天凌晨 3 点 30 分执行一次巡检并把标准输出和错误输出都丢弃因为脚本内部已经用 tee 把日志写入了报告文件。如果你希望定时任务失败时能被监控系统感知可以在脚本外层再包一层检测30 3 * * * /usr/local/bin/server_inspect.sh || echo 巡检执行失败另外如果担心脚本还没跑完下一次任务又启动可以用 flock 做执行锁30 3 * * * /usr/bin/flock -n /tmp/server_inspect.lock /usr/local/bin/server_inspect.sh这样即使上次执行卡住或超时也不会出现两个巡检进程同时跑、互相干扰日志的情况。脚本执行完成后还可以考虑把异常结果转发到内部告警群。最简单的方式是在脚本末尾加一步判断如果FAILED非零就调用一个通知脚本发送摘要。不过这一步我建议单独做不要在巡检脚本里堆太多跟系统检查无关的业务逻辑。6. 后续扩展与我的真实体会Codex 做好巡检脚本只是整个运维自动化的第一小步。脚本跑稳之后可以继续让 Codex 基于同一套巡检结果生成趋势报表或者把日志里的关键指标解析成 JSON上报给监控平台做可视化和阈值告警。甚至下一步可以让 Codex 根据巡检发现自动生成修复建议草案再由人工确认执行。每次扩展的边界都一样AI 负责分析和产出人负责拍板和执行。我个人的实操体会是把 Codex 从一个“直接执行者”降级成“方案生成器”效率优势一点没少但安全感至少翻了几倍。巡检脚本里那些重复枯燥的检查逻辑人写要半小时它几十秒就能出一版而我在旁边要做的只是花几分钟做一次认真的审查。最后一次补充一个实用小技巧我每次让 Codex 改脚本都会要求它同时更新脚本头部的注释块把变更原因写清楚。这样两周之后再看这份脚本每一块功能为什么存在、什么时间加的、解决了什么问题全都一目了然。审计的核心不是“留了多少日志”而是“后来的人是否还能读懂”。新服务器到手我现在第一句话都是先给我写一份可审计的巡检脚本跑通、留痕、再逐步加检查项。别让 AI 直接重启服务器这是我用 Codex 做运维这段时间最想提醒自己的一件事。