Home

Awesome

Name

lua-resty-checkups - Manage Nginx upstreams in pure ngx_lua

Build Status

Table of Contents

Status

Probably production ready in most cases, though not yet proven in the wild. Please check the issues list and let me know if you have any problems / questions.

Features

Installation

$ luarocks install lua-resty-checkups
$ opm get upyun/lua-resty-checkups

Just tweeks the lua_package_path or the LUA_PATH environment variable, to add the installation path for this Lua module:

/path/to/lua-resty-checkups/lib/resty/?.lua;

Compatibility

Synopsis

    -- 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_package_path "/path/to/lua-resty-checkups/lib/?.lua;/path/to/config.lua;;";

    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

                -- connect to a dead server, no upstream available
                ok, err = checkups.ready_ok("ups1", callback)
                if err then ngx.say(err) end

                -- add server to 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

                -- add server to new upstream
                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

                -- add server to ups2, reset rr state
                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
            }
        }
    }

A typical output of the /t location defined above is:

no servers available
12350
12350
12351
12350
12351

Configuration

Lua configuration

Configuration file of checkups is a lua module consists of two parts, the global part and the cluster part.

An example configuration file of checkups is shown below,

    -- config.lua

    -- Here is the global part

    _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,
    }


    -- The rests parts are cluster configurations

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

        protected = true,

        cluster = {
            {   -- level 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 },
                }
            },
            {   -- level 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 = {
            {   -- level 1
                upstream = "api.com",
            },
            {   -- level 2
                upstream = "api.com",
                upstream_only_backup = true,
            },
        },
    }

    return _M

global configurations

Cluster configurations

Nginx configuration

Add pathes of lua config file and checkups to lua_package_path and create lua shared dicts used by checkups. You should put these lines into http block of your Nginx config file.

lua_package_path "/path/to/lua-resty-checkups/lib/?.lua;/path/to/config.lua;;";

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

If you use stream subsystem, you should put these lines into stream block of your Nginx config file.

lua_package_path "/path/to/lua-resty-checkups/lib/?.lua;/path/to/config.lua;;";

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

syntax: init(config)

phase: init_by_lua

Copy upstreams from config.lua to shdict, extract servers from Nginx upstream blocks and do some basic initialization.

prepare_checker

syntax: prepare_checker(config)

phase: init_worker_by_lua

Copy configurations from config.lua to worker checkups, extract servers from Nginx upstream blocks and do some basic initialization.

create_checker

syntax: create_checker()

phase: init_worker_by_lua

Create heartbeat timer and upstream sync timer. Only one heartbeat timer will be created among all the workers. It's highly recommended to call this method in init_worker phase.

ready_ok

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

phase: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*

Select an available peer from cluster skey and call callback(peer.host, peer.port, opts).

The opts table accepts the following fields,

Returns what callback returns on success, or returns nil and a string describing the error otherwise.

If callback returns nil or false, checkups will consider it to be a failed try and will retry callback with another peer. So, always remember not to return nil or false after a successful callback.

select_peer

syntax: peer, err = select_peer(skey)

context: rewrite_by_lua*, access_by_lua*, content_by_lua*, balancer_by_lua

Select an available peer from cluster skey.

Return a table containing host and port of an available peer.

In case of errors, returns nil with a string describing the error.

get_status

syntax: status = get_status()

phase: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*

Return checkups status in json format.

get_ups_timeout

syntax: connect_timeout, send_timeout, read_timeout = get_ups_timeout(skey)

phase: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*

Return timeout of cluster skey.

feedback_status

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

context: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, balancer_by_lua.*

Mark server host:port in cluster skey as failed(true) or available(false).

Returns 1 on success, or returns nil and a string describing the error otherwise.

update_upstream

syntax: ok, err = update_upstream(skey, upstream)

phase: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*

Update cluster skey. upstream is in the same format as cluster in config.lua.

Returns true on success, or returns false and a string describing the error otherwise.

delete_upstream

syntax: ok, err = delete_upstream(skey)

phase: rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*

Delete cluster skey from upstream list.

Returns true on success, or returns false and a string describing the error otherwise.

Copyright and License

The bundle itself is licensed under the 2-clause BSD license.

Copyright (c) 2016, UPYUN(又拍云) Inc.

This module is licensed under the terms of the BSD license.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

See Also