1 博客可用性监控体系的演进
随着我博客架构的多次升级,背后的运行环境也变得越来越复杂:除了 WordPress 本身,还涉及读写分离(2个只读节点和1个主写节点)以及 Cloudflare 等一系列服务——这些环节共同构成了博客的运行体系,任何一个环节出现异常,都可能影响最终的访问体验。而单纯依靠自己偶尔打开网站检查,很难及时发现博客服务是否正常。因此,我很早就开始为博客搭建自动化的健康监测和故障通知机制。
最初,我采用的是自建 Uptime 监控系统,一旦监测到博客服务异常,就通过 Bark 服务向手机和电脑发送通知(参见文章:《Building a Real-time Application Health Monitoring and Alerting System Based on Uptime and Bark》)。这种方式实现起来比较简单,也能满足基本的可用性监测需求。
Bark 是一款面向苹果生态的免费、开源推送通知服务,主要由客户端和服务端两部分组成。客户端是安装在苹果终端(iPhone、iPad )上的免费 App(也可以在支持运行 iOS/iPadOS 应用的 Apple Silicon Mac 上使用),负责接收和展示推送通知;服务端则负责接收外部应用或监控系统发送的 HTTP 请求(提供类似 Webhook 的接口),并通过苹果的 APNs(Apple Push Notification service)推送服务,将通知转发到对应的苹果终端设备。
服务端可以使用免费的公共服务,也可以自己搭建(想自己搭建的朋友可以参考文章:《Building a Bark Server-based message push server》)。
Bark 的使用方式非常简单,只需要向指定的 API 地址发送一个 HTTP GET 或 POST 请求,就可以将自定义的标题和内容推送到对应设备。因此,无论是 Uptime 的故障告警,还是其他脚本、应用的运行状态通知,都可以很方便地通过 Bark 发送。
但随着博客架构逐渐复杂,我慢慢发现它存在两个比较明显的局限:
1、监控请求需要经过 Cloudflare 的安全策略检查。虽然可以通过自定义安全规则放行监控请求,但这意味着需要额外维护一套针对监控流量的安全规则,而且始终需要考虑这些规则与现有安全策略之间的关系。
2、自建 Uptime 本质上只能代表其所在 VPS 的网络视角:它虽然能够检查从这台 VPS 到 Cloudflare 边缘网络这条路径是否畅通,却无法反映其他地区、其他运营商的用户是否也能正常访问博客。换句话说,即使监控结果显示一切正常,也不能完全代表博客在全球范围内都具有良好的可访问性。
为了解决这些问题,后来我调整了监控架构:首先为 WordPress 开发了专门的探活插件,通过优化健康检查接口,让它使用 HTTP 204 状态码返回探活结果(参见文章:《WordPress Health Check API Optimization: Building an Active Probe Plugin with 204 Responses》),避免返回不必要的响应正文,减少单次健康检查的响应开销。
然后结合 Cloudflare 自身的运行状况检查(Health Checks)和事件通知机制,由 Cloudflare 在云端执行监测任务,并从多个区域发起健康检查。借助这种方式,我可以从不同网络位置检查博客公网入口以及各个 WordPress 节点的可用性,而不再局限于某一台 VPS 的单一网络视角。相关实现过程参见文章:《Cloudflare Monitoring and Alerting Combination in Practice: Lightweight O&M Experience Brought by Health Checks + Event Alerts》。
最后结合 Bark 手机通知和邮件通知,我逐渐建立起了一套相对完整的博客健康监测体系:无论是公网访问入口出现异常,还是背后的某个 WordPress 节点发生故障,都能够通过相应的监测和通知机制及时发现。
从当时的实际效果来看,这套体系已经能够满足我对博客服务可用性监测的要求。我原本以为,只要公网入口和各个后端节点都处于正常状态,就基本可以保证访客能够顺利访问博客。
但近期发生的一次访问异常,却让我意识到:监控系统显示服务正常,并不意味着所有用户的访问路径都一定正常。
2 一次异常的访问体验
在最近一段时间外出时,我发现了一个有些奇怪的问题:手机切换到蜂窝网络后,使用浏览器访问自己的博客,页面始终无法正常加载,进度条总是走到一半左右就停在那里:

但这个问题并不是始终存在的,我在家里的时候,无论使用台式机、iPad 还是手机浏览器,都可以正常打开博客——也就是说,问题似乎只出现在手机使用蜂窝网络访问博客的场景中。起初我还以为是只有我自己遇到的问题,但是TG群里也有其他朋友在反映:

我首先查看了 Cloudflare 的健康监测记录,并没有发现任何异常告警,公网入口和后端节点的监测状态都正常。随后又分别检查了家宽和手机蜂窝网络下的 DNS 解析结果,发现博客域名在两种网络环境下都正常解析到了 Cloudflare 的 IP 地址:


从当时掌握的信息来看,既没有发现服务端异常,也没有发现明显的 DNS 解析问题。考虑到国内网络环境中存在各种复杂因素,而且家里的访问始终正常,我也没有太在意,想着可能只是蜂窝网络侧的临时问题,过段时间应该就会自行恢复。
不过,后来又有群友反映了一个问题——最近访问我的网站时,浏览器会突然弹出一个要求网站”访问本地网络上其他设备”的权限提示。我听后觉得有些奇怪,于是也用浏览器的无痕模式测试了一下,结果还真能复现:

这个问题就不能拖了,毕竟,正常访客访问一个博客,突然跳出要求访问本地网络设备的权限提示,难免会让人产生一些不必要的联想。关键是,我自己根本不知道网站什么时候开始请求这些权限的,更不知道它为什么要访问访客的本地网络。于是,赶紧开始排查。
3 从异常请求到问题修复
既然浏览器出现了”访问本地网络中的其他设备”的权限提示,就说明网页加载过程中很可能存在访问内网 IP 的行为。最直接的排查方式,就是检查打开网页时实际加载了哪些资源。
我打开浏览器无痕模式,进入开发者工具的”网络”选项卡,再次访问博客,果然发现了异常:

所有报错的资源都指向”https://192.168.10.xx:xx/wp-content/plugins/wp-editormd/assets/“这个路径,也就是我安装的 WP Editor.md 插件所在目录。
注:WP Editor.md 是一款为 WordPress 提供 Markdown 编辑和相关扩展功能的插件,支持使用 Markdown 语法编写文章,并提供代码高亮、数学公式渲染等功能。
看到这里我忽然想起,大概几周前,我曾因为 wp-editormd 插件在Wordpress后台报错:

当时怀疑是 CDN 上的资源出现了问题,于是直接将资源加载方式切换成了”使用本地资源”:

这里还需要解释一下,为什么我配置的本地资源地址会使用家庭内网 IP,这与博客目前采用的多节点架构有关:平时负责文章编写和修改的是 Mac mini 主写节点,它并不直接承担对外发布任务;真正负责对外提供博客访问服务的是另外两个只读节点。因此,如果通过公网域名访问博客,我既无法确定当前访问的是哪个只读节点,也无法通过这种方式直接访问主写节点。
为了方便不同节点之间的日常管理和资源访问,我一直采用内网地址来访问家庭局域网中的主写节点和只读节点节点,而位于芝加哥 VPS 上的只读节点则通过 Tailscale IP 访问。因此,在配置 WP Editor.md 的本地资源时,我也顺手使用了家庭主写节点的内网地址 192.168.10.xx:xx,并没有考虑到这个地址最终可能被写入面向公网访客的前端页面中。
另,对WordPress多活架构感兴趣的朋友可以参考文章:Implementation Scheme of WordPress Multi-Active Architecture (Simplified Version) in Personal Blogs。
切换后,后台报错确实消失了,我也就认为问题已经解决。只是当时我理所当然地以为,WP Editor.md 只影响后台文章编辑功能,并不会影响前台页面。现在看来,这个判断显然过于简单了。
从浏览器的请求记录来看,实际加载到前台的资源一共有三项:
/wp-content/plugins/wp-editormd/assets/KaTeX/katex.min.css?ver=10.2.1
/wp-content/plugins/wp-editormd/assets/Emojify.js/css/basic/emojify.min.css?ver=10.2.1
/wp-content/plugins/wp-editormd/assets/FrontStyle/frontstyle.min.js?ver=10.2.1
它们分别涉及 KaTeX 数学公式渲染、Emojify 表情符号显示以及插件前端样式相关功能。也就是说,这些并非仅供后台编辑器使用的资源,而是实际出现在了博客前台页面中。由于当时配置的是家庭内网绝对路径,访客浏览器加载这些资源时,就会触发对本地网络资源的访问限制,从而出现权限提示。
至于我这里的处理过程,倒是有些意外。原本我打算把静态资源路径改成指向博客的公网域名 https://blog.tangwudi.com/wp-content/plugins/wp-editormd,这样至少可以避免前台继续引用外部访客无法访问的家庭内网资源。不过,这样调整也会带来一个问题:我平时通过内网地址访问 WordPress 后台编辑文章时,静态资源请求仍然会指向公网域名,由于请求 Referer 与现有 Cloudflare WAF 规则不匹配,可能会被拦截,因此还需要对 WAF 规则做一点小修改。
没想到,重新检查插件配置时,我尝试再次切换回“使用 CDN 资源”,之前的资源加载报错居然就消失了。既然 CDN 资源已经恢复正常,自然也就省去了修改静态资源路径和调整 WAF 规则的麻烦。至于最初 CDN 资源为什么会出现异常,我没有继续深究,下次再遇到时再说吧~。
保存配置后,我重新使用浏览器无痕模式访问博客,之前要求访问本地网络设备的权限提示已经消失。随后又切换到手机蜂窝网络进行测试,博客也恢复了正常访问。
4 从服务端监控到真实用户体验
现在回头看,之前手机浏览器使用蜂窝流量时卡在加载进度条中间,应该也是同一个原因造成的:192.168.10.xx:xx 属于家庭局域网使用的私有 IP 地址,在家庭局域网内访问时,这个问题并不明显;而切换到蜂窝网络后,由于设备根本无法访问这个私有 IP 地址,页面加载异常就表现得更加突出。
这种影响并不只体现在手机蜂窝网络访问时。在家庭局域网以外的电脑上,通过公网域名访问博客时,除了 Chrome 内核浏览器会弹出“网站尝试访问本地网络设备”的权限请求外,页面中指向内网地址的资源请求也无法正常完成,浏览器需要等待这些请求处理,因此页面整体加载速度也可能受到影响。怪不得之前还有群友反映过类似的问题:

只不过,不同浏览器和网络环境下的具体表现有所不同,并不一定都会像手机浏览器那样直接卡在加载进度条中间。
这个问题之所以一直没有被我发现,恰恰是因为平时大部分时间我都在家庭局域网内测试博客。虽然错误的资源地址在这个环境下同样无法正常访问,但对页面加载速度的影响并不明显,加上这些资源并非博客正常浏览所必需的内容,而我平时也很少使用 KaTeX 数学公式渲染、Emojify 表情符号显示等插件前端功能,因此一直没有引起我的注意,直到从外部网络访问时才暴露出这个隐患。
这次问题也让我意识到,服务端监控正常,并不意味着用户访问体验一定正常。我们平时搭建的健康检查体系,主要检查服务本身能否正常响应,以及特定接口和网络链路是否可用,却无法完全覆盖访客打开网页时涉及的所有环节。
对于 WordPress 这样由核心程序、主题和插件共同组成的系统,页面能否正常呈现,还取决于 JavaScript、CSS、图片及其他资源能否正确加载。即使服务器正常响应,只要某个资源引用了错误的地址,或者受到浏览器安全策略、用户网络环境等因素影响,访客仍然可能遇到页面加载异常。这次 WP Editor.md 插件的问题就是一个典型例子:WordPress 服务本身没有异常,Cloudflare 的健康监测也没有告警,但普通访客的访问体验已经受到了影响。
归根结底,自动化监控负责尽早发现服务异常,真实访问测试则负责确认用户能否正常使用服务。前者提高问题发现的及时性,后者弥补监控覆盖范围的不足。对于个人博客来说,不一定需要建立复杂的测试体系,但不能把监控状态正常等同于网站一切正常。服务是否真正可用,最终还是要回到用户的实际访问体验来判断。
注:WP Editor.md 已经有 6 年多没有更新了。之前觉得它用起来还算顺手,加上懒得折腾,所以一直沿用至今。经过这次问题,看来也该考虑寻找替代方案了。