durpina

具有良好 API 的动态上游反向代理

$ opm get slact/durpina

Durpina

具有良好 API 的动态上游反向代理

一个极其灵活、易于使用的动态 Nginx/OpenResty 上游模块,基于 toruneko 的 lua-resty-upstream。

可配置和可脚本化的负载均衡、服务器健康检查、向上下游添加和删除服务器等等。您无需研究 API 即可使用它,也无需成为 Lua 专家即可编写脚本。

安装

安装 OpenResty,然后使用 opm 工具安装 durpina

    opm install slact/durpina

示例配置

    #-- nginx.conf:
    http {
      lua_shared_dict upstream    1m; #-- shared memory to be used by durpina. 1mb should be neough
      lua_socket_log_errors       off; #-- don't clutter the error log when upstream severs fail
      
      upstream foo {
        server localhost:8080; #--default weight is 1
        server host1:8080 weight=5;
        server host2:8080 weight=7;
        balancer_by_lua_block {
          --load balance this upstream in round-robin mode
          require "durpina.balancer" "round-robin"
          --note: the above line is Lua syntax sugar equivalent to
          -- require("durpina.balancer")("round-robin")
        }
      }
      
      #-- 
      upstream bar {
        server 0.0.0.0; #-- nginx config syntax needs at least 1 server.
        #-- the address 0.0.0.0 is treated by Durpina as a placeholder and is ignored
        balancer_by_lua_block {
          require "durpina.balancer" "ip-hash"
        }
      }
      
      init_worker_by_lua_block {
        local Upstream = require "durpina.upstream"
        
        --Use the "upstream" lua_shared_dict declared above
        --setting the resolver is required for upstream server DNS resolution
        Upstream.init("upstream", {resolver="8.8.8.8"})
    
        local upfoo = Upstream.get("foo")
        --add a health check to the upstream
        upfoo:add_monitor("http", {uri="/still_alive"})
        
        local upbar = Upstream.get("bar")
        --this is an upstream with no servers
        
        --peers can be added anytime
        
        upbar:add_peer("localhost:8080 weight=1") --hostnames are resolved once when added, just like Nginx would do
        upbar:add_peer({host="10.0.0.2", port=8090, weight=7, fail_timeout=10}) --can be added as a table, too
        
        upbar:add_monitor("tcp", {port=10000}) -- check if able to make tcp connection to server on port 10000
      }
      
      server {
        #-- here's where we make use of the upstream
        listen 80;
        location /foo {
          proxy_pass http://foo;
        }
        location /bar {
          proxy_pass http://bar;
        }
      }
      
      server {
        #-- upstream info and management
        
        listen 8080;
        #-- POST /set_upstream_peer_weight/upstream_name/peer_name
        #-- request body is the peer's new weight
        location ~/set_upstream_peer_weight/foo/(.*)/(\d+) {
          content_by_lua_block {
            local Upstream = require "durpina.upstream"
            local up = Upstream.get("foo")
            
            local peername = ngx.var[1]
            local weight = tonumber(ngx.var[2])
            local peer = up:get_peer(peername)
            if peer and weight then
              peer:set_weight(weight)
              ngx.say("weight set!")
            else
              ngx.status = 404
              ngx.say("peer not found or weight invalid")
            end
          }
        }
      }
    }

API

上游

      Upstream = require "durpina.upstream"

Upstream.init(shdict_name, options)

      init_worker_by_lua_block {
        Upstream.init("myshdict", {resolver="8.8.8.8"})
      }

初始化 Durpina 以使用名为 shdict_name`lua_shared_dict`此调用是必需的,在执行其他任何操作之前,并且必须存在于 `init_worker_by_lua` 字符串、块或文件中。对于大多数设置,大小为 1m 的块就足够了。

options 参数支持以下参数

  • resolver:一个字符串或字符串数组,用作 DNS 解析的名称服务器。如果需要在 Nginx 启动后解析服务器主机名,则需要此参数。

Upstream.get(upstream_name)

      local upstream = Upstream.get("foo")

返回名为 upstream_name 的上游,其节点根据相应 上游 块的内容初始化。标记为 backup 或地址为 0.0.0.0 的上游节点将被忽略。

upstream.name

此上游的名称。

upstream:get_peer(peer_name)

      local peer = upstream:get_peer("localhost:8080")

返回名称为 peer_name节点,如果此上游中不存在这样的 节点,则返回 nil。

upstream:add_peer(peer_config)

      local peer, err = upstream:add_peer("localhost:8080 fail_timeout=15 weight=7")
      local peer, err = upstream:add_peer({name="localhost:8080", fail_timeout=15, weight=7})
      local peer, err = upstream:add_peer({host="localhost", port=8080, fail_timeout=15, weight=7})

将节点添加到上游。peer_config 参数可以是格式化为 `server` 上游指令的字符串,也可以是具有以下键的 Lua 表格:name(“host:port”)、hostportfail_timeoutweight。表格中必须存在 namehost

上游块中不允许两个节点具有相同的名称。

返回新添加的 节点nil, error

upstream:remove_peer(peer)

      local peer = upstream:get_peer("localhost:8080")
      loal ok, err = upstream:remove_peer(peer)

从上游中删除 节点

upstream:get_peers(selector)

      local peers = upstream:get_peers("all")

返回与 selector 匹配的 节点 数组,selector 可以是以下之一:nil(与 all 相同)、"all""failing""down""temporary_down""permanent_down"

upstream:add_monitor(name, opts)

      local ok, err = upstream:add_monitor("http", {url="/health_check"})

向上下游添加 `monitor`。监控器定期检查每个节点的健康状况,并在“监控器”部分中详细讨论。

upstream:info()

      print(upstream:info())


      /* output */
      {
        "name":"weighted_roundrobin",
        "revision":2,
        "peers":[{
            "name":"localhost:8083",
            "address":"127.0.0.1",
            "weight":1,
            "state":"up"
          },{
            "name":"127.0.0.1:8084",
            "address":"127.0.0.1",
            "weight":10,
            "state":"failing"
          },{
            "name":"127.0.0.1:8085",
            "address":"127.0.0.1",
            "weight":15,
            "state":"down"
          }],
        "monitors":[{
            "id":"http",
            "name":"http"
          }]
      }
    

返回包含有关此上游状态信息的 JSON 字符串。

节点

节点是 上游 中的服务器。它们是在内部初始化的——尽管有一个 Peer.new 方法,但您确实不应该使用它。相反,节点是使用 `upstream:add_peer()` 创建的,并且通过从 上游块 加载创建的。

      local peer = upstream:get_peer("127.0.0.1")

peer.name

节点的名称,格式为 "hostname:port"

peer.port

端口,显然。

peer.initial_weight

节点最初加载的权重,未受后续对 `peer:set_weight(n)` 的调用的修改

peer:get_address()

      local address, err = peer:get_address()

如果节点已解析,则返回节点地址。如果地址不可用或 DNS 解析失败,则返回 nil, err

peer:get_weight()

      local weight = peer:get_weight()

返回节点的当前权重。

peer:set_weight(weight)

      local ok, err = peer:set_weight(15)

为所有 Nginx 工作进程设置节点的当前权重。权重必须是正整数。

peer:get_upstream()

      local upstream = peer:get_upstream()

返回此节点的 `upstream`

peer:set_state(state)

      peer:set_state("down")

设置节点的状态,在所有 Nginx 工作进程之间共享。可以是 updowntemporary_down 之一

peer:is_down(kind)

如果节点已关闭,则返回 true。参数 kind 可以是 nil"any""permanent""temporary" 之一,并反映节点所处的关闭状态类型。kind 的默认值为 "any"

peer:is_failing()

如果节点当前正在失败,则返回 true;也就是说,如果它在过去 fail_timeout 时间间隔内记录了不止一次失败。

peer:add_fail()

将节点的故障计数器加 1。此计数器在所有 Nginx 工作进程之间共享。

peer:resolve(force)

如有必要,将节点主机名解析为其地址。如果 force 为 true,则覆盖现有的地址(如果存在)。与其他 peer 更新一样,新解析的地址会自动在 Nginx 工作进程之间共享。

为了使节点 DNS 解析工作,必须向 Upstream.init() 提供 resolver

均衡器

      require "durpina.balancer"

均衡器是在 上游块 中使用 `balancer_by_lua` 块调用的

      upstream foo {
        localhost:8080 weight=2;
        localhost:8081;
        balancer_by_lua_block {
          require "durpina.balancer" "round-robin"
          --this is syntactic sugar equivalent to
          -- require("durpina.balancer").balance("round-robin")
        }
      }

Balancer(algorithm, args...)

Balancer.balance(algorithm, args...)

      Balancer.balance(algorithm)

使用指定的 algorithm 平衡上游,支持以下算法

  • "round-robin"(加权)

  • "unweighted-round-robin"

  • "ip-hash",基于源 IP 的一致路由

  • "consistent-hash",基于自定义请求变量的一致路由

args... 参数将直接传递给均衡器。目前只有 consistent-hash 算法需要参数,即要散列的值

    balancer_by_lua_block {
      --load-balance by the first regex capture in the request url
      require "durpina.balancer" ("consistent-hash", ngx.var[1])
    }

监控器

      upstream:add_monitor(name, opts)

监控器添加到上游以检查节点的健康状态并运行定期维护任务。监控器不会直接初始化,而是通过 `upstream:add_monitor()` 调用添加。

监控器 name 标识正在添加的监控器类型。"多个监控器" 已包含在内,更多监控器可以使用 `Monitor.register()` 添加。

每个新监控器都将传递 opts 选项表。此表只能包含数字或字符串值。所有监控器都处理 optsid,该键唯一标识上游中的监控器。在不存在时,id 默认为监控器 name。因此,要拥有多个 http 监控器,至少必须为其中一个提供 id

      upstream:add_monitor("http") --pings the root url
      upstream:add_monitor("http", {id="http_hello", url="/hello", interval=30})

总共,以下 opts 由所有监控器使用

  • id**: 唯一标识监控器。默认值:监控器名称

  • interval**: 每次检查之间的时间。在每个间隔结束时检查一个节点,在所有 Nginx 工作进程之间分配。可以是数字或 Nginx 时间字符串(“10s”、“30m”等)。默认值:Monitor.default_interval(5 秒)

  • port**: 通过连接到此端口而不是节点的上游端口来执行监控器检查。

  • peers**: 要检查的节点类型。可以是 `upstream:get_peers()` 中的选择器之一。默认值:"all"

预定义监控器

http

发送 HTTP 请求,如果请求失败则添加故障。

      upstream:add_monitor("http", {id="http-hello", url="/hello", ok_codes="2xx", interval="5m"})

opts:

  • url**: /path/to/request 默认值:"/"

  • ok_codes**: 被视为“ok”的响应代码。包含代码数字和“Nxx”表示法的空格分隔字符串。默认值:"101 102 2xx 3xx"

  • header_***: 以“header_”为前缀的所有 opts 成为请求头

  • method**: 请求方法。默认值:"GET"

  • body**: 请求正文。默认值:nil

tcp

尝试通过 TCP 套接字连接到服务器,如果连接失败则添加故障。

      upstream:add_monitor("tcp", {id="tcp-ping", timeout=200})

opts:

haproxy-agent-check

尝试通过 TCP 连接到节点并读取一行文本。数据根据 HAProxy agent-check 规范进行处理。状态“drain”和“maint”被视为“down”,而“up”和“ready”都被视为“up”。

      upstream:add_monitor("haproxy-agent-check", {timeout=200})

opts**:

http-haproxy-agent-check

haproxy-agent-check 相同,但通过 HTTP。

      upstream:add_monitor("haproxy-agent-check", {url="/haproxy_agent_status"})

opts:

  • url**: /path/to/request 默认值:"/"

  • ok_codes**: 被视为“ok”的响应代码。包含代码数字和“Nxx”表示法的空格分隔字符串。默认值:“101 102 2xx 3xx”

  • header_***: 以“header_”为前缀的所有 opts 成为请求头

注册新监控器

新的监控器使用 Monitor.register 添加

Monitor.register(name, check)

      Monitor.register("fancy_monitor", check_function)
      -- or --
      Monitor.register("fancy_monitor", check_table)

按名称注册一个监控器,以便稍后添加到上游。Check 可以是表格或函数

      init_worker_by_lua_block {
        -- register as a table
        Monitor.register("mymonitor", {
          init = initialization_function, -- (optional)
          check = peer_checking_function, --(required)
          interval = default interval for this monitor --(optional)
        }
        
        --register as a function
        Monitor.register("mymonitor", peer_checking_function)
          -- is equivalent to --
        Monitor.register("mymonitor", {
          check = peer_checking_function
        })
      }

monitor check_table.init

每次将监控器添加到上游时都会调用 init 函数。它负责初始化监控器状态并验证 opts。它的签名如下

      function init_monitor(upstream, shared, local_state)

参数如下

  • upstream** 此监控器正在添加到其中的 上游

  • shared** 是一个 openresty 共享字典,命名空间为此监控器实例。

  • local_state** 是一个工作进程本地表格,用于跟踪执行状态、缓存和配置。它初始化为传递给 upstream:add_monitor()opts 表格的副本

monitor check_table.check

在配置的间隔内,每次对后续的每个节点都会调用 check 函数。它负责使用 `peer:set_state()` 和其他 `peer` 函数更改节点状态。它的签名如下

      function check_monitor(upstream, peer, shdict, local_state)

参数如下

  • upstream** 此监控器正在添加到其中的 上游

  • peer** 需要检查的 节点

  • shared** 是一个 openresty 共享字典,命名空间为此监控器实例。

  • local_state** 是一个工作进程本地表格,用于跟踪执行状态、缓存和配置。它初始化为传递给 upstream:add_monitor()opts 表格的副本

有关如何创建监控器的更多详细信息将在以后添加。

POD 错误

嗨!以上文档存在一些编码错误,如下所述:

大约在第 605 行

未终止的 B<...> 序列

大约在第 610 行

未终止的 B<...> 序列

大约在第 617 行

未终止的 B<...> 序列

大约在第 622 行

未终止的 B<...> 序列

大约在第 649 行

未终止的 B<...> 序列

大约在第 654 行

未终止的 B<...> 序列

大约在第 659 行

未终止的 B<...> 序列

大约在第 663 行

未终止的 B<...> 序列

大约在第 668 行

未终止的 B<...> 序列

大约在第 690 行

未终止的 B<...> 序列

大约在第 709 行

未终止的 B<...> 序列

大约在第 714 行

未终止的 B<...> 序列

大约在第 736 行

未终止的 B<...> 序列

大约在第 741 行

未终止的 B<...> 序列

大约在第 746 行

未终止的 B<...> 序列

大约在第 798 行

未终止的 B<...> 序列

大约在第 802 行

未终止的 B<...> 序列

大约在第 806 行

未终止的 B<...> 序列

大约在第 825 行

未终止的 B<...> 序列

大约在第 829 行

未终止的 B<...> 序列

大约在第 833 行

未终止的 B<...> 序列

大约在第 837 行

未终止的 B<...> 序列

作者

slact

许可证

mit

依赖项

版本