openresty基于GeoIP-ASN数据中的ASN号实现302重定向系统

安装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
声明:本文为原创,作者为 辣条①号,转载时请保留本声明及附带文章链接:https://boke.wsfnk.com/archives/1463.html
谢谢你请我吃辣条谢谢你请我吃辣条

如果文章对你有帮助,欢迎点击上方按钮打赏作者

最后编辑于:2025/4/19作者: 辣条①号

目标:网络规划设计师、系统工程师、ceph存储工程师、云计算工程师。 不负遇见,不谈亏欠!

暂无评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

arrow grin ! ? cool roll eek evil razz mrgreen smile oops lol mad twisted wink idea cry shock neutral sad ???

文章目录