跳转至

checkups: 在纯 Lua 中管理 NGINX 上游

安装

如果您尚未设置 RPM 仓库订阅,请 注册。然后您可以继续以下步骤。

CentOS/RHEL 7 或 Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-checkups

CentOS/RHEL 8+、Fedora Linux、Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-checkups

要在 NGINX 中使用此 Lua 库,请确保已安装 nginx-module-lua

本文档描述了 lua-resty-checkups v0.1,于 2019 年 2 月 1 日发布。


  • 定期向上游服务器发送心跳
  • 主动和被动健康检查
  • 动态上游更新
  • 通过加权轮询或一致性哈希进行负载均衡
  • 与 Nginx 上游块同步
  • 按级别或按键尝试集群

概述

    -- config.lua

    _M = {}

    _M.global = {
        checkup_timer_interval = 15,
        checkup_shd_sync_enable = true,
        shd_config_timer_interval = 1,
    }

    _M.ups1 = {
        cluster = {
            {
                servers = {
                    { host = "127.0.0.1", port = 4444, weight=10, max_fails=3, fail_timeout=10 },
                }
            },
        },
    }

    return _M
    -- nginx.conf

    lua_shared_dict state 10m;
    lua_shared_dict mutex 1m;
    lua_shared_dict locks 1m;
    lua_shared_dict config 10m;

    server {
        listen 12350;
        return 200 12350;
    }

    server {
        listen 12351;
        return 200 12351;
    }

    init_by_lua_block {
        local config = require "config"
        local checkups = require "resty.checkups.api"
        checkups.init(config)
    }

    init_worker_by_lua_block {
        local config = require "config"
        local checkups = require "resty.checkups.api"

        checkups.prepare_checker(config)
        checkups.create_checker()
    }

    server {
        location = /12350 {
            proxy_pass http://127.0.0.1:12350/;
        }
        location = /12351 {
            proxy_pass http://127.0.0.1:12351/;
        }

        location = /t {
            content_by_lua_block {
                local checkups = require "resty.checkups.api"

                local callback = function(host, port)
                    local res = ngx.location.capture("/" .. port)
                    ngx.say(res.body)
                    return 1
                end

                local ok, err

                -- 连接到一个不可用的服务器,没有可用的上游
                ok, err = checkups.ready_ok("ups1", callback)
                if err then ngx.say(err) end

                -- 将服务器添加到 ups1
                ok, err = checkups.update_upstream("ups1", {
                    {
                        servers = {
                            { host = "127.0.0.1", port = 12350, weight=10, max_fails=3, fail_timeout=10 },
                        }
                    },
                })

                if err then ngx.say(err) end
                ngx.sleep(1)
                ok, err = checkups.ready_ok("ups1", callback)
                if err then ngx.say(err) end
                ok, err = checkups.ready_ok("ups1", callback)
                if err then ngx.say(err) end

                -- 将服务器添加到新的上游
                ok, err = checkups.update_upstream("ups2", {
                        {
                            servers = {
                                { host="127.0.0.1", port=12351 },
                            }
                        },
                    })
                if err then ngx.say(err) end
                ngx.sleep(1)
                ok, err = checkups.ready_ok("ups2", callback)
                if err then ngx.say(err) end

                -- 将服务器添加到 ups2,重置轮询状态
                ok, err = checkups.update_upstream("ups2", {
                        {
                            servers = {
                                { host = "127.0.0.1", port = 12350, weight=10, max_fails=3, fail_timeout=10 },
                                { host = "127.0.0.1", port = 12351, weight=10, max_fails=3, fail_timeout=10 },
                            }
                        },
                    })
                if err then ngx.say(err) end
                ngx.sleep(1)
                ok, err = checkups.ready_ok("ups2", callback)
                if err then ngx.say(err) end
                ok, err = checkups.ready_ok("ups2", callback)
                if err then ngx.say(err) end
            }
        }
    }

上述定义的 /t 位置的典型输出为:

no servers available
12350
12350
12351
12350
12351

配置

Lua 配置

checkups 的配置文件是一个 lua 模块,由两部分组成:全局部分和集群部分。

以下是 checkups 的示例配置文件,

    -- config.lua

    -- 这是全局部分

    _M = {}

    _M.global = {
        checkup_timer_interval = 15,
        checkup_timer_overtime = 60,
        default_heartbeat_enable = true,
        checkup_shd_sync_enable = true,
        shd_config_timer_interval = 1,
    }

    -- 剩余部分是集群配置

    _M.redis = {
        enable = true,
        typ = "redis",
        timeout = 2,
        read_timeout = 15,
        send_timeout = 15,

        protected = true,

        cluster = {
            {   -- 级别 1
                    try = 2,
                servers = {
                    { host = "192.168.0.1", port = 6379, weight=10, max_fails=3, fail_timeout=10 },
                    { host = "192.168.0.2", port = 6379, weight=10, max_fails=3, fail_timeout=10 },
                }
            },
            {   -- 级别 2
                servers = {
                    { host = "192.168.0.3", port = 6379, weight=10, max_fails=3, fail_timeout=10 },
                }
            },
        },
    }

    _M.api = {
        enable = false,
        typ = "http",
            http_opts = {
            query = "GET /status HTTP/1.1\r\nHost: localhost\r\n\r\n",
            statuses = {
                    ["500"] = false,
                    ["502"] = false,
                    ["503"] = false,
                    ["504"] = false,
            },
        },

        mode = "hash",

        cluster = {
            dc1 = {
                servers = {
                    { host = "192.168.1.1", port = 1234, weight=10, max_fails=3, fail_timeout=10 },
                }
            },
            dc2 = {
                servers = {
                    { host = "192.168.1.2", port = 1234, weight=10, max_fails=3, fail_timeout=10 },
                }
            }
        }
    }

    _M.ups_from_nginx = {
        timeout = 2,

        cluster = {
            {   -- 级别 1
                upstream = "api.com",
            },
            {   -- 级别 2
                upstream = "api.com",
                upstream_only_backup = true,
            },
        },
    }

    return _M

全局配置

  • checkup_timer_interval: 向后端服务器发送心跳的间隔。默认值为 5
  • checkup_timer_overtime: 检查超时的间隔。在大多数情况下,您不需要更改此值。默认值为 60
  • default_heartbeat_enable: 检查是否默认向服务器发送心跳。默认值为 true
  • checkup_shd_sync_enable: 为每个工作进程创建上游同步器。如果设置为 false,动态上游将无法正常工作。默认值为 true
  • shd_config_timer_interval: 从共享内存同步上游列表的间隔。默认值等于 checkup_timer_interval
  • ups_status_sync_enable: 如果设置为 true,检查将从检查同步上游状态到 Nginx 上游块。默认值为 false
  • ups_status_timer_interval: 从检查同步上游状态到 Nginx 上游块的间隔。

集群配置

  • skey: _M.xxxxxxxxxx 是此集群的 skey(服务键)。
  • enable: 启用或禁用向服务器发送心跳。默认值为 true
  • typ: 集群类型,必须是 generalredismysqlhttp 之一。默认值为 general
    • general: 通过 TCP sock:connect 发送心跳。
    • redis: 通过 redis PING 发送心跳。需要 lua-resty-redis 模块。
    • mysql: 通过 mysql db:connect 发送心跳。需要 lua-resty-mysql 模块。
    • http: 通过 HTTP 请求发送心跳。您可以在 http_opts 中设置自定义 HTTP 请求和响应代码。
  • timeout: 连接到上游服务器的超时时间。默认值为 5
  • read_timeout: 读取上游服务器的超时时间(在发送心跳时不使用)。默认值等于 timeout
  • send_timeout: 向上游服务器写入的超时时间(在发送心跳时不使用)。默认值等于 timeout
  • http_opts: HTTP 心跳配置。仅适用于 typ="http"

    • query: 发送心跳的 HTTP 请求。
    • statuses: 如果服务器返回的代码设置为 false,则该服务器被视为故障。
  • mode: 负载均衡模式。可以设置为 hashurl_haship_hash。检查将通过 hash_keyngx.var.uringx.var.remote_addr 进行服务器负载均衡。默认值为 wrr

  • protected: 如果设置为 true 且集群中的所有服务器都失败,检查将不会将最后一个失败的服务器标记为不可用(err),而是标记为 unstable(在下次尝试中仍然可用)。默认值为 true
  • cluster: 您可以根据集群优先级配置多个级别,在每个级别中可以配置一个 servers 集群。检查将仅在前一个级别的所有服务器被视为不可用时尝试下一个级别。

    除了按级别尝试集群,您还可以配置检查按键尝试集群(请参见上面的 api 集群)。请记住,您还应该传递额外的参数,如 opts.cluster_key={"dc1", "dc2"}opts.cluster_key={3, 1, 2}checkups.read_ok,以使检查按 dc1dc2level 3level 1level 2 的顺序进行尝试。如果您没有将 opts.cluster_key 传递给 checkups.ready_ok,检查仍将按级别尝试集群。至于上面的 api 集群,检查最终将返回 no servers available。 * try: 重试次数。默认值为服务器数量。 * try_timeout: 限制请求响应的时间,与 nginx 的 proxy_next_upstream_timeout 类似。 * servers: servers 的配置如下所示, * weight: 设置服务器的权重。默认值为 1。 * max_fails: 设置在 fail_timeout 参数设置的持续时间内与服务器通信的失败尝试次数。默认情况下,失败尝试次数设置为 0,这会禁用尝试的计数。什么被视为失败尝试由 http_opts.statuses 定义(如果 typ="http")或 checkups.ready_ok 返回的 nil/false。此选项仅在轮询中可用。 * fail_timeout: 设置在考虑服务器不可用的情况下,进行指定数量的失败尝试的时间,以及服务器将被视为不可用的时间段。默认情况下,该参数设置为 10 秒。此选项仅在轮询中可用。

    • upstream: Nginx 上游块的名称。检查将从 Nginx 配置的上游块中提取服务器,使用 prepare_checker。需要 lua-upstream-nginx-module 模块。
    • upstream_only_backup: 如果设置为 true,检查将仅从 Nginx 上游块中提取备份服务器。

Nginx 配置

将 lua 配置文件和 checkups 的路径添加到 lua_package_path,并创建 checkups 使用的 lua 共享字典。您应该将这些行放入 Nginx 配置文件的 http 块中。

lua_shared_dict state 10m;
lua_shared_dict mutex 1m;
lua_shared_dict locks 1m;
lua_shared_dict config 10m;

如果您使用流子系统,您应该将这些行放入 Nginx 配置文件的 stream 块中。

lua_shared_dict stream_state 10m;
lua_shared_dict stream_mutex 1m;
lua_shared_dict stream_locks 1m;
lua_shared_dict stream_config 10m;

API

init

语法: init(config)

阶段: init_by_lua

将上游从 config.lua 复制到共享字典,从 NGINX 上游块中提取服务器并进行一些基本初始化。

prepare_checker

语法: prepare_checker(config)

阶段: init_worker_by_lua

将配置从 config.lua 复制到工作进程的检查中,从 NGINX 上游块中提取服务器并进行一些基本初始化。

create_checker

语法: create_checker()

阶段: init_worker_by_lua

创建心跳定时器和上游同步定时器。所有工作进程中只会创建一个心跳定时器。强烈建议在 init_worker 阶段调用此方法。

ready_ok

语法: res, err = ready_ok(skey, callback, opts?)

阶段: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*

从集群 skey 中选择一个可用的 peer 并调用 callback(peer.host, peer.port, opts)

opts 表接受以下字段,

  • cluster_key: 按 cluster_key 尝试集群。检查将按 cluster_key 的顺序尝试集群。clusters_key 可以是集群的名称或集群的级别。集群示例: {"cluster_name_A", "name_B", "name_C"}。级别示例: {3, 2, 1}
  • hash_key: 在 hash 负载均衡模式中使用的键。如果未设置,将使用 ngx.var.uri
  • try: 重试次数不得超过 try 次。
  • try_timeout: 限制请求响应的时间,与 nginx 的 proxy_next_upstream_timeout 类似。

成功时返回 callback 返回的内容,或者返回 nil 和描述错误的字符串。

如果 callback 返回 nilfalse,检查将认为这是一次失败的尝试,并将使用另一个 peer 重试 callback。因此,请始终记住在成功的回调后不要返回 nilfalse

select_peer

语法: peer, err = select_peer(skey)

上下文: rewrite_by_lua*, access_by_lua*, content_by_lua*, balancer_by_lua

从集群 skey 中选择一个可用的 peer。

返回一个包含可用 peer 的 hostport 的表。

如果发生错误,则返回 nil 和描述错误的字符串。

get_status

语法: status = get_status()

阶段: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*

json 格式返回检查状态。

get_ups_timeout

语法: connect_timeout, send_timeout, read_timeout = get_ups_timeout(skey)

阶段: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*

返回集群 skey 的超时时间。

feedback_status

语法: ok, err = feedback_status(skey, host, port, failed)

上下文: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, balancer_by_lua.*

将集群 skey 中的服务器 host:port 标记为失败(true)或可用(false)。

成功时返回 1,否则返回 nil 和描述错误的字符串。

update_upstream

语法: ok, err = update_upstream(skey, upstream)

阶段: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*

更新集群 skeyupstream 的格式与 config.lua 中的 cluster 相同。

成功时返回 true,否则返回 false 和描述错误的字符串。

delete_upstream

语法: ok, err = delete_upstream(skey)

阶段: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*

从上游列表中删除集群 skey

成功时返回 true,否则返回 false 和描述错误的字符串。

另见

GitHub

您可以在 nginx-module-checkups 的 GitHub 仓库中找到此模块的其他配置提示和文档。