Separate the ExternalName backend from other backends in the process of synchronizing the backend, because the synchronization of the ExternalName backend requires dns resolution, so we should ensure that it does not affect the synchronization of the Non-ExternalName backend. After separation, in the init worker stage, we should immediately synchronize the Non-ExternalName backend, otherwise there will be some requests that fail with 503 because the balancer cannot be obtained in the rewrite stage.

This commit is contained in:
qianyong 2020-12-10 10:51:47 +08:00
parent 5bd066463e
commit 8085304cb9
2 changed files with 66 additions and 16 deletions

View file

@ -107,6 +107,12 @@ local function sync_backend(backend)
return return
end end
if is_backend_with_external_name(backend) then
backend = resolve_external_names(backend)
end
backend.endpoints = format_ipv6_endpoints(backend.endpoints)
local implementation = get_implementation(backend) local implementation = get_implementation(backend)
local balancer = balancers[backend.name] local balancer = balancers[backend.name]
@ -126,13 +132,13 @@ local function sync_backend(backend)
return return
end end
if is_backend_with_external_name(backend) then balancer:sync(backend)
backend = resolve_external_names(backend)
end end
backend.endpoints = format_ipv6_endpoints(backend.endpoints) local function sync_backends_with_external_name()
for _, backend_with_external_name in pairs(backends_with_external_name) do
balancer:sync(backend) sync_backend(backend_with_external_name)
end
end end
local function sync_backends() local function sync_backends()
@ -141,9 +147,6 @@ local function sync_backends()
local current_timestamp = ngx.time() local current_timestamp = ngx.time()
if current_timestamp - backends_last_synced_at < BACKENDS_FORCE_SYNC_INTERVAL if current_timestamp - backends_last_synced_at < BACKENDS_FORCE_SYNC_INTERVAL
and raw_backends_last_synced_at <= backends_last_synced_at then and raw_backends_last_synced_at <= backends_last_synced_at then
for _, backend_with_external_name in pairs(backends_with_external_name) do
sync_backend(backend_with_external_name)
end
return return
end end
@ -161,12 +164,13 @@ local function sync_backends()
local balancers_to_keep = {} local balancers_to_keep = {}
for _, new_backend in ipairs(new_backends) do for _, new_backend in ipairs(new_backends) do
sync_backend(new_backend)
balancers_to_keep[new_backend.name] = balancers[new_backend.name]
if is_backend_with_external_name(new_backend) then if is_backend_with_external_name(new_backend) then
local backend_with_external_name = util.deepcopy(new_backend) local backend_with_external_name = util.deepcopy(new_backend)
backends_with_external_name[backend_with_external_name.name] = backend_with_external_name backends_with_external_name[backend_with_external_name.name] = backend_with_external_name
else
sync_backend(new_backend)
end end
balancers_to_keep[new_backend.name] = true
end end
for backend_name, _ in pairs(balancers) do for backend_name, _ in pairs(balancers) do
@ -272,11 +276,12 @@ local function get_balancer()
end end
function _M.init_worker() function _M.init_worker()
-- when worker starts, sync backends without delay -- when worker starts, sync non ExternalName backends without delay
-- we call it in timer because for endpoints that require sync_backends()
-- we call sync_backends_with_external_name in timer because for endpoints that require
-- DNS resolution it needs to use socket which is not available in -- DNS resolution it needs to use socket which is not available in
-- init_worker phase -- init_worker phase
local ok, err = ngx.timer.at(0, sync_backends) local ok, err = ngx.timer.at(0, sync_backends_with_external_name)
if not ok then if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err) ngx.log(ngx.ERR, "failed to create timer: ", err)
end end
@ -285,6 +290,11 @@ function _M.init_worker()
if not ok then if not ok then
ngx.log(ngx.ERR, "error when setting up timer.every for sync_backends: ", err) ngx.log(ngx.ERR, "error when setting up timer.every for sync_backends: ", err)
end end
ok, err = ngx.timer.every(BACKENDS_SYNC_INTERVAL, sync_backends_with_external_name)
if not ok then
ngx.log(ngx.ERR, "error when setting up timer.every for sync_backends_with_external_name: ",
err)
end
end end
function _M.rewrite() function _M.rewrite()

View file

@ -1,3 +1,5 @@
local cjson = require("cjson.safe")
local util = require("util")
local balancer, expected_implementations, backends local balancer, expected_implementations, backends
local original_ngx = ngx local original_ngx = ngx
@ -339,7 +341,9 @@ describe("Balancer", function()
local mock_instance = { sync = function(backend) end } local mock_instance = { sync = function(backend) end }
setmetatable(mock_instance, implementation) setmetatable(mock_instance, implementation)
implementation.new = function(self, backend) return mock_instance end implementation.new = function(self, backend) return mock_instance end
local s = spy.on(implementation, "new")
assert.has_no.errors(function() balancer.sync_backend(backend) end) assert.has_no.errors(function() balancer.sync_backend(backend) end)
assert.spy(s).was_called_with(implementation, expected_backend)
stub(mock_instance, "sync") stub(mock_instance, "sync")
assert.has_no.errors(function() balancer.sync_backend(backend) end) assert.has_no.errors(function() balancer.sync_backend(backend) end)
assert.stub(mock_instance.sync).was_called_with(mock_instance, expected_backend) assert.stub(mock_instance.sync).was_called_with(mock_instance, expected_backend)
@ -364,9 +368,11 @@ describe("Balancer", function()
local mock_instance = { sync = function(backend) end } local mock_instance = { sync = function(backend) end }
setmetatable(mock_instance, implementation) setmetatable(mock_instance, implementation)
implementation.new = function(self, backend) return mock_instance end implementation.new = function(self, backend) return mock_instance end
assert.has_no.errors(function() balancer.sync_backend(backend) end) local s = spy.on(implementation, "new")
assert.has_no.errors(function() balancer.sync_backend(util.deepcopy(backend)) end)
assert.spy(s).was_called_with(implementation, expected_backend)
stub(mock_instance, "sync") stub(mock_instance, "sync")
assert.has_no.errors(function() balancer.sync_backend(backend) end) assert.has_no.errors(function() balancer.sync_backend(util.deepcopy(backend)) end)
assert.stub(mock_instance.sync).was_called_with(mock_instance, expected_backend) assert.stub(mock_instance.sync).was_called_with(mock_instance, expected_backend)
end) end)
@ -400,4 +406,38 @@ describe("Balancer", function()
assert.stub(mock_instance.sync).was_called_with(mock_instance, backend) assert.stub(mock_instance.sync).was_called_with(mock_instance, backend)
end) end)
end) end)
describe("sync_backends()", function()
after_each(function()
reset_ngx()
end)
it("sync backends", function()
backends = {
{
name = "access-router-production-web-80", port = "80", secure = false,
sslPassthrough = false,
endpoints = {
{ address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 },
{ address = "10.184.97.100", port = "8080", maxFails = 0, failTimeout = 0 },
{ address = "10.184.98.239", port = "8080", maxFails = 0, failTimeout = 0 },
},
sessionAffinityConfig = { name = "", cookieSessionAffinity = { name = "" } },
trafficShapingPolicy = {
weight = 0,
header = "",
headerValue = "",
cookie = ""
},
}
}
mock_ngx({ var = { proxy_upstream_name = "access-router-production-web-80" }, ctx = { } })
ngx.shared.configuration_data:set("backends", cjson.encode(backends))
reset_balancer()
balancer.init_worker()
assert.not_equal(balancer.get_balancer(), nil)
end)
end)
end) end)