From 8085304cb98c0c5ba0470f02dc5ac73b72a62bbd Mon Sep 17 00:00:00 2001 From: qianyong Date: Thu, 10 Dec 2020 10:51:47 +0800 Subject: [PATCH] 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. --- rootfs/etc/nginx/lua/balancer.lua | 38 +++++++++++------- rootfs/etc/nginx/lua/test/balancer_test.lua | 44 ++++++++++++++++++++- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/rootfs/etc/nginx/lua/balancer.lua b/rootfs/etc/nginx/lua/balancer.lua index be3bbb320..4a59780ac 100644 --- a/rootfs/etc/nginx/lua/balancer.lua +++ b/rootfs/etc/nginx/lua/balancer.lua @@ -107,6 +107,12 @@ local function sync_backend(backend) return 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 balancer = balancers[backend.name] @@ -126,24 +132,21 @@ local function sync_backend(backend) return end - if is_backend_with_external_name(backend) then - backend = resolve_external_names(backend) - end - - backend.endpoints = format_ipv6_endpoints(backend.endpoints) - balancer:sync(backend) end +local function sync_backends_with_external_name() + for _, backend_with_external_name in pairs(backends_with_external_name) do + sync_backend(backend_with_external_name) + end +end + local function sync_backends() local raw_backends_last_synced_at = configuration.get_raw_backends_last_synced_at() ngx.update_time() local current_timestamp = ngx.time() if current_timestamp - backends_last_synced_at < BACKENDS_FORCE_SYNC_INTERVAL 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 end @@ -161,12 +164,13 @@ local function sync_backends() local balancers_to_keep = {} 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 local backend_with_external_name = util.deepcopy(new_backend) backends_with_external_name[backend_with_external_name.name] = backend_with_external_name + else + sync_backend(new_backend) end + balancers_to_keep[new_backend.name] = true end for backend_name, _ in pairs(balancers) do @@ -272,11 +276,12 @@ local function get_balancer() end function _M.init_worker() - -- when worker starts, sync backends without delay - -- we call it in timer because for endpoints that require + -- when worker starts, sync non ExternalName backends without delay + 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 -- 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 ngx.log(ngx.ERR, "failed to create timer: ", err) end @@ -285,6 +290,11 @@ function _M.init_worker() if not ok then ngx.log(ngx.ERR, "error when setting up timer.every for sync_backends: ", err) 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 function _M.rewrite() diff --git a/rootfs/etc/nginx/lua/test/balancer_test.lua b/rootfs/etc/nginx/lua/test/balancer_test.lua index aa505f174..4acf3769a 100644 --- a/rootfs/etc/nginx/lua/test/balancer_test.lua +++ b/rootfs/etc/nginx/lua/test/balancer_test.lua @@ -1,3 +1,5 @@ +local cjson = require("cjson.safe") +local util = require("util") local balancer, expected_implementations, backends local original_ngx = ngx @@ -339,7 +341,9 @@ describe("Balancer", function() local mock_instance = { sync = function(backend) end } setmetatable(mock_instance, implementation) 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.spy(s).was_called_with(implementation, expected_backend) stub(mock_instance, "sync") assert.has_no.errors(function() balancer.sync_backend(backend) end) 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 } setmetatable(mock_instance, implementation) 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") - 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) end) @@ -400,4 +406,38 @@ describe("Balancer", function() assert.stub(mock_instance.sync).was_called_with(mock_instance, backend) 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)