测试版本:ingress-nginx-controller:v1.7.0-2

开启access_by_lua_block,让acess阶段执行插件(适用于优先级分流)

kubectl exec -it  -n ingress-nginx ingress-nginx-controller-qdwws    -- cat template/nginx.tmpl > nginx.tmpl

修改nginx.tmpl,下列access_by_lua_block部分是被注释掉的,去掉注释,增加plugins.run()

location {{ $path }} {
    ...
            # be careful with `access_by_lua_block` and `satisfy any` directives as satisfy any
            # will always succeed when there's `access_by_lua_block` that does not have any lua code doing `ngx.exit(ngx.DECLINED)`
            # other authentication method such as basic auth or external auth useless - all requests will be allowed.
            access_by_lua_block {
                plugins.run()
            }

将nginx.tmpl文件放到configmap中

kubectl create cm -n ingress-nginx nginx-custom-template-config --from-file nginx.tmpl

修改ingress-nginx-controller的ds,增加nginx-configmap的挂载,保存如下文件为ingress-volume-patch.yaml后执行kubectl patch -n ingress-nginx daemonsets.apps ingress-nginx-controller --patch-file ingress-volume-patch.yaml

spec:
  template:
    spec:
      containers:
      - name: ingress-nginx-controller
        volumeMounts:
        - name: nginx-custom-template
          mountPath: /etc/nginx/template/nginx.tmpl
          subPath: nginx.tmpl
      volumes:
      - name: nginx-custom-template
        configMap:
          name: nginx-custom-template-config

增加自定义插件并启用

增加插件(插件中代码还需要调试优化)

创建如下ConfigMap,必须为main.lua,通过定义并返回_M来导出函数

脚本说明:

apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-plugins
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/component: controller
data:
  main.lua: |
    local ngx = ngx
    local cjson = require "cjson"
    local _M = {}
    local CONFIG_TTL = 30

    -- 读取配置
    local function get_config()
      local CONFIG_PATH = "/etc/nginx/conf.d/conn_limit_config.json"
      local config_cache = ngx.shared.config_cache
      local cached = config_cache:get("config")
      if cached then
          return cjson.decode(cached)
      end
      local file = io.open(CONFIG_PATH, "r")
      if not file then
          ngx.log(ngx.ERR, "无法读取配置文件: ", CONFIG_PATH)
          ngx.exit(500)
      end
      local content = file:read("*a")
      file:close()
      local config = cjson.decode(content)
      config_cache:set("config", content, CONFIG_TTL)
      return config
    end

    -- 判断域名是否在列表中
    local function is_in_list(domain, list)
      for _, d in ipairs(list) do
          if d == domain then return true end
      end
      return false
    end

    -- 清理计数
    local function cleanup(is_low, host)
      local low_conns = ngx.shared.low_conns
      local global_conns = ngx.shared.global_conns
      local new_global = global_conns:incr("total", -1, 0) or 0
      local new_low = is_low and (low_conns:incr("count", -1, 0) or 0) or 0
      ngx.log(ngx.INFO, "[结束阶段计数] Host: ", host or "",
              " | 全局连接=", new_global,
              " | 低优连接=", new_low)
    end

    -- access 阶段:连接数统计与限流
    function _M.access()
      ngx.log(ngx.INFO, "[conn-limit-TEST] access 阶段执行!")  -- 新增测试日志
      local low_conns = ngx.shared.low_conns
      local global_conns = ngx.shared.global_conns
      local config = get_config()
      local host = ngx.var.host or ""
      local is_low = is_in_list(host, config.low_priority.domains or {})

      local global_threshold = config.global_threshold or 1
      local low_max = config.low_priority.max_conns or 1

      -- 增加计数
      local current_global = global_conns:incr("total", 1, 0)
      local current_low = is_low and low_conns:incr("count", 1, 0) or 0

      ngx.log(ngx.INFO, "[访问阶段计数] Host: ", host,
              " | 全局连接=", current_global,
              " | 低优连接=", current_low)

      -- 限制低优请求
      if current_global >= global_threshold and is_low and current_low > low_max then
          ngx.log(ngx.WARN, "[拒绝请求] Host=", host,
                  " | 全局=", current_global,
                  " | 低优=", current_low)
          cleanup(is_low, host)
          ngx.status = 503
          ngx.say("Service busy (low-prio quota exceeded: ", current_low, "/", low_max, ")")
          return ngx.exit(503)
      end

      -- 保存上下文
      ngx.ctx.host = host
      ngx.ctx.is_low = is_low
    end

    -- log 阶段:连接数释放
    function _M.log()
      local host = ngx.ctx.host
      local is_low = ngx.ctx.is_low
      if host then
          cleanup(is_low, host)
      end
    end
    -- 客户端中断处理
    function on_abort_phase()
      local host = ngx.ctx.host
      local is_low = ngx.ctx.is_low
      if host then
          ngx.log(ngx.WARN, "[客户端中断] Host=", host)
          cleanup(is_low, host)
      end
    end
    return _M

挂载到ingress-nginx-controller的ds中,保存如下文件为ingress-plugins-patch.yaml后执行kubectl patch -n ingress-nginx daemonsets.apps ingress-nginx-controller --patch-file ingress-plugins-patch.yaml

spec:
  template:
    spec:
      containers:
      - name: ingress-nginx-controller
        volumeMounts:
        - name: plugin-volume
          mountPath: /etc/nginx/lua/plugins/dynamic_conn_limit/main.lua ##必须在/etc/nginx/lua/plugins/下面,目录为插件名称,文件为main.lua
          subPath: main.lua
      volumes:
      - name: plugin-volume
        configMap:
          name: ingress-nginx-plugins

启用插件

修改ingress-nginx-controller的配置kubectl edit cm -n ingress-nginx ingress-nginx增加字段plugins: dynamic_conn_limit以及lua-shared-dicts: global_conns:10m,low_conns:10m,config_cache:10m增加共享内存

配置文件

创建如下ConfigMap,配置全局连接数为8000,nginx1.org为高优域名,nginx.org为低优域名,低优域名最大连接数为3000,实现

apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-conn-config
  namespace: ingress-nginx
data:
  conn_limit_config.json: |
    {
      "global_threshold": 8000,
      "high_priority_domains": ["nginx1.org"],
      "low_priority": {
        "domains": ["nginx.org"],
        "max_conns": 3000
      }
    }

注解中插入lua脚本即可在access阶段执行下面脚本(不适用优先级分流)

     nginx.ingress.kubernetes.io/server-snippet: |
      access_by_lua_file /etc/nginx/lua/scripts/dynamic-conn-limit.lua;

手动将脚本挂载进去即可,具体步骤参考文档上面,此种方法只能在access阶段执行,当连接释放时,存储的连接数变量无法减少。

存在问题

不知道是版本过低还是ingress-nginx-controller的bug,配置完成之后经常出现插件或者lua脚本注入执行不生效的问题,任何日志没打印,导致脚本的功能无法生效。但是脚本中存在错误的时候加载会报错,当多次者修改配置再还原,重启等操作之后,插件或者lua脚本功能可能会生效。