资讯动态

Linux进程监控与实时告警实战:从零搭建守护体系

发布时间:2026/10/16 10:02:44 来源:尧图企业网站定制
凌晨两点手机在床头柜上震了一下。我迷迷糊糊摸起来一看是一个服务进程挂掉了内存占用异常飙高把后面一连串请求全部拖垮。当时我刚接手一批服务器还没有任何监控手段全靠第二天早上登录机器用top和ps翻历史。那次之后我花了几天把整个告警链路从零搭起来之后夜里手机基本只有误报测试才响。这个事让我意识到所谓“守护服务器”真正落地的第一步不是上什么高端系统而是先把关键进程盯住把异常状态用最快的方式通知到自己。这篇文章我想完整整理一套 Linux 进程监控与实时告警的入门方案先讲清楚要监控什么、为什么这么监控再给一套零依赖的 Bash 脚本实现最后拆解告警渠道、定时调度、常见坑位以及后续往监控平台平滑迁移的路线。适合刚接手服务器、对 Linux 有一定基础、想快速搭起一套“能守夜”的监控体系的人。1. 先搞清楚进程监控到底在看什么动手写脚本之前最应该想明白的并不是“用什么命令”而是你期望监控替你做哪几件事。1.1 进程监控的四个观察维度我刚做监控时犯过一个典型错误只检查进程在不在。后来一个 Java 服务进程一直在但端口已经不监听了线上请求全部超时监控却一声不吭。那次之后我把进程监控拆成了四个维度存活状态进程是否还在运行pid 是否存在systemd 管理的服务是否处于 active 状态。资源占用CPU 使用率、内存 RSS 占用、磁盘 I/O、文件描述符数量有没有越界。行为异常进程是否变成了僵尸状态是否出现短时间内反复崩溃重启子进程数量是否异常膨胀。业务可用性端口是否正常监听、健康检查接口是否返回预期结果。这一层严格说已经超出“进程监控”本身但它是进程监控最自然的延伸。这四个维度不是平行的而是递进关系。进程活着不一定代表服务正常资源没超不代表响应快。对一个“守护”级监控来说至少要把前两层做扎实第三层是性价比很高的加分项第四层建议在进程监控跑通后尽快补上。1.2 阈值怎么定不拍脑袋看基线很多人在配置阈值时有一个误区随便设一个“看起来合理”的数字比如 CPU 超过 80% 就报警。结果不是误报满天飞就是真出事了还没触发。我现在的做法是先跑基线。新机器上线后先让监控脚本只记录不告警跑三天到一个星期把同一时段内的 CPU、内存、进程数的正常波动区间摸出来。比如一个业务进程平时内存稳定在 2.1GB峰值 2.6GB突然某天涨到 4GB这就是明确的信号如果平时就是 1GB 到 4GB 波动那 4GB 根本不该报警。常用的进程监控指标和参考维度我整理成一个表格指标推荐采集方式参考阈值思路存活状态pgrep -f或 systemd 状态进程不存在立即告警CPU 使用率/proc/pid/stat或ps -o %cpu单进程持续超过 100%即一整个核心就要关注多核机器可放宽到 200%内存 RSS/proc/pid/status的 VmRSS或ps -o rss较稳定基线基础上超出 40%-50% 持续 5 分钟以上文件描述符/proc/pid/fd目录计数超过ulimit -n上限的 80% 就要排查泄漏僵尸进程ps -eo stat中带 Z 状态任何数量的僵尸进程都值得调查来源重启次数systemd 的NRestarts或自建计数60 秒内超过 3 次立即告警阈值本身不是死的。我的经验是存活状态必须 0 延迟、立即告警资源类指标要设置“持续 N 分钟”的条件避免瞬时尖峰误报重启类指标则要配合防抖逻辑避免崩溃循环带来的告警风暴。2. 化繁为简先写脚本而不是上平台市面上现成的监控工具很多Prometheus、Zabbix、Grafana功能都很强。但如果你刚接手几台服务器我强烈建议先写脚本把流程跑通再决定要不要上平台。2.1 为什么我推荐脚本起步脚本方案的核心优势是“零依赖、零侵入、完全可控”。不需要额外安装服务不会占用太多系统资源。一个每分钟执行的 Bash 脚本CPU 开销基本可以忽略。逻辑完全透明出了问题你自己能看懂。监控平台的一句话告警“内存使用率超过阈值”排查时你还得自己去翻面板脚本里可以顺手把进程的完整命令行、最近日志片段一起捞出来。后续不管是接企业微信、钉钉、Telegram 还是单纯发邮件改动都在脚本内部非常灵活。我当时对比过两种方案直接部署一套完整的监控平台还是先用脚本。平台方案光是部署、配置、学面板就得花一两天而且它默认的告警规则是针对整个主机而不是具体某个业务进程定制起来反而绕。脚本方案当天晚上就能跑起来。另一个实际原因是脚本在最小范围内验证了“监控什么指标真正有用”比如确认你的进程在不同负载下的正常范围。等到数据积累够了、告警规则打磨好了再平移到平台上规则直接照着脚本里的参数抄过去就行。平台是放大工具不是定义工具。2.2 采集数据依赖的几把“尺子”脚本里的核心命令要先说清楚因为“用什么命令”直接决定了监控能不能抓准。pgrep是最常用的进程查找命令。pgrep -f pattern会匹配完整的命令行比只匹配进程名要准确得多。比如有个程序二进制名是app-server但你可能启动了多个实例命令行里带了不同的-config参数这时按完整命令行匹配才能区分目标。但pgrep -f有个容易踩的坑你自己写的监控脚本名字里如果包含了要匹配的关键字脚本运行时也会被当成目标进程匹配出来。我第一次写监控脚本时脚本名就叫check_app.sh里面用pgrep -f app结果脚本永远认为自己还活着。解决办法有两个一是进程匹配关键词写得更精确比如带唯一参数二是在匹配时排除掉自己最常用的是pgrep -f app-server | grep -v $$或者直接用完整的启动命令作为匹配串。采集 CPU 和内存时ps命令要注意单位。ps -o %cpu显示的百分比是进程相对于单核 CPU 的使用率不是相对于整机所有核心。8 核的机器上一个进程跑满 8 个线程%cpu可能显示 700%但这并不一定是异常。内存方面要关注 RSS 而不是 VSZ。RSS 是进程实际占用的物理内存VSZ 是虚拟内存包含了很多可能永远不会被真正使用的地址空间数值虚高没有任何告警价值。判断 systemd 管理的服务时systemctl is-active service返回active才是正常的。但要注意一个服务处于 active 状态并不代表它的端口在监听所以更可靠的方式是配合端口检查。可以用ss -ltn或者ss -ltnp看指定端口是否有进程监听这一步对“业务可用性”维度尤其重要。2.3 核心命令组合模拟一次“体检”假设我们要监控一个名为app-server的进程它在 8080 端口提供服务。一个完整的监控判断流程是这样的# 1. 先确认进程是否存在 pgrep -f app-server /dev/null if [ $? -ne 0 ]; then echo 进程不存在 fi # 2. 进程存在时采集它的 PID PID$(pgrep -f app-server | head -1) # 3. 采集 CPU 和内存占用 ps -o %cpu,rss -p $PID # 4. 检查端口是否在监听 ss -ltn | grep -q :8080这套命令组合的价值在于分层判断第一步是“还在不在”第四步是“业务通不通”中间两步是“活得好不好”。告警文案里如果能把这四层的结果都带上你半夜收到消息时基本不用再上服务器二次确认。我实际用的监控脚本会把这些结果汇总成一段告警消息比如进程不存在、端口无响应、CPU 飙到 300% 持续了 5 分钟消息里直接写清楚。这里多花十分钟后面能省无数次深夜登录服务器排查的时间。3. 守护的两个层面监控与自愈监控发现问题只是第一步真正能在夜里帮你减少损失的是“自愈机制”——让服务在异常时自动恢复而不是等你去处理。Linux 上实现自愈分两种情况。3.1 systemd 自带的服务守护姿势如果你的服务是通过 systemd 管理的其实自带了一层守护功能很多人没有充分利用它。给某个服务写 systemd unit 文件时最关键的三个参数[Service] Restartalways RestartSec5s StartLimitIntervalSec60s StartLimitBurst3Restartalways进程退出后立即重新拉起。RestartSec5s重启前等待 5 秒防止快速失败时把 CPU 打满。StartLimitIntervalSec和StartLimitBurst限制在 60 秒内最多重启 3 次超过这个次数就放弃系统不会被一个“反复崩溃又反复拉起”的进程拖垮。这个配置的本质是能自动恢复的服务让它自己恢复但如果它进入“死循环崩溃”模式要继续保留状态让监控发出明确告警而不是让它像电脑抽风一样无限重启。有一个细节需要注意Restartalways并不等于“进程一直在跑”。如果你没有设置上面两个启动限制一个入参校验失败就退出的进程会在 60 秒内被拉起几十次整台机器都跟着遭殃。我见过一台机器因为这种配置问题系统负载飙升到 20 多最后还是重启才解决。3.2 非 systemd 进程的看守逻辑业务里还有一类进程不在 systemd 管理之下比如直接在命令行里启动的、通过 nohup 后台跑的、或者打包在第三方脚本里启动的。这时候自愈就得自己写看守逻辑。看守逻辑最常见的问题是“误判进程不存在”然后重复拉起导致多份实例同时运行。要避免这个问题第一用 PID 文件记录进程 ID第二拉起前检查进程是否真的存在第三拉起操作本身要加锁防止两个监控循环同时执行。一个简单的带锁拉起逻辑# 用 flock 保证同一时间只有一个看守进程在操作 exec 9/var/lock/app-monitor.lock flock -n 9 || exit 0 # 检查 PID 文件对应的进程是否还活着 if [ -f /var/run/app-server.pid ]; then OLD_PID$(cat /var/run/app-server.pid) if kill -0 $OLD_PID 2/dev/null; then exit 0 # 进程正常不需要拉起 fi fi # 进程不存在重新启动并记录新 PID nohup /opt/app/bin/app-server /var/log/app-server.log 21 echo $! /var/run/app-server.pid这套自愈逻辑的核心是“先验证、再操作”。还要注意自愈动作本身必须记录日志否则第二天你发现服务时间线对不上根本不知道它夜里自己重启了几次。自愈机制和监控脚本的分工要明确监控脚本负责观察和上报自愈动作如果和监控放在同一个脚本里要保证在进程反复崩溃超过一定次数之后停止自动拉起把问题暴露出来交给告警而不是无限隐性地“自我修复”。4. 告警链路搭建与防打扰设计监控是眼睛告警是嘴巴。嘴巴如果一直乱叫或者该叫的时候叫不出来前面的监控做得再好也白搭。4.1 告警渠道选择与消息内容设计告警渠道的选择要看你的使用场景。我的经验是这样的渠道适用场景优点缺点邮件正式记录、后续审计可留痕、可归档可能被垃圾邮件过滤不适合紧急告警企业微信/钉钉群机器人团队协作最常用实时性好手机必达支持 markdown依赖外网 API内网机器需要额外配置出口代理Telegram Bot个人服务器、海外服务器免配置、支持群组国内网络环境不友好短信/电话核心业务故障必达性最强成本高、需要专业服务对多数人来说最现实的组合是内网服务器用邮件做归档同时给企业微信或钉钉的群机器人发 webhook实现手机实时通知。如果你不想接外部 API本地直接用mailx发邮件是最简单的方案。设计 webhook 的时候告警消息内容要比“某进程异常”更有价值。我实际用的消息模板长这样[生产环境] 服务异常告警 主机名prod-01 时间2025-01-18 03:22:10 监控项app-server 进程 状态进程不存在 当前值未发现匹配进程 持续时间约 3 分钟 最近日志/var/log/app-server.log 最后 20 行见附件为什么要把主机名、时间、当前值、最近日志都塞进去因为半夜收到告警的你第一反应肯定不是登录服务器而是先判断“要不要现在爬起来处理”。如果消息里已经写清楚“进程不存在且最近日志里有 OutOfMemoryError”你基本能判断是内存问题可以先去睡白天再处理如果写的是“进程反复崩溃已被自动拉起”你甚至可以直接忽略。curl 发企业微信机器人 webhook 的例子curl -s -X POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key你的机器人key \ -H Content-Type: application/json \ -d { msgtype: markdown, markdown: { content: ## 服务异常\n**主机**prod-01\n**服务**app-server\n**状态**进程不存在 } }注意 webhook 的 URL 里不要直接写永久密钥要放在独立的配置文件里脚本从文件里读取。否则脚本一旦被同事看到或者传到代码仓库里任何人都能往你的群里发垃圾消息。4.2 告警去重、恢复通知与静默窗口告警领域有一个很实际的问题重复告警。进程挂了 10 分钟监控每分钟执行一次如果你的告警逻辑是“发现异常就发消息”10 分钟你就得收 10 条消息。我见过有人一夜收了两百多条同样的告警最后把手机静音继续睡等真正需要处理时人已经麻了。去重机制不复杂用状态文件记录上一次的告警状态。只有状态发生变化时才发消息。比如上次状态是“正常”这次变成“异常”发一条“进程挂掉”如果上次已经是“异常”这次还是异常说明还未恢复不再重复发只更新状态文件里的持续时间。下面是一个状态文件去重的简化示例STATE_FILE/var/run/app-monitor.state # 读取上一次状态 PREV_STATE [ -f $STATE_FILE ] PREV_STATE$(cat $STATE_FILE) # 本次状态 CURRENT_STATEALERT if [ $PREV_STATE ! $CURRENT_STATE ]; then # 状态发生了变化才发送告警 echo $CURRENT_STATE $STATE_FILE send_alert app-server 进程异常 fi恢复通知也要做。进程恢复正常后发一条“服务已恢复”的消息这样夜里被吵醒的人知道事情已经解决可以安心睡。恢复通知的去重和异常去重是同一套逻辑状态从“异常”变回“正常”时发。静默窗口和告警升级是更进阶的设计。所谓静默窗口就是在某些已知的维护时段比如凌晨做备份的时段暂时关闭资源类告警避免误报告警升级是指进程挂掉后如果 5 分钟内没有恢复给更高优先级的渠道比如电话再发一条提醒一线人员翻车了。脚本里用一个时间戳文件记录首次异常时间超过预设时长就触发升级逻辑。这些都是典型的生产环境实战经验建议从第一天就设计进去避免后续频繁改动。5. 定时调度、日志审计与开机自启监控脚本本身需要被周期性地执行还要保证自身在系统重启后能重新跑起来并且运行过程有记录。这一节把脚本工程化。5.1 定时调度cron 还是 systemd timer手动跑一次监控脚本没有任何意义你需要让它在后台定期执行。最常用的两个工具是 cron 和 systemd timer。cron 简单直接适合“系统已经配好 cron 服务、不用多做一轮适配”的环境。一行配置即可* * * * * /opt/monitor/app_monitor.sh /dev/null 21但 cron 有一个短板它的执行记录默认要看系统日志如果脚本执行失败、超时甚至整个 cron 服务挂了cron 本身不会给你任何通知。对“守护”工作来说监控器本身失聪是很危险的。我推荐有条件的环境用 systemd timer。它自带执行日志管理、失败状态查询、执行时间统计而且和 systemd 的集成度更高。一个监控任务的 timer 文件如下[Unit] DescriptionRun app monitor every minute [Timer] OnCalendar*:0/1 AccuracySec1s Persistenttrue [Install] WantedBytimers.target对应的 service 文件[Unit] DescriptionApp Monitor Service [Service] Typeoneshot ExecStart/opt/monitor/app_monitor.sh启用方式systemctl daemon-reload systemctl enable app-monitor.timer systemctl start app-monitor.timer和 cron 相比systemd timer 带来的最大好处是“可观测”systemctl status app-monitor.timer能看到上次执行时间、下次执行时间journalctl -u app-monitor.service能看每次执行的完整输出。排查问题的时候这些信息比起翻 cron 的日志舒服太多了。5.2 日志留痕与告警审计监控脚本自身也要写日志。最理想的状态是把每次检查的动作、结果、异常状态都完整记下来方便事后回溯。一个简单的日志函数LOG_FILE/var/log/app-monitor.log log() { echo $(date %Y-%m-%d %H:%M:%S) $* $LOG_FILE } log 检查开始 # ... 执行监控逻辑 log 检查结束状态正常日志里一定要记录几个固定字段时间、主机名、监控项、状态、当前值、告警结果。这样后续用grep也能快速检索。这些日志文件要留意大小避免服务器跑几个月后磁盘被日志撑爆。解决办法是配置 logrotate。Linux 发行版默认都装了 logrotate只需在/etc/logrotate.d/下放一个配置/var/log/app-monitor.log { weekly rotate 4 compress missingok notifempty }这个配置的意思是每周轮转一次保留最近 4 份轮转后的旧日志压缩存放。运维里 90% 的日志相关问题都是因为没有考虑“日志文件会无限增长”这件事。开机自启方面如果你用 systemd timersystemctl enable就已经处理了开机自启。如果用的 cron要确认 cron 服务本身是 enabled 状态同时建议把监控脚本放在/opt/monitor/这种固定目录不要放在会被清理的临时目录里。6. 常见坑位与排查速查搭监控的坑往往不是搭不起来而是搭起来之后开始出现各种误报和漏报。我把我踩过的主要坑位整理成了速查表并按排查优先级排序。6.1 五个高频问题复盘现象可能原因排查方法解决思路监控脚本判断“进程不存在”但服务其实在跑pgrep -f匹配词太宽泛匹配到了脚本自身、被排除或权限不足在脚本里加上 ps -efgrep 手动验证告警风暴同一告警短时间内刷屏状态去重逻辑缺失每次检查都触发发送查看告警发送时间间隔是否都是调度周期引入状态文件去重、静默窗口进程在但端口不通服务实际不可用只做了进程存活检查没做端口/健康检查ss -ltn查看监听状态curl -f访问健康检查接口在监控维度中增加端口和 HTTP 健康检查僵尸进程杀不死反复告警僵尸进程已经死掉但父进程没回收它kill 无效ps -eo stat,pid,ppid找到僵尸进程检查其父进程处理父进程或重启父进程而不是尝试杀僵尸进程监控脚本自身占用过高 CPU循环检查多个进程时脚本写得低效或采集命令太过频繁top看脚本进程资源占用合并采集命令拉长检查周期避免脚本内嵌大循环第一个坑值得多说一句。pgrep -f配合head -1取第一个 PID在多实例部署时可能抓到的是错误实例。如果你的机器上既有app-server-1又有app-server-2匹配词写得太模糊pgrep结果可能不是你想要的。这种情况下更推荐用进程启动时自带的唯一命令行参数来定位比如pgrep -f app-server --config/etc/app/prod.yaml。宁可匹配条件苛刻一点也不能让它抓到别的进程。第二个坑是告警风暴绝大多数刚搭监控的人都会碰到。我把它理解为监控系统的“误报”比“漏报”更消耗人因为误报多了以后真正告警来了你反而忽略了。所以去重和静默窗口不是可选项是必选项。第四个坑和“看门狗思维”有关。你以为“杀完僵尸进程就好了”实际上僵尸进程是父进程没有调用wait回收子进程导致的它本身已经不消耗 CPU 和内存但会占着 PID 号。长期积累的僵尸进程会把 PID 号耗尽新进程无法启动。监控脚本看到有僵尸最优做法是通知你去看它的父进程而不是在脚本里自动杀掉很可能是误杀。6.2 独门排查心法分享一条常用排查路径从告警消息反推问题源头可以节省大量时间。收到一条“CPU 使用率超过 300%”的告警不要急着跑top先看告警消息里的最近日志和进程启动时间。如果最近日志里出现线程 dump 或者 OutOfMemoryError先猜内存泄漏如果日志一切正常再用top -Hp pid看进程内线程占用确认是哪个线程烧的 CPU如果是配置文件被改、流量突增导致的正常波动看入口流量和最近部署记录通常一翻就明白了。这条心法的核心是用监控采集的数据缩小范围而不是拿着top漫无目的地按 CPU 排序。一次省下十几分钟高峰期可能就是避免故障扩大的关键。7. 从脚本到平台可视化的下一步脚本方案解决了“有没有”的问题但它毕竟只是单机视角。当服务器数量多起来或者你需要回看历史趋势、对比多个节点的同一指标脚本的边界就出来了。这时候可以考虑上一个轻量级监控平台。7.1 什么信号说明该上平台了如果出现下面几个迹象说明脚本方案的维护成本开始追平平台方案的部署成本服务器数量超过 5 台相同的脚本要在每台机器上部署一份版本更新要一台台手动同步。需要把历史曲线拉出来看一个星期前的 CPU 到底是多少脚本做不到它的历史只有日志里零散的记录。告警规则复杂了要根据标签分环境、按业务分组、按不同阈值配置不同的接收人。这些在脚本里靠分支逻辑实现每增加一条规则就要改一次脚本越来越难维护。需要可视化展示老板或者同事想看“服务的实时负载、资源使用率”脚本没有仪表盘还得临时手动登录机器。出现两个以上迹象就可以把平台迁移提上日程了。7.2 成熟路线的选型参考一个轻量但够用的常用组合是Node Exporter 或进程级 Exporter 负责采集主机和进程指标Prometheus 负责存储和计算告警规则Alertmanager 负责告警路由和去重Grafana 负责画仪表盘。整个过程跑下来告警规则可以直接从脚本里抄监控指标比脚本还要多一个维度持续的历史趋势。迁移平台有几个坑要提前注意端口冲突、内存占用、数据保留时间。Prometheus 默认只保留 15 天的数据如果要求保留一个月需要配置retention参数。对资源紧张的小服务器来说Prometheus 的内存占用不可忽视如果只监控一台机器可以先用轻量方案比如直接用 Uptime Kuma 这类自带界面和告警的开源工具连 Prometheus 都不用装。无论选哪条路线脚本方案里的设计经验——指标维度、阈值基线、去重逻辑、告警消息格式——都可以原封不动地带过去。平台只是把“采集、存储、展示、通知”四件事做得更自动化你对业务进程的理解才是监控系统真正的核心资产。最后说一点体会。监控和告警这类系统价值不在于配置了多少个监控项、发了多少条告警而在于它稳定运行的时间里你有多少次没被它打扰。那些收不到告警的安静夜晚才是它真正在好好工作。如果你第一次搭挑一个最重要的业务进程先把脚本跑起来把日志写起来把告警调通观察一周再说。一个守不住的夜班比任何花哨的方案都更能教会你判断阈值、过滤噪音、区分真假异常。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑