在 linux 中,nginx 结合 lua 脚本实现邮件告警的核心做法是利用 nginx 的 `error_log` 指令配合 lua 的 `log_by_lua_block` 模块,或者通过 openresty 提供的 `ngx.timer` 异步发送请求。具体而言,当 nginx 检测到上游服务不可用(如连接超时或返回 502/504)时,lua 代码会捕获该错误事件,构建 http post 请求发送至 smtp 服务器或邮件网关 api,从而触发邮件通知。

Nginx 如何识别并触发故障转移逻辑
Nginx 识别故障主要依赖 upstream 模块的健康检查机制和负载均衡策略。当配置了 `max_fails` 和 `fail_timeout` 时,如果上游节点在指定时间内失败次数超过阈值,Nginx 会自动将该节点标记为暂时不可用,并将流量转移到其他健康节点。这一过程是 Nginx 内核层面的行为,但默认情况下不会主动发送邮件。要实现告警,我们需要在 Lua 层监听这些错误事件。

upstream backend {
server 192.168.1.10:8080 max_fails=3 fail_timeout=10s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=10s;
server 192.168.1.12:8080 max_fails=3 fail_timeout=10s;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
# 使用 log_by_lua_block 捕获日志中的错误信息
log_by_lua_block {
local err = ngx.log(ngx.ERR, "Upstream error detected")
-- 这里可以进一步解析 ngx.var.upstream_status
if ngx.var.upstream_status == "502" or ngx.var.upstream_status == "504" then
-- 触发告警逻辑
alert.send_mail("Server Down", "Upstream status: " .. ngx.var.upstream_status)
end
}
}
}
上述配置展示了基本的故障转移环境。当 `192.168.1.10` 连续失败 3 次且在 10 秒内未恢复时,Nginx 会将流量切分到 `.11` 和 `.12`。此时,`log_by_lua_block` 会在每次请求处理后执行,检查 `ngx.var.upstream_status`。如果状态码为 502 或 504,说明发生了故障转移或上游错误,此时调用自定义的 `alert.send_mail` 函数。注意,直接同步发送邮件可能会阻塞 Nginx 工作进程,因此通常建议使用 `ngx.timer.at` 进行异步处理。
Lua 脚本如何实现异步邮件发送
为了避免阻塞 Nginx 主线程,Lua 脚本应使用 OpenResty 提供的定时器功能来异步发送 HTTP 请求到邮件服务。我们可以封装一个基于 `cjson` 和 `resty.http` 的邮件发送模块,将告警内容序列化为 JSON 并通过 HTTPS 发送给支持 Webhook 的邮件网关(如 SendGrid、AWS SES 或自建 Postfix 网关)。

-- alert.lua
local cjson = require "cjson"
local http = require "resty.http"
local function send_async_alert(title, body)
ngx.timer.at(0, function(premature, args)
if premature then return end
local title = args[1]
local body = args[2]
local httpc = http.new()
httpc:set_timeout(5000) -- 5秒超时
local resp, err = httpc:request_uri("https://api.mailgun.net/v3/example.com/messages",
{
method = "POST",
headers = {
["Authorization"] = "Basic dXNlckBleGFtcGxlLmNvbTprZXktMTIzNDU2",
["Content-Type"] = "application/x-www-form-urlencoded"
},
query = {
from = "Alerts <alerts@example.com>",
to = "admin@example.com",
subject = title,
text = body
}
})
if not resp then
ngx.log(ngx.ERR, "Failed to send email: ", err)
else
ngx.log(ngx.INFO, "Email sent successfully, status: ", resp.status)
end
httpc:close()
end, {title, body})
end
return {send_mail = send_async_alert}
这段代码定义了一个 `send_async_alert` 函数,它使用 `ngx.timer.at` 创建一个后台任务。在任务中,实例化 `resty.http` 客户端,设置合理的超时时间,并向邮件服务商的 API 发起 POST 请求。请求体包含发件人、收件人、主题和正文。由于使用了定时器,即使邮件发送失败或超时,也不会影响当前 Nginx 请求的正常响应。开发者需要将 `Authorization` 替换为实际的 API Key,并调整 `query` 参数以适配具体的邮件服务提供商接口。
基于 upstream_status 502 的具体告警验证
在实际运行中,当上游服务器完全宕机时,Nginx 会返回 502 Bad Gateway。我们可以通过模拟上游关闭端口来验证 Lua 脚本是否能正确捕获该状态并触发邮件发送。假设我们有一个简单的 Python 脚本来模拟上游服务,启动后故意杀死进程,观察 Nginx 的错误日志和邮件接收情况。
# 模拟上游服务的简单 Python 脚本
import http.server
import socketserver
import time
import os
class Handler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK")
with socketserver.TCPServer(("192.168.1.10", 8080), Handler) as httpd:
print("Serving at port 8080")
httpd.serve_forever()
运行上述 Python 脚本后,访问 Nginx 代理的地址。一旦停止 Python 进程,Nginx 的 `upstream_status` 将变为 502。此时,`log_by_lua_block` 中的条件判断成立,调用 `alert.send_mail`。由于使用了异步定时器,邮件发送动作会在后台执行。开发者可以通过检查 Nginx 的 `error.log` 中是否有 "Email sent successfully" 的日志,以及收件箱是否收到标题为 "Server Down" 的邮件,来验证整个流程的有效性。如果邮件未收到,需检查 `resty.http` 的网络连通性及 API 密钥的正确性。


















