• Nginx负载均衡之动态更新upstream


            Nginx 的配置是启动时一次性加载到内存中的,在实际的使用中,对 Nginx 服务器上游服务器组中节点的添加或移除仍需要重启或热加载 Nginx 进程。在 Nginx 的商业版本中,提供了 ngx_http_api_module 模块,可以通过 API 动态添加或移除上游服务器组中的节点。
            对于 Nginx 开源版本,通过 Nginx 的扩展版 OpenResty 及 Lua 脚本也可以实现上游服务器组中节点的动态操作,这里只使用 OpenResty 的 lua-upstream-nginx-module 模块简单演示节点的上下线状态动态修改的操作。该模块提供了 set_peer_down 指令,该指令可以对 upstream 的节点实现上下线的控制。
            由于该指令只支持 worker 级别的操作,为使得 Nginx 的所有 worker 都生效,此处通过编写 Lua 脚本与 lua-resty-upstream-healthcheck 模块做了简单的集成,利用 lua-resty-upstream-healthcheck 模块的共享内存机制将节点状态同步给其他工作进程,实现对 upstream 的节点状态的控制。
            首先在 OpenResty 的 lualib 目录下创建公用函数文件 api_func.lua,lualib/api_func.lua 内容如下:

    1. local _M = { _VERSION = '1.0' }
    2. local cjson = require "cjson"
    3. local upstream = require "ngx.upstream"
    4. local get_servers = upstream.get_servers
    5. local get_primary_peers = upstream.get_primary_peers
    6. local set_peer_down = upstream.set_peer_down
    7. # 分割字符串为table
    8. local function split( str,reps )
    9. local resultStrList = {}
    10. string.gsub(str,"[^"..reps.."]+",function ( w )
    11. table.insert(resultStrList,w)
    12. end)
    13. return resultStrList
    14. end
    15. # 获取server列表
    16. local function get_args_srv( args )
    17. if not args["server"] then
    18. ngx.say("failed to get post args: ", err)
    19. return nil
    20. else
    21. if type(args["server"]) ~= "table" then
    22. server_list=split(args["server"],",")
    23. else
    24. server_list=args["server"]
    25. end
    26. end
    27. return server_list
    28. end
    29. # 获取节点在upstream中的顺序
    30. local function get_peer_id(ups,server_name)
    31. local srvs = get_servers(ups)
    32. for i, srv in ipairs(srvs) do
    33. -- ngx.print(srv["name"])
    34. if srv["name"] == server_name then
    35. target_srv = srv
    36. target_srv["id"] = i-1
    37. break
    38. end
    39. end
    40. return target_srv["id"]
    41. end
    42. # 获取节点共享内存key
    43. local function gen_peer_key(prefix, u, is_backup, id)
    44. if is_backup then
    45. return prefix .. u .. ":b" .. id
    46. end
    47. return prefix .. u .. ":p" .. id
    48. end
    49. # 设置节点状态
    50. local function set_peer_down_globally(ups, is_backup, id, value,zone_define)
    51. local u = ups
    52. local dict = zone_define
    53. local ok, err = set_peer_down(u, is_backup, id, value)
    54. if not ok then
    55. ngx.say(cjson.encode({code = "E002", msg = "failed to set peer down", data = err}))
    56. end
    57. local key = gen_peer_key("d:", u, is_backup, id)
    58. local ok, err = dict:set(key, value)
    59. if not ok then
    60. ngx.say(cjson.encode({code = "E003", msg = "failed to set peer down state", data = err}))
    61. end
    62. end
    63. # 获取指定upstream的节点列表
    64. function _M.list_server(ups)
    65. local srvs, err = get_servers(ups)
    66. ngx.say(cjson.encode(srvs))
    67. end
    68. # 设置节点状态
    69. function _M.set_server(ups,args,status,backup,zone_define)
    70. local server_list = get_args_srv(args)
    71. if server_list == nil then
    72. ngx.say(cjson.encode({code = "E001", msg = "no args",data = server_list}))
    73. return nil
    74. end
    75. for _, s in pairs(server_list) do
    76. local peer_id = get_peer_id(ups,s)
    77. if status then
    78. local key = gen_peer_key("nok:", ups, backup, peer_id)
    79. local ok, err = zone_define:set(key, 1)
    80. set_peer_down_globally(ups, backup, peer_id, true,zone_define)
    81. else
    82. local key = gen_peer_key("ok:", ups, backup, peer_id)
    83. local ok, err = zone_define:set(key, 0)
    84. set_peer_down_globally(ups, backup, peer_id, nil,zone_define)
    85. end
    86. end
    87. ngx.say(cjson.encode({code = "D002", msg = "set peer is success",data = server_list}))
    88. end
    89. return _M

            Nginx 配置文件 status.conf 的内容如下:

    1. # 关闭socket错误日志
    2. lua_socket_log_errors off;
    3. # 设置共享内存名称及大小
    4. lua_shared_dict _healthcheck_zone 10m;
    5. init_worker_by_lua_block {
    6. local hc = require "resty.upstream.healthcheck"
    7. # 设置需要健康监测的upstream
    8. local ups = {"foo.com","sslback"}
    9. # 遍历ups,绑定健康监测策略
    10. for k, v in pairs(ups) do
    11. local ok, err = hc.spawn_checker{
    12. shm = "_healthcheck_zone", # 绑定lua_shared_dict定义的共享内存
    13. upstream = v, # 绑定upstream指令域
    14. type = "http",
    15. http_req = "GET / HTTP/1.0\r\nHost: foo.com\r\n\r\n",
    16. # 用以检测的raw格式http请求
    17. interval = 2000, # 每2s检测一次
    18. timeout = 1000, # 检测请求超时时间为1s
    19. fall = 3, # 连续失败3次,被检测节点被置为DOWN状态
    20. rise = 2, # 连续成功2次,被检测节点被置为UP状态
    21. # 当健康检测请求返回的响应码为200302时,被认
    22. # 为检测通过
    23. valid_statuses = {200, 302},
    24. concurrency = 10, # 健康检测请求的并发数为10
    25. }
    26. if not ok then
    27. ngx.log(ngx.ERR, "failed to spawn health checker: ", err)
    28. return
    29. end
    30. end
    31. }
    32. upstream foo.com {
    33. server 192.168.2.145:8080;
    34. server 192.168.2.109:8080;
    35. server 127.0.0.1:12356 backup;
    36. }
    37. upstream sslback {
    38. server 192.168.2.145:443;
    39. server 192.168.2.159:443;
    40. }
    41. server {
    42. listen 18080;
    43. access_log off;
    44. error_log off;
    45. # 健康检测状态页
    46. location = /healthcheck {
    47. access_log off;
    48. allow 127.0.0.1;
    49. allow 192.168.2.0/24;
    50. allow 192.168.101.0/24;
    51. deny all;
    52. default_type text/plain;
    53. content_by_lua_block {
    54. local hc = require "resty.upstream.healthcheck"
    55. ngx.say("Nginx Worker PID: ", ngx.worker.pid())
    56. ngx.print(hc.status_page())
    57. }
    58. }
    59. location = /ups_api {
    60. default_type application/json;
    61. content_by_lua '
    62. # 获取URL参数
    63. local ups = ngx.req.get_uri_args()["ups"]
    64. local act = ngx.req.get_uri_args()["act"]
    65. if act == nil or ups == nil then
    66. ngx.say("usage: /ups_api?ups={name}&act=[down,up,list]")
    67. return
    68. end
    69. # 引用api_func.lua脚本
    70. local api_fun = require "api_func"
    71. # 绑定共享内存_healthcheck_zone
    72. local zone_define=ngx.shared["_healthcheck_zone"]
    73. if act == "list" then
    74. # 获取指定upstream的节点列表
    75. api_fun.list_server(ups)
    76. else
    77. ngx.req.read_body()
    78. local args, err = ngx.req.get_post_args()
    79. if act == "up" then
    80. # 节点状态将设置为UP
    81. api_fun.set_server(ups,args,false,false,zone_define)
    82. end
    83. if act == "down" then
    84. # 节点状态将设置为DOWN
    85. api_fun.set_server(ups,args,true,false,zone_define)
    86. end
    87. end
    88. ';
    89. }
    90. }

            操作命令如下:

    1. # 查看upstream foo.com的服务器列表
    2. curl "http://127.0.0.1:18080/ups_api?act=list&ups=foo.com"
    3. # 将192.168.2.145:8080这个节点设置为DOWN状态
    4. curl -X POST -d "server=192.168.2.145:8080" "http://127.0.0.1:18080/ups_api?act= down&ups=foo.com"
    5. # 将192.168.2.145:8080这个节点设置为UP状态
    6. curl -X POST -d "server=192.168.2.145:8080" "http://127.0.0.1:18080/ups_api?act= up&ups=foo.com"

     

     

  • 相关阅读:
    上海控安SmartRocket系列产品推介(五):SmartRocket Scanner软件成分分析工具
    [附源码]Python计算机毕业设计Django餐馆点餐管理系统
    一幅长文细学JavaScript(七)——一幅长文系列
    小米OPPO三星一加红魔全机型解锁BL详细教程合集-ROOT刷机必要操作
    SpringBean生命周期&扩展接口&简化配置
    瑞吉外卖项目学习笔记01
    【云原生布道系列】第二篇:云原生时代领域解决方案专家的价值
    Postman —— post请求数据类型
    暑假加餐|有钱人和你想的不一样(第15天)+财富与金钱的秘密【干货】
    金融机构如何做好中小微物流企业的风控措施?
  • 原文地址:https://blog.csdn.net/qq_37278522/article/details/139731259