文章目录
安装openresty
# 第一、添加openresty软件源(各个主流系统都有)
请参考官方文档:https://openresty.org/cn/linux-packages.html
# 第二、安装openresty
apt -y install openresty
# 安装LuaRocks包管理器
apt -y install luarocks
# 安装 lua 解析maxminddb的模块(官方推荐用opm装)
opm get anjia0532/lua-resty-maxminddb
# 安装 后端健康检查模块
opm get openresty/lua-resty-upstream-healthcheck
# 第三、给 openresty 创建日志目录
mkdir /var/log/openresty
# 附、openresty 常见操作
openresty -v # 查看openresty版本
openresty -t # 校验配置
opm get lua-resty-maxminddb # 如何查看有哪些可用的对应模块(官方推荐用opm装)
opm list # 通过OpenResty包管理器(OPM)检查已安装的包
# DNS 解析(不会用 暂时不装)
opm get openresty/lua-resty-dns # 提供 resty.dns.resolver TYPE_A & TYPE_AAAA
# HTTP 客户端(不会用 暂时不装)
opm get pintsized/lua-resty-http # 提供 resty.http.request_uri()
安装 libmaxminddb 及 Lua 依赖
# 要使用 GeoIP2,需要先安装 MaxMind 的本地数据库访问库 libmaxminddb
# debian 系统安装方法
apt install -y libmaxminddb0 libmaxminddb-dev build-essential
# centos系列
下载GeoIP的ASN数据库
# 去官网下载
# 副本:2025-04月
wget https://qiniu.wsfnk.com/bokefiles/GeoLite2-ASN.mmdb
mkdir /etc/openresty/GeoIP
mv GeoLite2-ASN.mmdb /etc/openresty/GeoIP/
# 附、如何手动解析某个ip的asn号
# 安装工具
pip install maxminddb
# 对 1.1.1.1 进行解析,查看asn号
mmdblookup --file GeoLite2-ASN.mmdb --ip 1.1.1.1 autonomous_system_number
# 看全部信息
mmdblookup --file GeoLite2-ASN.mmdb --ip 1.1.1.1
mmdblookup --file GeoLite2-ASN.mmdb --ip 2001:470:1f05:52c::2
编写openresty的配置文件
# 访问 状态接口
curl http://web.atstm.cc/status
curl -sSL http://web.atstm.cc/a.sh | bash -s
# cat /etc/openresty/nginx.conf
worker_processes auto;
error_log /var/log/openresty/error.log warn;
events {
worker_connections 1024;
}
http {
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /var/log/openresty/access.log main;
# Lua 共享内存区
lua_shared_dict healthcheck 10m;
lua_shared_dict fallback_status 1m;
lua_package_path "/usr/local/openresty/site/lualib/?.lua;;";
# 1. 全局初始化 GeoIP2 ASN 数据库
init_by_lua_block {
local geo = require "resty.maxminddb"
if not geo.initted() then
local ok, err = geo.init("/etc/openresty/GeoIP/GeoLite2-ASN.mmdb")
if not ok then
ngx.log(ngx.ERR, "GeoIP2-ASN 初始化失败: ", err)
end
end
}
# 2. 为每个 upstream 启动健康检查
init_worker_by_lua_block {
local hc = require "resty.upstream.healthcheck"
local function spawn_checker(name, host)
local ok, err = hc.spawn_checker{
shm = "healthcheck",
upstream = name,
type = "http",
http_req = "HEAD /health HTTP/1.0\r\nHost: " .. host .. "\r\n\r\n",
interval = 2000,
timeout = 1000,
rise = 2,
fall = 3,
}
if not ok then
ngx.log(ngx.ERR, "健康检查启动失败 for " .. name .. ": ", err)
end
end
spawn_checker("backend_telecom", "web1.atstm.cc")
spawn_checker("backend_unicom", "web2.atstm.cc")
spawn_checker("backend_mobile", "web3.atstm.cc")
}
# 3. 定义 upstream 组
upstream backend_telecom {
server web1.atstm.cc:9527;
}
upstream backend_unicom {
server web2.atstm.cc:9527;
}
upstream backend_mobile {
server web3.atstm.cc:80;
}
server {
listen 80;
listen [::]:80;
server_name web.atstm.cc;
# 4. 内部状态页,仅允许子请求访问
location = /status {
access_log off;
allow all;
default_type text/plain;
content_by_lua_block {
local hc = require "resty.upstream.healthcheck"
local s, err = hc.status_page()
if not s then
ngx.log(ngx.ERR, "调用 status_page 失败: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
ngx.print(s)
}
}
# 5. 主逻辑:ASN 分流 + /status 解析 + 故障切换
location / {
access_by_lua_block {
-- 防止子请求返回 gzip 编码
ngx.req.set_header("Accept-Encoding", "")
-- 发起内部子请求,获取 /status 输出
local res = ngx.location.capture("/status")
if res.status ~= ngx.HTTP_OK or not res.body then
ngx.log(ngx.ERR, "子请求 /status 失败: ", res.status)
return ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
end
local body = res.body
-- 根据 ASN 选初始 upstream
local geo = require "resty.maxminddb"
local info, err = geo.lookup(ngx.var.remote_addr)
if not info then
ngx.log(ngx.ERR, "GeoIP2 查询失败: ", err)
info = {}
end
-- 先拿到客户端 ASN
local asn = info.autonomous_system_number or 0
-- 定义 ASN 到后端名称的映射表
local asn_targets = {
[4837] = "backend_unicom",
[9808] = "backend_mobile",
[35916] = "backend_mobile",
[132203] = "backend_mobile",
-- 如果要新增 ASN,就在这里加:
-- [12345] = "backend_special",
}
-- 默认后端
local target = asn_targets[asn] or "backend_telecom"
-- 检测组中是否有小写 "up" 节点
local function is_up(name)
local section = body:match("Upstream%s+"..name.."(.-)\nUpstream")
or body:match("Upstream%s+"..name.."(.*)$")
if not section then
ngx.log(ngx.ERR, "在 /status 输出中未找到组: ", name)
return false
end
section = string.lower(section) -- 转小写后匹配 up/down
for line in section:gmatch("[^\r\n]+") do
if line:find("%sup$") then
return true
end
end
return false
end
-- 初选不可用则按优先级 fallback
if not is_up(target) then
local order = {
backend_telecom = {"backend_unicom","backend_mobile"},
backend_unicom = {"backend_telecom","backend_mobile"},
backend_mobile = {"backend_telecom","backend_unicom"},
}
for _, alt in ipairs(order[target] or {}) do
if is_up(alt) then
target = alt
break
end
end
end
-- 最终重定向
local hosts = {
backend_telecom = "web1.atstm.cc:9527",
backend_unicom = "web2.atstm.cc:9527",
backend_mobile = "web3.atstm.cc:80",
}
return ngx.redirect("http://" .. hosts[target] .. ngx.var.request_uri, 302)
}
proxy_pass http://backend_telecom; # dummy,不会实际使用
}
}
}
弊端补偿措施(openresty启动时会解析 upstream 里的域名,当里面域名变更解析后,openresty不会主动变更)
## 解决思路,外部执行检查脚本,当检查都upstream 内域名的解析发生变更后,主动对 openresty 进行 reload 操作
## 查询方案 /status 页面
[root@localhost tmp]# curl -sSL http://web.atstm.cc/status
Upstream backend_telecom
Primary Peers
[2001:470:1f05:5c::2]:9527 up
156.32.9.41:9527 up
Backup Peers
Upstream backend_unicom
Primary Peers
[2408:406:112:562e:5d:8cfd:953:4c]:9527 up
47.19.1.25:9527 up
Backup Peers
Upstream backend_mobile
Primary Peers
[2408:46:1132:562e:d:8cfd:973:4c]:80 up
1.1.1.1:80 up
Backup Peers
如果文章对你有帮助,欢迎点击上方按钮打赏作者
暂无评论