Pular para conteúdo

upstream: Módulo de balanceamento de carga e failover de conexão upstream para nginx-module-lua

Instalação

Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Em seguida, você pode prosseguir com os seguintes passos.

CentOS/RHEL 7 ou 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-upstream

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

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

Para usar esta biblioteca Lua com NGINX, certifique-se de que o nginx-module-lua está instalado.

Este documento descreve lua-resty-upstream v0.10 lançado em 19 de dezembro de 2019.


Módulo de balanceamento de carga e failover de conexão upstream

Visão Geral

Crie um dicionário compartilhado lua. Defina seus pools e hosts upstream em init_by_lua, isso será salvo no dicionário compartilhado.

Use o método connect para retornar um socket tcp conectado.

Alternativamente, passe um módulo resty (por exemplo, lua-resty-redis ou lua-resty-http) que implemente connect() e set_timeout().

Chame process_failed_hosts para lidar com hosts com falha sem bloquear a solicitação atual.

Use resty.upstream.api para modificar a configuração upstream durante a inicialização ou em tempo de execução, isso é recomendado!

resty.upstream.http envolve o lua-resty-http do @pintsized.

Ele permite failover com base em códigos de status HTTP, bem como no status da conexão do socket.

lua_shared_dict my_upstream_dict 1m;
init_by_lua '
    upstream_socket  = require("resty.upstream.socket")
    upstream_api = require("resty.upstream.api")

    upstream, configured = upstream_socket:new("my_upstream_dict")
    if not upstream then
        error(configured)
    end
    api = upstream_api:new(upstream)

    if not configured then -- Apenas reconfigurar na inicialização, a memória compartilhada persiste através de um HUP
        api:create_pool({id = "primary", timeout = 100})
        api:set_priority("primary", 0)
        api:set_method("primary", "round_robin")
        api:add_host("primary", { id="a", host = "127.0.0.1", port = "80", weight = 10 })
        api:add_host("primary", { id="b", host = "127.0.0.1", port = "81",  weight = 10 })

        api:create_pool({id = "dr"})
        api:set_priority("dr", 10)
        api:add_host("dr", { host = "127.0.0.1", port = "82", weight = 5 })
        api:add_host("dr", { host = "127.0.0.1", port = "83", weight = 10 })

        api:create_pool({id = "test", priority = 5})
        api:add_host("primary", { id="c", host = "127.0.0.1", port = "82", weight = 10 })
        api:add_host("primary", { id="d", host = "127.0.0.1", port = "83", weight = 10 })
    end
';

init_worker_by_lua 'upstream:init_background_thread()';

server {

    location / {
        content_by_lua '
            local sock, err = upstream:connect()
            upstream:process_failed_hosts()
        ';
    }

}

upstream.socket

new

syntax: upstream, configured = upstream_socket:new(dictionary, id?)

Retorna um novo objeto upstream usando o nome do dicionário fornecido. Quando chamado em init_by_lua, retorna uma variável adicional se o dicionário já contiver configuração. Aceita um parâmetro id opcional, este deve ser único se várias instâncias de upstream.socket estiverem usando o mesmo dicionário.

init_background_thread

syntax: ok, err = upstream:init_background_thread()

Inicializa a thread em segundo plano, deve ser chamada em init_worker_by_lua.

connect

syntax: ok, err = upstream:connect(client?, key?)

Tenta se conectar a um host nos pools definidos na ordem de prioridade usando o método de balanceamento de carga selecionado. Retorna um socket conectado e uma tabela contendo o host, poolid e pool conectados ou nil e uma mensagem de erro.

Quando passado um socket ou módulo resty, retornará o mesmo objeto após a conexão bem-sucedida ou nil.

Além disso, métodos de hash podem aceitar uma key opcional para definir como hash a conexão para determinar o host. Por padrão, ngx.var.remote_addr é usado. Este valor é ignorado quando o método do pool é round robin.

resty_redis = require('resty.redis')
local redis = resty_redis.new()

local key = ngx.req.get_headers()["X-Forwarded-For"]

local redis, err = upstream:connect(redis, key)

if not redis then
    ngx.log(ngx.ERR, err)
    ngx.status = 500
    return ngx.exit(ngx.status)
end

ngx.log(ngx.info, 'Conectado a ' .. err.host.host .. ':' .. err.host.port)
local ok, err = redis:get('key')

process_failed_hosts

syntax: ok, err = upstream:process_failed_hosts()

Processa quaisquer hosts com falha ou recuperados da solicitação atual. Gera um callback imediato via ngx.timer.at, não bloqueia a solicitação atual.

get_pools

syntax: pools = usptream:get_pools()

Retorna uma tabela contendo a configuração atual de pools e hosts. Ex.:

{
    primary = {
        up = true,
        method = 'round_robin',
        timeout = 100,
        priority = 0,
        hosts = {
            web01 = {
                host = "127.0.0.1",
                weight = 10,
                port = "80",
                lastfail = 0,
                failcount = 0,
                up = true,
                healthcheck = true
            },
            web02 = {
                host = "127.0.0.1",
                weight = 10,
                port = "80",
                lastfail = 0,
                failcount = 0,
                up = true,
                healthcheck = { interval = 30, path = '/check' }
            }
        }
    },
    secondary = {
        up = true,
        method = 'round_robin',
        timeout = 2000,
        priority = 10,
        hosts = {
            dr01 = {
                host = "10.10.10.1",
                weight = 10,
                port = "80",
                lastfail = 0,
                failcount = 0,
                up = true
            }
        }
    },
}

save_pools

syntax: ok, err = upstream:save_pools(pools)

Salva uma tabela de pools no dicionário compartilhado, pools deve estar no mesmo formato que o retornado por get_pools.

sort_pools

syntax: ok, err = upstream:sort_pools(pools)

Gera uma ordem de prioridade no dicionário compartilhado com base na tabela de pools fornecida.

bind

syntax: ok, err = upstream:bind(event, func)

Vincula uma função a ser chamada quando eventos ocorrerem. func deve esperar 1 argumento contendo os dados do evento.

Retorna true em um vínculo bem-sucedido ou nil e uma mensagem de erro em caso de falha.

local function host_down_handler(event)
    ngx.log(ngx.ERR, "Host: ", event.host.host, ":", event.host.port, " no pool '", event.pool.id,'" está fora do ar!')
end
local ok, err = upstream:bind('host_down', host_down_handler)

Evento: host_up

Disparado quando um host muda de status de fora do ar para em funcionamento. Os dados do evento são uma tabela contendo o host e o pool afetados.

Evento: host_down

Disparado quando um host muda de status de em funcionamento para fora do ar. Os dados do evento são uma tabela contendo o host e o pool afetados.

upstream.api

Essas funções permitem que você reconfigure dinamicamente pools e hosts upstream.

new

syntax: api, err = upstream_api:new(upstream)

Retorna um novo objeto api usando o objeto upstream fornecido.

set_method

syntax: ok, err = api:set_method(poolid, method)

Define o método de balanceamento de carga para o pool especificado. Atualmente, métodos de round robin aleatório e hashing são suportados.

create_pool

syntax: ok, err = api:create_pool(pool)

Cria um novo pool a partir de uma tabela de opções, pool deve conter pelo menos 1 chave id que deve ser única dentro do objeto upstream atual.

Outras opções válidas são:

  • method Método de balanceamento
  • timeout Tempo limite de conexão em ms
  • priority Pools de maior prioridade são usados posteriormente
  • read_timeout
  • keepalive_timeout
  • keepalive_pool
  • status_codes Veja status_codes

Hosts não podem ser definidos neste ponto.

Nota: IDs são convertidos para uma string por esta função.

Valores padrão do pool:

{ method = 'round_robin', timeout = 2000, priority = 0 }

set_priority

syntax: ok, err = api:set_priority(poolid, priority)

A prioridade deve ser um número, retorna nil em caso de erro.

add_host

syntax: ok, err = api:add_host(poolid, host)

Aceita um ID de pool e uma tabela de opções, host deve conter pelo menos host. Se o ID do host não for especificado, será um índice numérico com base no número de hosts no pool.

Nota: IDs são convertidos para uma string por esta função.

Padrões:

{ host = '', port = 80, weight = 0}

remove_host

syntax: ok, err = api:remove_host(poolid, host)

Aceita um poolid e um hostid para remover do pool.

down_host

syntax: ok,err = api:down_host(poolid, host)

Marca manualmente um host como fora do ar, este host não será recuperado automaticamente.

up_host

syntax: ok,err = api:up_host(poolid, host)

Restaura manualmente um host inativo para o pool.

upstream.http

Funções para fazer requisições http para hosts upstream.

new

syntax: httpc, err = upstream_http:new(upstream, ssl_opts?)

Retorna um novo objeto http upstream usando o objeto upstream fornecido.

ssl_opts é uma tabela opcional para configurar o suporte a SSL. * ssl definido como true para habilitar o handshake SSL, padrão false * ssl_verify definido como false para desativar a verificação do certificado SSL, padrão true * sni_host uma string a ser usada como o hostname sni, padrão é o cabeçalho Host da requisição.

lua https_upstream = Upstream_HTTP:new(upstream_ssl, { ssl = true, ssl_verify = true, sni_host = "foo.example.com" })

init_background_thread

syntax: ok, err = upstream_http:init_background_thread()

Inicializa a thread em segundo plano, deve ser chamada em init_worker_by_lua.

Não chame o método init_background_thread em upstream.socket se estiver usando a thread em segundo plano upstream.http.

request

syntax: res, err_or_conn_info, status? = upstream_api:request(params)

Aceita os mesmos parâmetros que o método request do lua-resty-http.

Em uma solicitação bem-sucedida, retorna o objeto lua-resty-http e uma tabela contendo o host e o pool conectados.

Se a solicitação falhar, retorna nil, o erro e um código de status http sugerido.

local ok, err, status = upstream_http:request({
        path = "/helloworld",
        headers = {
            ["Host"] = "example.com",
        }
    })
if not ok then
    ngx.status = status
    ngx.say(err)
    ngx.exit(status)
else
    local host = err.host
    local pool = err.pool
end

set_keepalive

syntax: ok, err = upstream_http:set_keepalive()

Passa o tempo limite de keepalive / pool da configuração do pool para o método set_keepalive do lua-resty-http.

get_reused_times

syntax: ok, err = upstream_http:get_reused_times()

Passa para o método get_reused_times do lua-resty-http.

close

syntax: ok, err = upstream_http:close()

Passa para o método close do lua-resty-http.

Verificações de Saúde HTTP

Verificações de saúde ativas em segundo plano podem ser habilitadas adicionando o parâmetro healthcheck a um host.

Um valor de true habilitará a verificação padrão, uma requisição GET para /.

O parâmetro healthcheck também pode ser uma tabela de parâmetros válidos para o método request do lua-resty-http.

Com alguns parâmetros adicionais:

  • interval para definir o tempo entre as verificações de saúde, em segundos. Deve ser >= 10s. O padrão é 60s.
  • timeout define o tempo limite de conexão para as verificações de saúde. O padrão é a configuração do pool.
  • read_timeout define o tempo limite de leitura para as verificações de saúde. O padrão é a configuração do pool.
  • status_codes uma tabela de códigos de status de resposta inválidos. O padrão é a configuração do pool.

A falha da verificação em segundo plano é de acordo com os mesmos parâmetros que para uma solicitação de frontend, a menos que seja explicitamente substituída.

-- Parâmetros de verificação personalizados
api:add_host("primary", {
     host = 123.123.123.123,
     port = 80,
     healthcheck = {
        interval = 30, -- verificar a cada 30s
        timeout      = (5*1000), -- 5s de tempo limite de conexão
        read_timeout = (15*1000), -- 15s de tempo limite de leitura
        status_codes = {["5xx"] = true, ["403"] = true}, -- respostas 5xx e 403 são uma falha
        -- parâmetros resty-http
        path = "/check",
        headers = {
            ["Host"] = "domain.com",
            ["Accept-Encoding"] = "gzip"
        }
     }
})

-- Parâmetros de verificação padrão
api:add_host("primary", {host = 123.123.123.123, port = 80, healthcheck = true})

GitHub

Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-upstream.