4月16日 14:52,freeshell 控制面板访问超时,几分钟后转变成 502 错误。直到 17 日 12:47,zsj 重启 php5-fpm,问题才解决。非常抱歉一天来无法访问 freeshell 控制面板。Freeshell 虚拟机没有受到影响。

问题触发的过程是这样的:

  1. 4月16日 14:48,某用户安装 Fedora 20,而 Fedora 20 安装脚本的最后一步 yum localinstall 忘了加 -y 选项(这是4月11日引入的 bug),导致安装脚本停在让用户选择 y/n 的位置。
  2. PHP session 在 session_start() 后会被锁住,在脚本退出或者调用 session_write_close() 之前,如果另一个相同 session 的 HTTP 请求也调用了 session_start(),则会等待这个锁。由于 PHP 脚本一般执行时间很短,我一直没发现这个坑爹的特性。之前也发现在重装等操作过程中,刷新页面没有响应,不过一直以为是 OpenVZ 的锁,没有引起重视。
  3. 此用户多次刷新页面,每个 HTTP 请求都是相同的 session 在等待锁,占满了 php5-fpm 的 50 个 worker 进程,导致 freeshell 控制面板的所有访问被阻塞。

这两个 bug 的修复:

问题持续了整整一天,Freeshell 的报警系统怎么没有工作呢?

  1. DNSPod 报警通过邮件和短信发送,报警邮件进了 Gmail 的垃圾邮件箱,报警短信没有收到。
  2. Blog 服务器上部署的 URL 监控系统,报警邮件是发到 [email protected],LUG 邮件服务器里这个 Email 的转发是匹配的默认规则,不知什么时候这条默认规则被去掉了。由于核心服务都配置了 DNSPod 监控,每次出问题时 blog 上监控系统的失语并未被发现。
  3. Freeshell 某虚拟机里部署的 URL 监控系统,由于监控了一些不太稳定的 URL,报警邮件被分类到一个标签里,我很长时间才去看一次。
  4. Freeshell 控制服务器上本来每 5 分钟检查一次 freeshell 物理节点上是否有运行时间过长的虚拟机管理进程,如果发现就发邮件报警,但上个月 freeshell 出现大量僵死进程之后,收到大量报警邮件,就把这个 crontab 关掉了。freeshell 换内核后僵死进程问题修复了,但忘了重新打开 crontab 监控。

从理论上说,这个问题应该被上述 4 个监控系统同时发现,但事实上只有 DNSPod 一个监控渠道是工作的。DNSPod 确实尽职尽责地发现了问题,但邮件进垃圾箱、短信没收到同时发生,还是挺巧合的。

我给 freeshell 脆弱的代码加了一些防护措施:

  • Freeshell 管理操作加乐观锁,如果加锁失败,给用户返回错误。
  • Freeshell 锁定期间,在控制面板上提示用户 “另一个操作正在进行”。
  • SSH 操作开始前,就把 command line 写进日志备查(之前是操作结束后才将 command line 和 SSH output 一并写入日志)。
  • SSH 操作结束后,在日志里记录操作耗时,以便排查长时间锁住的问题。
  • SSH 操作失败(返回码非零)时,发邮件给管理员。
  • 每小时检查是否有处于锁定状态超过一小时的 freeshell,如果有,邮件通知管理员。
  • 封装 mysql_query,数据库访问失败时将错误信息发邮件给管理员。
  • 给 freeshell 管理脚本中的 HTTP 操作(从 mirrors 源更新)设置 60 秒的超时。

SSH 操作失败的邮件报警机制很快就派上了用场:3月23日由于批量替换变量名出错(错误发生在一个比较少见的代码执行路径上),导致 7 号节点创建虚拟机失败。经查,这个 bug 共导致 4 个用户创建虚拟机失败,已经恢复这些用户的 freeshell 并发送了邮件。

今天提交了 24 个 commit,sigh……