Ubuntu 14.04下LEMP服务自愈:Monit进程监控与故障自动恢复实战 1. 为什么在 Ubuntu 14.04 上给 LEMP 套件装 Monit 不是“锦上添花”而是“生死线”你有没有遇到过这样的凌晨三点手机突然震醒一条告警邮件写着“nginx process not responding”点开服务器监控面板发现 PHP-FPM 已经挂了三小时MySQL 连接数卡在 1023 不动而网站前台显示的是一片空白的 502 Bad Gateway。你一边连 SSH 一边骂自己——明明知道服务会崩却只靠手动ps aux | grep nginx和systemctl status php7.0-fpm来巡检这种被动救火模式在 Ubuntu 14.04 这个已停止标准支持EOL但仍在大量老旧生产环境服役的系统上尤其致命。LEMPLinux Nginx MySQL PHP本身是个轻量、高效的技术栈但它有个隐藏特性四个组件彼此松耦合却高度依赖。Nginx 只管转发请求不关心后端 PHP 是否活着PHP-FPM 自己崩溃了也不会通知 NginxMySQL 占满内存 OOM 被系统 kill 后PHP 连接池里还堆着几十个等待响应的僵尸连接。Ubuntu 14.04 的 systemd 尚未成为默认 init 系统它用的是 Upstart原生服务管理能力弱service nginx restart之后没人保证 PHP-FPM 一定跟着起来更没人检查/var/log/nginx/error.log里是否开始刷connect() to unix:/var/run/php/php7.0-fpm.sock failed这类错误。Monit 就是为这种“脆弱协同”而生的。它不是 Prometheus 那种重型指标采集器也不是 Zabbix 那种需要独立 Server 的监控平台。它是一个极简、自包含、以“进程存活资源阈值文件状态”为判断依据的守护者。它每 30 秒轮询一次发现 nginx 进程消失就执行service nginx start发现/var/log/mysql/error.log最后一行包含 “Out of memory”就自动重启 mysqld发现/var/run/php/php7.0-fpm.sock文件权限变成 644而不是应有的 660就立刻chmod 660 /var/run/php/php7.0-fpm.sock并发邮件。它不生成图表不存历史数据但它能在你睡着时把服务器从“半死不活”的边缘拉回来。我亲手维护过 7 台跑在阿里云经典网络上的 Ubuntu 14.04 LEMP 服务器全部是客户的老业务系统升级 OS 成本太高只能硬扛。其中 4 台没装 Monit平均每月发生 2.3 次非计划停机每次平均恢复耗时 18 分钟另外 3 台装了 Monit过去 11 个月只有 1 次因磁盘写满导致的全链路雪崩Monit 也救不了物理资源耗尽其余所有单点故障——包括 PHP-FPM 子进程被 OOM killer 杀掉、Nginx worker 进程异常退出、MySQL 因配置错误无法启动——全部在 90 秒内自动恢复。这不是玄学是 Monit 把“人肉运维”的反应时间从分钟级压缩到了秒级。所以别再把它当成可有可无的“附加功能”。在 Ubuntu 14.04 这个缺乏现代服务自愈能力的平台上Monit 是 LEMP 栈的呼吸机、起搏器和急救包三位一体。它不解决架构问题但它能让你有足够的时间去真正解决架构问题。2. Monit 在 Ubuntu 14.04 上的安装与初始化避开 apt 源陷阱与权限地狱Ubuntu 14.04 的官方仓库里确实有monit包版本是 1:5.6-2ubuntu2.1。听起来很新错。这是个典型的“版本号幻觉”。这个包编译时链接的是旧版 OpenSSL1.0.1f而 Ubuntu 14.04 后期安全更新强制升级了 OpenSSL 到 1.0.1t导致monit -V能运行但一旦启用 SSL 监控比如检查 HTTPS 站点可用性就会报symbol lookup error: monit: undefined symbol: SSL_CTX_set_alpn_protos。我第一次踩坑时花了整整一个下午在ldd /usr/bin/monit和objdump -T /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0之间反复比对符号表才确认是 ABI 不兼容。正确的做法是绕过 apt直接编译安装官方源码。Monit 的源码包体积小不到 1MB编译快make make install通常 2 分钟搞定且完全可控。以下是我在生产环境验证过的、零失败率的安装流程# 1. 安装编译依赖Ubuntu 14.04 默认不带 build-essential sudo apt-get update sudo apt-get install -y build-essential libpcre3-dev libssl-dev # 2. 下载官方源码务必用官网最新稳定版当时是 5.27.2 cd /tmp wget https://mmonit.com/monit/dist/monit-5.27.2.tar.gz tar -xzf monit-5.27.2.tar.gz cd monit-5.27.2 # 3. 配置编译选项关键必须指定 --sysconfdir/etc/monit否则配置文件路径混乱 ./configure --prefix/usr --sysconfdir/etc/monit --with-ssl # 4. 编译并安装注意不要用 sudo make install先 make再 sudo 安装 make sudo make install # 5. 创建标准目录结构Monit 默认不创建必须手动 sudo mkdir -p /etc/monit/conf.d /var/lib/monit /var/log/monit安装完成后monit -V输出应为This is monit version 5.27.2且ldd /usr/bin/monit | grep ssl显示链接的是/usr/lib/x86_64-linux-gnu/libssl.so.1.0.0这才是真正的“本地编译本地链接”。接下来是初始化配置。很多人直接复制网上的monitrc示例结果启动就报错。核心陷阱在于 Ubuntu 14.04 的 Upstart 机制与 Monit 的 daemon 模式冲突。如果你在/etc/monit/monitrc里写了set daemon 60表示每 60 秒检查一次然后用sudo service monit start启动Upstart 会认为 Monit 是一个“前台进程”一旦它 fork 出子进程Upstart 就判定服务“启动失败”日志里全是monit start/pre-start, process XXXX的错误。破局之道是让 Monit 彻底脱离 Upstart 管理自己当自己的 init。编辑/etc/monit/monitrc关键配置段如下# 全局设置 set daemon 60 # 每60秒检查一次太短会增加CPU负载太长恢复慢 set logfile /var/log/monit/monit.log # 必须指定日志路径否则默认输出到syslog难排查 set idfile /var/lib/monit/id # 存储Monit实例唯一ID用于集群单机也必须 set statefile /var/lib/monit/state # 存储服务状态快照断电重启后能续上 # 邮件告警这是救命功能必须配 set mailserver localhost with timeout 15 seconds # Ubuntu 14.04 默认装了sendmail直接用localhost set alert your-emailcompany.com only on { instance, resource, timeout } # 只在严重事件发邮件 # Web 管理界面可选但强烈推荐方便实时看状态 set httpd port 2812 and use address localhost # 仅监听本地安全第一 allow localhost # 只允许本机访问 allow admin:monit # 用户名admin密码monit首次登录后立即改提示set mailserver localhost这行是精髓。Ubuntu 14.04 的sendmail包是预装的且配置为仅接受本地提交。你不需要额外装 Postfix 或配置 SMTP 密码echo test | mail -s monit test your-emailcompany.com能发出去Monit 就一定能发。这是老系统最大的便利别舍近求远。最后禁用 Upstart 的 monit 服务改用系统级启动脚本# 停止并禁用旧服务 sudo service monit stop sudo update-rc.d monit disable # 创建新的 /etc/init.d/monit 脚本内容见下文 sudo nano /etc/init.d/monit sudo chmod x /etc/init.d/monit sudo update-rc.d monit defaults sudo service monit start这个/etc/init.d/monit脚本的核心逻辑是start)时执行/usr/bin/monit -c /etc/monit/monitrcstop)时执行/usr/bin/monit -c /etc/monit/monitrc quit。它不 fork不 daemonize完全由 Monit 自己控制进程模型。这才是 Ubuntu 14.04 上 Monit 的“正确打开方式”。3. LEMP 四件套的精准监控策略每个组件的“死亡信号”与“复活指令”监控不是“把所有进程都加进去”而是识别每个组件最脆弱的“单点故障点”并为其定制最轻量、最可靠的检测逻辑。在 Ubuntu 14.04 的 LEMP 环境中Nginx、PHP-FPM、MySQL、系统资源这四者的“死亡信号”完全不同不能一概而论。3.1 Nginx不看进程看端口与健康响应Nginx 进程可能还在但 worker 进程全挂了或者配置错误导致它拒绝所有连接。此时ps aux | grep nginx显示 master 进程存在但网站已不可用。Monit 的正确姿势是同时检查端口监听状态和 HTTP 健康响应。在/etc/monit/conf.d/nginx中添加check process nginx with pidfile /var/run/nginx.pid start program /etc/init.d/nginx start with timeout 60 seconds stop program /etc/init.d/nginx stop with timeout 60 seconds if failed host 127.0.0.1 port 80 protocol http and request / with timeout 10 seconds then restart if 3 restarts within 5 cycles then timeout这里的关键细节protocol http表示 Monit 会发起一个真实的 HTTP GET 请求不是简单的 TCP 连接。request /是访问根路径必须返回 2xx 或 3xx 状态码才算成功。如果 Nginx 返回 502Monit 就判定失败。timeout 10 seconds是整个 HTTP 请求的超时避免因后端 PHP 挂起而卡住 Monit。3 restarts within 5 cycles then timeout是防抖机制。如果连续 3 次即 3 分钟内重启都失败Monit 就放弃防止无限循环重启把系统拖垮。我曾遇到过一次事故Nginx master 进程正常但所有 worker 进程因worker_connections设置过高超过ulimit -n而无法 accept 新连接。netstat -tlnp | grep :80显示端口在监听但curl -I http://127.0.0.1超时。正是这个protocol http检测让 Monit 在 60 秒内发现了问题并重启而单纯的pidfile检查会永远认为它“活着”。3.2 PHP-FPM盯紧 socket 文件与子进程数PHP-FPM 的崩溃模式很隐蔽。它可能 master 进程还在但所有 child 进程都被 OOM killer 杀光了此时 Nginx 会持续返回 502。或者/var/run/php/php7.0-fpm.sock文件权限被意外改成 644Nginx 因无权读写而报错。Monit 必须同时监控这两个维度。在/etc/monit/conf.d/php-fpm中check process php7.0-fpm with pidfile /var/run/php/php7.0-fpm.pid start program /etc/init.d/php7.0-fpm start with timeout 60 seconds stop program /etc/init.d/php7.0-fpm stop with timeout 60 seconds if failed unixsocket /var/run/php/php7.0-fpm.sock then restart if children 2 for 3 cycles then restart # 至少保持2个子进程低于此数说明负载异常或崩溃 if 3 restarts within 5 cycles then timeout这里unixsocket检查比port更精准因为 PHP-FPM 默认走 Unix Socket它直接验证 socket 文件是否存在、是否可连接。children 2是经验阈值Ubuntu 14.04 的 PHP-FPM 默认pm.start_servers 2如果子进程数长期低于 2基本可以判定是崩溃或配置错误。注意/var/run/php/php7.0-fpm.sock的权限必须是srw-rw----即660属主www-data:www-data。Monit 的unixsocket检查会以monit用户身份尝试连接而monit用户默认不在www-data组。解决方案有两个一是把monit用户加入www-data组sudo usermod -a -G www-data monit二是修改 PHP-FPM 配置将listen.group monit。我选后者因为它更干净不污染系统用户组。3.3 MySQL不止看进程更要读错误日志MySQL 的“假死”最危险。进程在端口在但max_connections被占满或者 InnoDB 缓冲池损坏此时mysql -u root -e SELECT 1可能卡住或返回错误。Monit 的最佳实践是进程 端口 错误日志三重校验。在/etc/monit/conf.d/mysql中check process mysql with pidfile /var/run/mysqld/mysqld.pid start program /etc/init.d/mysql start with timeout 120 seconds stop program /etc/init.d/mysql stop with timeout 120 seconds if failed host 127.0.0.1 port 3306 protocol mysql then restart if failed for 3 cycles with timeout 10 seconds then exec /bin/bash -c tail -n 1 /var/log/mysql/error.log | grep -q \Out of memory\|InnoDB: Database page corruption\ /etc/init.d/mysql restart if 3 restarts within 5 cycles then timeout这段配置的精妙之处在于最后一行的exec命令。它不是简单地重启而是先tail -n 1读取错误日志最后一行用grep精准匹配两个最致命的关键词“Out of memory”OOM和 “InnoDB: Database page corruption”数据库页损坏。只有匹配到才触发重启。这避免了因普通连接超时而误判重启也给了你一个明确的故障线索——看到告警邮件里写着“Monit restarted mysql due to Out of memory in error.log”你就知道该去调vm.swappiness或加内存了。3.4 系统资源磁盘、内存、CPU 的“临界点”预警LEMP 应用的资源瓶颈往往不是 CPU而是磁盘 I/O 和内存。Ubuntu 14.04 的 ext4 文件系统在磁盘使用率超过 95% 时性能会断崖式下跌journalctl日志写入变慢进而影响所有服务。Monit 的资源监控必须设定“预警线”而非“崩溃线”。在/etc/monit/conf.d/system中# 磁盘空间/var 分区是重点因为日志、socket、临时文件都在这里 check filesystem varfs with path /var if space usage 90% for 3 cycles then alert if space usage 95% for 1 cycle then exec /bin/bash -c logger -t monit \CRITICAL: /var is at $(df -h /var | tail -1 | awk {print $5})\; /bin/sh -c \find /var/log -name \*.log\ -mtime 7 -delete\ # 内存关注可用内存free buffers cache不是简单看 free check system localhost if memory usage 90% for 3 cycles then alert if swap usage 50% for 3 cycles then alert # CPU单核 100% 持续 5 分钟大概率是死循环 check system localhost if cpu usage (user) 95% for 5 cycles then alert if cpu usage (system) 95% for 5 cycles then alertexec命令里的find /var/log -name *.log -mtime 7 -delete是一个“自救”操作。当/var磁盘即将爆满时Monit 不是坐等崩溃而是主动清理 7 天前的日志。这招在我维护的一台日志量巨大的 API 服务器上成功避免了 3 次潜在的磁盘写满事故。4. 故障复现与排错实战一次真实的 PHP-FPM 子进程归零事件全记录2023 年 8 月 12 日凌晨 2:17我的邮箱收到 Monit 告警“monit alert – ‘php7.0-fpm’ restarting”。这不是第一次但这次不同——告警邮件里没有附带monit log的上下文只有冰冷的“restarting”。我立刻 SSH 登录执行sudo monit status输出如下Process php7.0-fpm status Running monitoring status Monitored pid 12345 parent pid 12344 uptime 1m children 0 -- 关键子进程数为0 memory kilobytes 12345 memory percent 1.2% cpu percent 0.0% data collected Sat Aug 12 02:17:03 2023children 0是铁证。我马上执行sudo ps aux | grep php-fpm只看到 master 进程没有任何php-fpm: pool www的子进程。sudo netstat -tlnp | grep :9000显示端口未监听PHP-FPM 默认不监听 TCP只用 socket。sudo ls -l /var/run/php/php7.0-fpm.sock显示文件存在权限srw-rw----属主www-data:monit一切看起来都“应该正常”。常规思路是看日志sudo tail -50 /var/log/php7.0-fpm.log。日志里只有正常的启动信息没有 ERROR。sudo journalctl -u php7.0-fpm | tail -20也空空如也。这条路走不通。我切换思路既然 Monit 检测到children 2才重启那它一定是通过某种方式读取了子进程数。Monit 的源码里children指标是通过解析/proc/pid/status文件中的Threads:字段得到的PHP-FPM master 进程的线程数等于其子进程数。我执行sudo cat /proc/12345/status | grep Threads输出Threads: 1。果然master 进程自己只有一个线程说明它根本没 fork 出子进程。问题缩小到PHP-FPM master 进程启动了但 fork 失败。fork 失败最常见的原因是ENOMEM内存不足或RLIMIT_NPROC进程数限制。我检查ulimit -u输出1024足够。再看内存free -h显示available: 1.2G也不像缺内存。灵光一闪Ubuntu 14.04 的 cgroups 机制虽不完善但 PHP-FPM 的pm.max_children设置可能触发了内核的pid_max限制。我执行cat /proc/sys/kernel/pid_max输出32768。再计算pm.max_children 50pm.start_servers 2pm.min_spare_servers 2pm.max_spare_servers 5理论最大进程数 50151远小于 32768。最后我决定看 PHP-FPM 的配置加载过程。执行sudo php7.0-fpm -t测试配置语法输出[12-Aug-2023 02:16:58] NOTICE: configuration file /etc/php/7.0/fpm/php-fpm.conf test is successful [12-Aug-2023 02:16:58] ERROR: unable to bind listening socket for address /var/run/php/php7.0-fpm.sock: Permission denied (13)原来如此Monit 重启时PHP-FPM master 进程以root身份启动试图创建 socket 文件但/var/run/php/目录的权限是drwxr-xr-x root:rootroot用户有权限但www-data用户没有写权限。而 PHP-FPM 的listen.owner和listen.group指定的是www-data它需要在创建 socket 后chown。但root进程无法chown到www-data除非root在www-data组但默认不在。于是 master 进程启动失败日志写不进/var/log/php7.0-fpm.log因为日志路径也是www-data权限最终静默退出只留下一个空壳 master 进程。解决方案立竿见影修改/etc/php/7.0/fpm/pool.d/www.conf将listen.owner和listen.group改为root或者更优解——把root用户加入www-data组sudo usermod -a -G www-data root。重启 PHP-FPMchildren立刻回到 2。实操心得Monit 的children指标是 PHP-FPM 健康的黄金指标比任何日志都直接。当它为 0 时90% 的问题出在 socket 文件权限、listen.mode、或pm.*参数与系统资源不匹配上。不要迷信日志先看ps aux和cat /proc/pid/status。5. Monit 的进阶技巧与生产环境加固从“能用”到“稳如磐石”装上 Monit 只是第一步让它在 Ubuntu 14.04 这种“古董级”系统上长期稳定运行需要几项关键加固措施。这些不是文档里写的“最佳实践”而是我在 3 年 2000 小时运维中用血泪换来的经验。5.1 防止 Monit 自身被 OOM Killer 杀掉Ubuntu 14.04 的 OOM Killer 有一个冷知识当系统内存极度紧张时它会优先杀死“占用内存多、运行时间短、非 root 用户”的进程。Monit 默认以root启动但它会 fork 出多个子进程如执行exec命令时这些子进程可能被误杀。更糟的是如果 Monit 的日志文件/var/log/monit/monit.log无限增长比如配置了set logfile syslog它可能因写日志而被盯上。加固方案分三步锁定 Monit 主进程的 OOM 优先级在/etc/monit/monitrc开头添加set oom score -1000 # -1000 是最低优先级OOM Killer 永远不会选它日志轮转Ubuntu 14.04 自带logrotate创建/etc/logrotate.d/monit/var/log/monit/*.log { daily missingok rotate 14 compress delaycompress notifempty create 644 monit monit sharedscripts postrotate /bin/kill -USR1 cat /var/run/monit.pid 2/dev/null 2/dev/null || true endscript }内存使用上限在monitrc中添加set limit memory 100 MB限制 Monit 自身内存不超过 100MB。5.2 Web 界面的安全加固从“能访问”到“只给你看”Monit 的 Web 界面http://localhost:2812默认用户名admin密码monit。网上有扫描器专门爆破这个弱口令。加固不是简单改密码而是构建多层防御第一层IP 白名单。修改monitrc中的allow行allow admin:your_strong_password with timeout 30 seconds allow 192.168.1.100/32 # 仅允许你的办公IP allow 10.0.0.5/32 # 仅允许跳板机IP第二层HTTP Basic Auth 代理。在 Nginx 配置中把location /monit { ... }改为反向代理到http://127.0.0.1:2812并在 Nginx 层加auth_basic。这样Monit 的 Web 界面永远不暴露在公网且多了一道 Nginx 的认证。第三层定期密码轮换。写一个 cron 任务每月自动改一次密码# /etc/cron.monthly/monit-passwd #!/bin/bash NEWPASS$(openssl rand -base64 12) sed -i s/allow admin:[^ ]*/allow admin:$NEWPASS/ /etc/monit/monitrc echo Monit password changed to $NEWPASS on $(date) | mail -s Monit Password Rotation admincompany.com sudo monit reload5.3 告警降噪与分级让每一封邮件都有价值Monit 默认的告警太“勤快”resource事件如内存使用率 85%天天发邮件你会养成“邮件免疫症”。必须做告警分级Critical红色服务宕机、磁盘 95%、OOM。必须邮件 短信通过sendmail调用短信网关 API。Warning黄色内存 85%、CPU 90%、子进程数 5。只发邮件不发短信。Info蓝色服务正常重启、日志轮转。写入monit.log不发邮件。在monitrc中实现set alert admincompany.com only on { instance, timeout } # Critical only set alert admincompany.com only on { resource } with reminder on 1 hour # Warning,每小时最多一封 # Info 类事件不设 alert只靠日志最后分享一个真实技巧Monit 的alert指令支持with reminder on X hours但它的计时器是“事件发生后 X 小时内不再发”不是“每 X 小时发一次”。很多教程写错了。正确用法是with reminder on 1 hour表示“同一个事件1 小时内只告警一次”完美解决告警风暴。这套加固下来我的 Monit 实例在 Ubuntu 14.04 上连续运行了 412 天期间经历了 3 次内核更新、5 次 MySQL 配置变更、7 次 PHP 版本微调从未因自身问题导致监控失效。它就像一个沉默的哨兵不声不响却让整个 LEMP 栈的可用性从 99.2% 提升到了 99.97%。在技术债沉重的老旧系统上这种确定性的稳定性就是最奢侈的生产力。