Merge branch 'master' of https://github.com/kubernetes/ingress-nginx into proxyssl
This commit is contained in:
commit
65b9e2c574
391 changed files with 23957 additions and 20447 deletions
|
|
@ -24,7 +24,8 @@ RUN clean-install \
|
|||
|
||||
COPY --chown=www-data:www-data . /
|
||||
|
||||
RUN cp /usr/local/openresty/nginx/conf/mime.types /etc/nginx/mime.types
|
||||
RUN cp /usr/local/openresty/nginx/conf/mime.types /etc/nginx/mime.types \
|
||||
&& cp /usr/local/openresty/nginx/conf/fastcgi_params /etc/nginx/fastcgi_params
|
||||
RUN ln -s /usr/local/openresty/nginx/modules /etc/nginx/modules
|
||||
|
||||
# Add LuaRocks paths
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
local ngx_balancer = require("ngx.balancer")
|
||||
local cjson = require("cjson.safe")
|
||||
local util = require("util")
|
||||
local dns_util = require("util.dns")
|
||||
local dns_lookup = require("util.dns").lookup
|
||||
local configuration = require("configuration")
|
||||
local round_robin = require("balancer.round_robin")
|
||||
local chash = require("balancer.chash")
|
||||
|
|
@ -52,7 +52,7 @@ local function resolve_external_names(original_backend)
|
|||
local backend = util.deepcopy(original_backend)
|
||||
local endpoints = {}
|
||||
for _, endpoint in ipairs(backend.endpoints) do
|
||||
local ips = dns_util.resolve(endpoint.address)
|
||||
local ips = dns_lookup(endpoint.address)
|
||||
for _, ip in ipairs(ips) do
|
||||
table.insert(endpoints, { address = ip, port = endpoint.port })
|
||||
end
|
||||
|
|
@ -190,6 +190,10 @@ local function route_to_alternative_balancer(balancer)
|
|||
end
|
||||
|
||||
local function get_balancer()
|
||||
if ngx.ctx.balancer then
|
||||
return ngx.ctx.balancer
|
||||
end
|
||||
|
||||
local backend_name = ngx.var.proxy_upstream_name
|
||||
|
||||
local balancer = balancers[backend_name]
|
||||
|
|
@ -201,9 +205,11 @@ local function get_balancer()
|
|||
local alternative_backend_name = balancer.alternative_backends[1]
|
||||
ngx.var.proxy_alternative_upstream_name = alternative_backend_name
|
||||
|
||||
return balancers[alternative_backend_name]
|
||||
balancer = balancers[alternative_backend_name]
|
||||
end
|
||||
|
||||
ngx.ctx.balancer = balancer
|
||||
|
||||
return balancer
|
||||
end
|
||||
|
||||
|
|
@ -260,6 +266,7 @@ if _TEST then
|
|||
_M.get_implementation = get_implementation
|
||||
_M.sync_backend = sync_backend
|
||||
_M.route_to_alternative_balancer = route_to_alternative_balancer
|
||||
_M.get_balancer = get_balancer
|
||||
end
|
||||
|
||||
return _M
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
-- /finagle-core/src/main/scala/com/twitter/finagle/loadbalancer/PeakEwma.scala
|
||||
|
||||
|
||||
local resty_lock = require("resty.lock")
|
||||
local util = require("util")
|
||||
local split = require("util.split")
|
||||
|
||||
|
|
@ -13,10 +14,36 @@ local ngx_log = ngx.log
|
|||
local INFO = ngx.INFO
|
||||
|
||||
local DECAY_TIME = 10 -- this value is in seconds
|
||||
local LOCK_KEY = ":ewma_key"
|
||||
local PICK_SET_SIZE = 2
|
||||
|
||||
local ewma_lock, ewma_lock_err = resty_lock:new("balancer_ewma_locks", {timeout = 0, exptime = 0.1})
|
||||
if not ewma_lock then
|
||||
error(ewma_lock_err)
|
||||
end
|
||||
|
||||
local _M = { name = "ewma" }
|
||||
|
||||
local function lock(upstream)
|
||||
local _, err = ewma_lock:lock(upstream .. LOCK_KEY)
|
||||
if err then
|
||||
if err ~= "timeout" then
|
||||
ngx.log(ngx.ERR, string.format("EWMA Balancer failed to lock: %s", tostring(err)))
|
||||
end
|
||||
end
|
||||
|
||||
return err
|
||||
end
|
||||
|
||||
local function unlock()
|
||||
local ok, err = ewma_lock:unlock()
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, string.format("EWMA Balancer failed to unlock: %s", tostring(err)))
|
||||
end
|
||||
|
||||
return err
|
||||
end
|
||||
|
||||
local function decay_ewma(ewma, last_touched_at, rtt, now)
|
||||
local td = now - last_touched_at
|
||||
td = (td > 0) and td or 0
|
||||
|
|
@ -26,28 +53,55 @@ local function decay_ewma(ewma, last_touched_at, rtt, now)
|
|||
return ewma
|
||||
end
|
||||
|
||||
local function get_or_update_ewma(self, upstream, rtt, update)
|
||||
local ewma = self.ewma[upstream] or 0
|
||||
local function store_stats(upstream, ewma, now)
|
||||
local success, err, forcible = ngx.shared.balancer_ewma_last_touched_at:set(upstream, now)
|
||||
if not success then
|
||||
ngx.log(ngx.WARN, "balancer_ewma_last_touched_at:set failed " .. err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.WARN, "balancer_ewma_last_touched_at:set valid items forcibly overwritten")
|
||||
end
|
||||
|
||||
success, err, forcible = ngx.shared.balancer_ewma:set(upstream, ewma)
|
||||
if not success then
|
||||
ngx.log(ngx.WARN, "balancer_ewma:set failed " .. err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.WARN, "balancer_ewma:set valid items forcibly overwritten")
|
||||
end
|
||||
end
|
||||
|
||||
local function get_or_update_ewma(upstream, rtt, update)
|
||||
local lock_err = nil
|
||||
if update then
|
||||
lock_err = lock(upstream)
|
||||
end
|
||||
local ewma = ngx.shared.balancer_ewma:get(upstream) or 0
|
||||
if lock_err ~= nil then
|
||||
return ewma, lock_err
|
||||
end
|
||||
|
||||
local now = ngx.now()
|
||||
local last_touched_at = self.ewma_last_touched_at[upstream] or 0
|
||||
local last_touched_at = ngx.shared.balancer_ewma_last_touched_at:get(upstream) or 0
|
||||
ewma = decay_ewma(ewma, last_touched_at, rtt, now)
|
||||
|
||||
if not update then
|
||||
return ewma, nil
|
||||
end
|
||||
|
||||
self.ewma[upstream] = ewma
|
||||
self.ewma_last_touched_at[upstream] = now
|
||||
store_stats(upstream, ewma, now)
|
||||
|
||||
unlock()
|
||||
|
||||
return ewma, nil
|
||||
end
|
||||
|
||||
|
||||
local function score(self, upstream)
|
||||
local function score(upstream)
|
||||
-- Original implementation used names
|
||||
-- Endpoints don't have names, so passing in IP:Port as key instead
|
||||
local upstream_name = upstream.address .. ":" .. upstream.port
|
||||
return get_or_update_ewma(self, upstream_name, 0, false)
|
||||
return get_or_update_ewma(upstream_name, 0, false)
|
||||
end
|
||||
|
||||
-- implementation similar to https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
|
||||
|
|
@ -63,12 +117,12 @@ local function shuffle_peers(peers, k)
|
|||
-- peers[1 .. k] will now contain a randomly selected k from #peers
|
||||
end
|
||||
|
||||
local function pick_and_score(self, peers, k)
|
||||
local function pick_and_score(peers, k)
|
||||
shuffle_peers(peers, k)
|
||||
local lowest_score_index = 1
|
||||
local lowest_score = score(self, peers[lowest_score_index])
|
||||
local lowest_score = score(peers[lowest_score_index])
|
||||
for i = 2, k do
|
||||
local new_score = score(self, peers[i])
|
||||
local new_score = score(peers[i])
|
||||
if new_score < lowest_score then
|
||||
lowest_score_index, lowest_score = i, new_score
|
||||
end
|
||||
|
|
@ -76,6 +130,31 @@ local function pick_and_score(self, peers, k)
|
|||
return peers[lowest_score_index], lowest_score
|
||||
end
|
||||
|
||||
-- slow_start_ewma is something we use to avoid sending too many requests
|
||||
-- to the newly introduced endpoints. We currently use average ewma values
|
||||
-- of existing endpoints.
|
||||
local function calculate_slow_start_ewma(self)
|
||||
local total_ewma = 0
|
||||
local endpoints_count = 0
|
||||
|
||||
for _, endpoint in pairs(self.peers) do
|
||||
local endpoint_string = endpoint.address .. ":" .. endpoint.port
|
||||
local ewma = ngx.shared.balancer_ewma:get(endpoint_string)
|
||||
|
||||
if ewma then
|
||||
endpoints_count = endpoints_count + 1
|
||||
total_ewma = total_ewma + ewma
|
||||
end
|
||||
end
|
||||
|
||||
if endpoints_count == 0 then
|
||||
ngx.log(ngx.INFO, "no ewma value exists for the endpoints")
|
||||
return nil
|
||||
end
|
||||
|
||||
return total_ewma / endpoints_count
|
||||
end
|
||||
|
||||
function _M.balance(self)
|
||||
local peers = self.peers
|
||||
local endpoint, ewma_score = peers[1], -1
|
||||
|
|
@ -83,7 +162,7 @@ function _M.balance(self)
|
|||
if #peers > 1 then
|
||||
local k = (#peers < PICK_SET_SIZE) and #peers or PICK_SET_SIZE
|
||||
local peer_copy = util.deepcopy(peers)
|
||||
endpoint, ewma_score = pick_and_score(self, peer_copy, k)
|
||||
endpoint, ewma_score = pick_and_score(peer_copy, k)
|
||||
end
|
||||
|
||||
ngx.var.balancer_ewma_score = ewma_score
|
||||
|
|
@ -92,7 +171,7 @@ function _M.balance(self)
|
|||
return endpoint.address .. ":" .. endpoint.port
|
||||
end
|
||||
|
||||
function _M.after_balance(self)
|
||||
function _M.after_balance(_)
|
||||
local response_time = tonumber(split.get_first_value(ngx.var.upstream_response_time)) or 0
|
||||
local connect_time = tonumber(split.get_first_value(ngx.var.upstream_connect_time)) or 0
|
||||
local rtt = connect_time + response_time
|
||||
|
|
@ -101,30 +180,41 @@ function _M.after_balance(self)
|
|||
if util.is_blank(upstream) then
|
||||
return
|
||||
end
|
||||
get_or_update_ewma(self, upstream, rtt, true)
|
||||
|
||||
get_or_update_ewma(upstream, rtt, true)
|
||||
end
|
||||
|
||||
function _M.sync(self, backend)
|
||||
self.traffic_shaping_policy = backend.trafficShapingPolicy
|
||||
self.alternative_backends = backend.alternativeBackends
|
||||
local normalized_endpoints_added, normalized_endpoints_removed = util.diff_endpoints(self.peers, backend.endpoints)
|
||||
|
||||
local changed = not util.deep_compare(self.peers, backend.endpoints)
|
||||
if not changed then
|
||||
if #normalized_endpoints_added == 0 and #normalized_endpoints_removed == 0 then
|
||||
ngx.log(ngx.INFO, "endpoints did not change for backend " .. tostring(backend.name))
|
||||
return
|
||||
end
|
||||
|
||||
ngx_log(INFO, string_format("[%s] peers have changed for backend %s", self.name, backend.name))
|
||||
|
||||
self.traffic_shaping_policy = backend.trafficShapingPolicy
|
||||
self.alternative_backends = backend.alternativeBackends
|
||||
self.peers = backend.endpoints
|
||||
self.ewma = {}
|
||||
self.ewma_last_touched_at = {}
|
||||
|
||||
for _, endpoint_string in ipairs(normalized_endpoints_removed) do
|
||||
ngx.shared.balancer_ewma:delete(endpoint_string)
|
||||
ngx.shared.balancer_ewma_last_touched_at:delete(endpoint_string)
|
||||
end
|
||||
|
||||
local slow_start_ewma = calculate_slow_start_ewma(self)
|
||||
if slow_start_ewma ~= nil then
|
||||
local now = ngx.now()
|
||||
for _, endpoint_string in ipairs(normalized_endpoints_added) do
|
||||
store_stats(endpoint_string, slow_start_ewma, now)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function _M.new(self, backend)
|
||||
local o = {
|
||||
peers = backend.endpoints,
|
||||
ewma = {},
|
||||
ewma_last_touched_at = {},
|
||||
traffic_shaping_policy = backend.trafficShapingPolicy,
|
||||
alternative_backends = backend.alternativeBackends,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ local cjson = require("cjson.safe")
|
|||
local configuration_data = ngx.shared.configuration_data
|
||||
local certificate_data = ngx.shared.certificate_data
|
||||
|
||||
local _M = {
|
||||
nameservers = {}
|
||||
}
|
||||
local _M = {}
|
||||
|
||||
function _M.get_backends_data()
|
||||
return configuration_data:get("backends")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
local ngx_balancer = require("ngx.balancer")
|
||||
local cjson = require("cjson.safe")
|
||||
local util = require("util")
|
||||
local dns_util = require("util.dns")
|
||||
local dns_lookup = require("util.dns").lookup
|
||||
local configuration = require("tcp_udp_configuration")
|
||||
local round_robin = require("balancer.round_robin")
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ local function resolve_external_names(original_backend)
|
|||
local backend = util.deepcopy(original_backend)
|
||||
local endpoints = {}
|
||||
for _, endpoint in ipairs(backend.endpoints) do
|
||||
local ips = dns_util.resolve(endpoint.address)
|
||||
local ips = dns_lookup(endpoint.address)
|
||||
for _, ip in ipairs(ips) do
|
||||
table.insert(endpoints, {address = ip, port = endpoint.port})
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,91 +1,151 @@
|
|||
local util = require("util")
|
||||
|
||||
local original_ngx = ngx
|
||||
local function reset_ngx()
|
||||
_G.ngx = original_ngx
|
||||
end
|
||||
|
||||
local function mock_ngx(mock)
|
||||
local _ngx = mock
|
||||
setmetatable(_ngx, { __index = ngx })
|
||||
_G.ngx = _ngx
|
||||
end
|
||||
|
||||
local function flush_all_ewma_stats()
|
||||
ngx.shared.balancer_ewma:flush_all()
|
||||
ngx.shared.balancer_ewma_last_touched_at:flush_all()
|
||||
end
|
||||
|
||||
local function store_ewma_stats(endpoint_string, ewma, touched_at)
|
||||
ngx.shared.balancer_ewma:set(endpoint_string, ewma)
|
||||
ngx.shared.balancer_ewma_last_touched_at:set(endpoint_string, touched_at)
|
||||
end
|
||||
|
||||
local function assert_ewma_stats(endpoint_string, ewma, touched_at)
|
||||
assert.are.equals(ewma, ngx.shared.balancer_ewma:get(endpoint_string))
|
||||
assert.are.equals(touched_at, ngx.shared.balancer_ewma_last_touched_at:get(endpoint_string))
|
||||
end
|
||||
|
||||
|
||||
describe("Balancer ewma", function()
|
||||
local balancer_ewma = require("balancer.ewma")
|
||||
local ngx_now = 1543238266
|
||||
local backend, instance
|
||||
|
||||
before_each(function()
|
||||
mock_ngx({ now = function() return ngx_now end, var = { balancer_ewma_score = -1 } })
|
||||
|
||||
backend = {
|
||||
name = "namespace-service-port", ["load-balance"] = "ewma",
|
||||
endpoints = {
|
||||
{ address = "10.10.10.1", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
{ address = "10.10.10.2", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
{ address = "10.10.10.3", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
}
|
||||
}
|
||||
store_ewma_stats("10.10.10.1:8080", 0.2, ngx_now - 1)
|
||||
store_ewma_stats("10.10.10.2:8080", 0.3, ngx_now - 5)
|
||||
store_ewma_stats("10.10.10.3:8080", 1.2, ngx_now - 20)
|
||||
|
||||
instance = balancer_ewma:new(backend)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
reset_ngx()
|
||||
flush_all_ewma_stats()
|
||||
end)
|
||||
|
||||
describe("after_balance()", function()
|
||||
local ngx_now = 1543238266
|
||||
_G.ngx.now = function() return ngx_now end
|
||||
_G.ngx.var = { upstream_response_time = "0.25", upstream_connect_time = "0.02", upstream_addr = "10.184.7.40:8080" }
|
||||
|
||||
it("updates EWMA stats", function()
|
||||
local backend = {
|
||||
name = "my-dummy-backend", ["load-balance"] = "ewma",
|
||||
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
|
||||
}
|
||||
local instance = balancer_ewma:new(backend)
|
||||
ngx.var = { upstream_addr = "10.10.10.2:8080", upstream_connect_time = "0.02", upstream_response_time = "0.1" }
|
||||
|
||||
instance:after_balance()
|
||||
assert.equal(0.27, instance.ewma[ngx.var.upstream_addr])
|
||||
assert.equal(ngx_now, instance.ewma_last_touched_at[ngx.var.upstream_addr])
|
||||
|
||||
local weight = math.exp(-5 / 10)
|
||||
local expected_ewma = 0.3 * weight + 0.12 * (1.0 - weight)
|
||||
|
||||
assert.are.equals(expected_ewma, ngx.shared.balancer_ewma:get(ngx.var.upstream_addr))
|
||||
assert.are.equals(ngx_now, ngx.shared.balancer_ewma_last_touched_at:get(ngx.var.upstream_addr))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("balance()", function()
|
||||
it("returns single endpoint when the given backend has only one endpoint", function()
|
||||
local backend = {
|
||||
name = "my-dummy-backend", ["load-balance"] = "ewma",
|
||||
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
|
||||
}
|
||||
local instance = balancer_ewma:new(backend)
|
||||
local single_endpoint_backend = util.deepcopy(backend)
|
||||
table.remove(single_endpoint_backend.endpoints, 3)
|
||||
table.remove(single_endpoint_backend.endpoints, 2)
|
||||
local single_endpoint_instance = balancer_ewma:new(single_endpoint_backend)
|
||||
|
||||
local peer = instance:balance()
|
||||
assert.equal("10.184.7.40:8080", peer)
|
||||
local peer = single_endpoint_instance:balance()
|
||||
|
||||
assert.are.equals("10.10.10.1:8080", peer)
|
||||
assert.are.equals(-1, ngx.var.balancer_ewma_score)
|
||||
end)
|
||||
|
||||
it("picks the endpoint with lowest score when there two of them", function()
|
||||
local backend = {
|
||||
name = "my-dummy-backend", ["load-balance"] = "ewma",
|
||||
endpoints = {
|
||||
{ address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
{ address = "10.184.97.100", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
}
|
||||
}
|
||||
local instance = balancer_ewma:new(backend)
|
||||
instance.ewma = { ["10.184.7.40:8080"] = 0.5, ["10.184.97.100:8080"] = 0.3 }
|
||||
instance.ewma_last_touched_at = { ["10.184.7.40:8080"] = ngx.now(), ["10.184.97.100:8080"] = ngx.now() }
|
||||
it("picks the endpoint with lowest decayed score", function()
|
||||
local two_endpoints_backend = util.deepcopy(backend)
|
||||
table.remove(two_endpoints_backend.endpoints, 2)
|
||||
local two_endpoints_instance = balancer_ewma:new(two_endpoints_backend)
|
||||
|
||||
local peer = instance:balance()
|
||||
assert.equal("10.184.97.100:8080", peer)
|
||||
local peer = two_endpoints_instance:balance()
|
||||
|
||||
-- even though 10.10.10.1:8080 has a lower ewma score
|
||||
-- algorithm picks 10.10.10.3:8080 because its decayed score is even lower
|
||||
assert.equal("10.10.10.3:8080", peer)
|
||||
assert.are.equals(0.16240233988393523723, ngx.var.balancer_ewma_score)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("sync()", function()
|
||||
local backend, instance
|
||||
|
||||
before_each(function()
|
||||
backend = {
|
||||
name = "my-dummy-backend", ["load-balance"] = "ewma",
|
||||
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
|
||||
}
|
||||
instance = balancer_ewma:new(backend)
|
||||
end)
|
||||
|
||||
it("does nothing when endpoints do not change", function()
|
||||
local new_backend = {
|
||||
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
|
||||
}
|
||||
|
||||
instance:sync(new_backend)
|
||||
end)
|
||||
|
||||
it("updates endpoints", function()
|
||||
local new_backend = {
|
||||
endpoints = {
|
||||
{ address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
{ address = "10.184.97.100", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
}
|
||||
}
|
||||
|
||||
instance:sync(new_backend)
|
||||
assert.are.same(new_backend.endpoints, instance.peers)
|
||||
end)
|
||||
|
||||
it("resets stats", function()
|
||||
it("does not reset stats when endpoints do not change", function()
|
||||
local new_backend = util.deepcopy(backend)
|
||||
new_backend.endpoints[1].maxFails = 3
|
||||
|
||||
instance:sync(new_backend)
|
||||
|
||||
assert.are.same(new_backend.endpoints, instance.peers)
|
||||
|
||||
assert_ewma_stats("10.10.10.1:8080", 0.2, ngx_now - 1)
|
||||
assert_ewma_stats("10.10.10.2:8080", 0.3, ngx_now - 5)
|
||||
assert_ewma_stats("10.10.10.3:8080", 1.2, ngx_now - 20)
|
||||
end)
|
||||
|
||||
it("updates peers, deletes stats for old endpoints and sets average ewma score to new ones", function()
|
||||
local new_backend = util.deepcopy(backend)
|
||||
|
||||
-- existing endpoint 10.10.10.2 got deleted
|
||||
-- and replaced with 10.10.10.4
|
||||
new_backend.endpoints[2].address = "10.10.10.4"
|
||||
-- and there's one new extra endpoint
|
||||
table.insert(new_backend.endpoints, { address = "10.10.10.5", port = "8080", maxFails = 0, failTimeout = 0 })
|
||||
|
||||
instance:sync(new_backend)
|
||||
|
||||
assert.are.same(new_backend.endpoints, instance.peers)
|
||||
|
||||
assert_ewma_stats("10.10.10.1:8080", 0.2, ngx_now - 1)
|
||||
assert_ewma_stats("10.10.10.2:8080", nil, nil)
|
||||
assert_ewma_stats("10.10.10.3:8080", 1.2, ngx_now - 20)
|
||||
|
||||
local slow_start_ewma = (0.2 + 1.2) / 2
|
||||
assert_ewma_stats("10.10.10.4:8080", slow_start_ewma, ngx_now)
|
||||
assert_ewma_stats("10.10.10.5:8080", slow_start_ewma, ngx_now)
|
||||
end)
|
||||
|
||||
it("does not set slow_start_ewma when there is no existing ewma", function()
|
||||
local new_backend = util.deepcopy(backend)
|
||||
table.insert(new_backend.endpoints, { address = "10.10.10.4", port = "8080", maxFails = 0, failTimeout = 0 })
|
||||
|
||||
-- when the LB algorithm instance is just instantiated it won't have any
|
||||
-- ewma value set for the initial endpoints (because it has not processed any request yet),
|
||||
-- this test is trying to simulate that by flushing existing ewma values
|
||||
flush_all_ewma_stats()
|
||||
|
||||
instance:sync(new_backend)
|
||||
|
||||
assert_ewma_stats("10.10.10.1:8080", nil, nil)
|
||||
assert_ewma_stats("10.10.10.2:8080", nil, nil)
|
||||
assert_ewma_stats("10.10.10.3:8080", nil, nil)
|
||||
assert_ewma_stats("10.10.10.4:8080", nil, nil)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ local function reset_backends()
|
|||
backends = {
|
||||
{
|
||||
name = "access-router-production-web-80", port = "80", secure = false,
|
||||
secureCACert = { secret = "", caFilename = "", pemSha = "" },
|
||||
secureCACert = { secret = "", caFilename = "", caSha = "" },
|
||||
sslPassthrough = false,
|
||||
endpoints = {
|
||||
{ address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 },
|
||||
|
|
@ -49,7 +49,7 @@ local function reset_backends()
|
|||
},
|
||||
},
|
||||
{ name = "my-dummy-app-1", ["load-balance"] = "round_robin", },
|
||||
{
|
||||
{
|
||||
name = "my-dummy-app-2", ["load-balance"] = "chash",
|
||||
upstreamHashByConfig = { ["upstream-hash-by"] = "$request_uri", },
|
||||
},
|
||||
|
|
@ -86,6 +86,44 @@ describe("Balancer", function()
|
|||
end)
|
||||
end)
|
||||
|
||||
describe("get_balancer()", function()
|
||||
it("always returns the same balancer for given request context", function()
|
||||
local backend = {
|
||||
name = "my-dummy-app-6", ["load-balance"] = "ewma",
|
||||
alternativeBackends = { "my-dummy-canary-app-6" },
|
||||
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } },
|
||||
trafficShapingPolicy = {
|
||||
weight = 0,
|
||||
header = "",
|
||||
headerValue = "",
|
||||
cookie = ""
|
||||
},
|
||||
}
|
||||
local canary_backend = {
|
||||
name = "my-dummy-canary-app-6", ["load-balance"] = "ewma",
|
||||
alternativeBackends = { "my-dummy-canary-app-6" },
|
||||
endpoints = { { address = "11.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } },
|
||||
trafficShapingPolicy = {
|
||||
weight = 5,
|
||||
header = "",
|
||||
headerValue = "",
|
||||
cookie = ""
|
||||
},
|
||||
}
|
||||
|
||||
balancer.sync_backend(backend)
|
||||
balancer.sync_backend(canary_backend)
|
||||
|
||||
mock_ngx({ var = { proxy_upstream_name = backend.name } })
|
||||
|
||||
local expected = balancer.get_balancer()
|
||||
|
||||
for i = 1,50,1 do
|
||||
assert.are.same(expected, balancer.get_balancer())
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("route_to_alternative_balancer()", function()
|
||||
local backend, _balancer
|
||||
|
||||
|
|
@ -277,8 +315,7 @@ describe("Balancer", function()
|
|||
}
|
||||
}
|
||||
|
||||
local dns_helper = require("test/dns_helper")
|
||||
dns_helper.mock_dns_query({
|
||||
helpers.mock_resty_dns_query(nil, {
|
||||
{
|
||||
name = "example.com",
|
||||
address = "192.168.1.1",
|
||||
|
|
|
|||
|
|
@ -115,15 +115,20 @@ describe("Configuration", function()
|
|||
end)
|
||||
|
||||
it("returns a status of 400", function()
|
||||
local original_io_open = _G.io.open
|
||||
_G.io.open = function(filename, extension) return false end
|
||||
assert.has_no.errors(configuration.call)
|
||||
assert.equal(ngx.status, ngx.HTTP_BAD_REQUEST)
|
||||
_G.io.open = original_io_open
|
||||
end)
|
||||
|
||||
it("logs 'dynamic-configuration: unable to read valid request body to stderr'", function()
|
||||
local original_io_open = _G.io.open
|
||||
_G.io.open = function(filename, extension) return false end
|
||||
local s = spy.on(ngx, "log")
|
||||
assert.has_no.errors(configuration.call)
|
||||
assert.spy(s).was_called_with(ngx.ERR, "dynamic-configuration: unable to read valid request body")
|
||||
_G.io.open = original_io_open
|
||||
end)
|
||||
end)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
local _M = {}
|
||||
|
||||
local configuration = require("configuration")
|
||||
local resolver = require("resty.dns.resolver")
|
||||
local old_resolver_new = resolver.new
|
||||
|
||||
local function reset(nameservers)
|
||||
configuration.nameservers = nameservers or { "1.1.1.1" }
|
||||
end
|
||||
|
||||
function _M.mock_new(func, nameservers)
|
||||
reset(nameservers)
|
||||
resolver.new = func
|
||||
end
|
||||
|
||||
function _M.mock_dns_query(response, err)
|
||||
reset()
|
||||
resolver.new = function(self, options)
|
||||
local r = old_resolver_new(self, options)
|
||||
r.query = function(self, name, options, tries)
|
||||
return response, err
|
||||
end
|
||||
return r
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
||||
50
rootfs/etc/nginx/lua/test/helpers.lua
Normal file
50
rootfs/etc/nginx/lua/test/helpers.lua
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
local _M = {}
|
||||
|
||||
local resty_dns_resolver = require("resty.dns.resolver")
|
||||
|
||||
local original_resty_dns_resolver_new = resty_dns_resolver.new
|
||||
local original_io_open = io.open
|
||||
|
||||
function _M.with_resolv_conf(content, func)
|
||||
local new_resolv_conf_f = assert(io.tmpfile())
|
||||
new_resolv_conf_f:write(content)
|
||||
new_resolv_conf_f:seek("set", 0)
|
||||
|
||||
io.open = function(path, mode)
|
||||
if path ~= "/etc/resolv.conf" then
|
||||
error("expected '/etc/resolv.conf' as path but got: " .. tostring(path))
|
||||
end
|
||||
if mode ~= "r" then
|
||||
error("expected 'r' as mode but got: " .. tostring(mode))
|
||||
end
|
||||
|
||||
return new_resolv_conf_f, nil
|
||||
end
|
||||
|
||||
func()
|
||||
|
||||
io.open = original_io_open
|
||||
|
||||
if io.type(new_resolv_conf_f) ~= "closed file" then
|
||||
error("file was left open")
|
||||
end
|
||||
end
|
||||
|
||||
function _M.mock_resty_dns_new(func)
|
||||
resty_dns_resolver.new = func
|
||||
end
|
||||
|
||||
function _M.mock_resty_dns_query(mocked_host, response, err)
|
||||
resty_dns_resolver.new = function(self, options)
|
||||
local r = original_resty_dns_resolver_new(self, options)
|
||||
r.query = function(self, host, options, tries)
|
||||
if mocked_host and mocked_host ~= host then
|
||||
return error(tostring(host) .. " is not mocked")
|
||||
end
|
||||
return response, err
|
||||
end
|
||||
return r
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
||||
|
|
@ -11,6 +11,7 @@ do
|
|||
-- if there's more constants need to be whitelisted for test runs, add here.
|
||||
local GLOBALS_ALLOWED_IN_TEST = {
|
||||
_TEST = true,
|
||||
helpers = true,
|
||||
}
|
||||
local newindex = function(table, key, value)
|
||||
rawset(table, key, value)
|
||||
|
|
@ -33,6 +34,8 @@ do
|
|||
setmetatable(_G, { __newindex = newindex })
|
||||
end
|
||||
|
||||
_G.helpers = require("test.helpers")
|
||||
_G._TEST = true
|
||||
|
||||
local ffi = require("ffi")
|
||||
local lua_ingress = require("lua_ingress")
|
||||
|
|
|
|||
|
|
@ -1,43 +1,89 @@
|
|||
describe("resolve", function()
|
||||
local dns = require("util.dns")
|
||||
local dns_helper = require("test/dns_helper")
|
||||
local conf = [===[
|
||||
nameserver 1.2.3.4
|
||||
nameserver 4.5.6.7
|
||||
search ingress-nginx.svc.cluster.local svc.cluster.local cluster.local
|
||||
options ndots:5
|
||||
]===]
|
||||
|
||||
helpers.with_resolv_conf(conf, function()
|
||||
require("util.resolv_conf")
|
||||
end)
|
||||
|
||||
describe("dns.lookup", function()
|
||||
local dns, dns_lookup, spy_ngx_log
|
||||
|
||||
before_each(function()
|
||||
spy_ngx_log = spy.on(ngx, "log")
|
||||
dns = require("util.dns")
|
||||
dns_lookup = dns.lookup
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
package.loaded["util.dns"] = nil
|
||||
end)
|
||||
|
||||
it("sets correct nameservers", function()
|
||||
dns_helper.mock_new(function(self, options)
|
||||
helpers.mock_resty_dns_new(function(self, options)
|
||||
assert.are.same({ nameservers = { "1.2.3.4", "4.5.6.7" }, retrans = 5, timeout = 2000 }, options)
|
||||
return nil, ""
|
||||
end, { "1.2.3.4", "4.5.6.7" })
|
||||
dns.resolve("example.com")
|
||||
end)
|
||||
dns_lookup("example.com")
|
||||
end)
|
||||
|
||||
it("returns host when an error happens", function()
|
||||
local s_ngx_log = spy.on(ngx, "log")
|
||||
describe("when there's an error", function()
|
||||
it("returns host when resolver can not be instantiated", function()
|
||||
helpers.mock_resty_dns_new(function(...) return nil, "an error" end)
|
||||
assert.are.same({ "example.com" }, dns_lookup("example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to instantiate the resolver: an error")
|
||||
end)
|
||||
|
||||
dns_helper.mock_new(function(...) return nil, "an error" end)
|
||||
assert.are.same({ "example.com" }, dns.resolve("example.com"))
|
||||
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to instantiate the resolver: an error")
|
||||
it("returns host when the query returns nil", function()
|
||||
helpers.mock_resty_dns_query(nil, nil, "oops!")
|
||||
assert.are.same({ "example.com" }, dns_lookup("example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
|
||||
end)
|
||||
|
||||
dns_helper.mock_dns_query(nil, "oops!")
|
||||
assert.are.same({ "example.com" }, dns.resolve("example.com"))
|
||||
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
|
||||
it("returns host when the query returns empty answer", function()
|
||||
helpers.mock_resty_dns_query(nil, {})
|
||||
assert.are.same({ "example.com" }, dns_lookup("example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno A record resolved\nno AAAA record resolved")
|
||||
end)
|
||||
|
||||
dns_helper.mock_dns_query({ errcode = 1, errstr = "format error" })
|
||||
assert.are.same({ "example.com" }, dns.resolve("example.com"))
|
||||
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nserver returned error code: 1: format error\nserver returned error code: 1: format error")
|
||||
it("returns host when there's answer but with error", function()
|
||||
helpers.mock_resty_dns_query(nil, { errcode = 1, errstr = "format error" })
|
||||
assert.are.same({ "example.com" }, dns_lookup("example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\n" ..
|
||||
"server returned error code: 1: format error\nserver returned error code: 1: format error")
|
||||
end)
|
||||
|
||||
dns_helper.mock_dns_query({})
|
||||
assert.are.same({ "example.com" }, dns.resolve("example.com"))
|
||||
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno record resolved\nno record resolved")
|
||||
it("retuns host when there's answer but no A/AAAA record in it", function()
|
||||
helpers.mock_resty_dns_query(nil, { { name = "example.com", cname = "sub.example.com", ttl = 60 } })
|
||||
assert.are.same({ "example.com" }, dns_lookup("example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno A record resolved\nno AAAA record resolved")
|
||||
end)
|
||||
|
||||
dns_helper.mock_dns_query({ { name = "example.com", cname = "sub.example.com", ttl = 60 } })
|
||||
assert.are.same({ "example.com" }, dns.resolve("example.com"))
|
||||
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno record resolved\nno record resolved")
|
||||
it("returns host when the query returns nil and number of dots is not less than configured ndots", function()
|
||||
helpers.mock_resty_dns_query(nil, nil, "oops!")
|
||||
assert.are.same({ "a.b.c.d.example.com" }, dns_lookup("a.b.c.d.example.com"))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
|
||||
end)
|
||||
|
||||
it("returns host when the query returns nil for a fully qualified domain", function()
|
||||
helpers.mock_resty_dns_query("example.com.", nil, "oops!")
|
||||
assert.are.same({ "example.com." }, dns_lookup("example.com."))
|
||||
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
|
||||
end)
|
||||
end)
|
||||
|
||||
it("resolves all A records of given host, caches them with minimal ttl and returns from cache next time", function()
|
||||
dns_helper.mock_dns_query({
|
||||
it("returns answer from cache if it exists without doing actual DNS query", function()
|
||||
dns._cache:set("example.com", { "192.168.1.1" })
|
||||
assert.are.same({ "192.168.1.1" }, dns_lookup("example.com"))
|
||||
end)
|
||||
|
||||
it("resolves a fully qualified domain without looking at resolv.conf search and caches result", function()
|
||||
helpers.mock_resty_dns_query("example.com.", {
|
||||
{
|
||||
name = "example.com",
|
||||
name = "example.com.",
|
||||
address = "192.168.1.1",
|
||||
ttl = 3600,
|
||||
},
|
||||
|
|
@ -47,28 +93,43 @@ describe("resolve", function()
|
|||
ttl = 60,
|
||||
}
|
||||
})
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns_lookup("example.com."))
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns._cache:get("example.com."))
|
||||
end)
|
||||
|
||||
local lrucache = require("resty.lrucache")
|
||||
local old_lrucache_new = lrucache.new
|
||||
lrucache.new = function(...)
|
||||
local cache = old_lrucache_new(...)
|
||||
it("starts with host itself when number of dots is not less than configured ndots", function()
|
||||
local host = "a.b.c.d.example.com"
|
||||
helpers.mock_resty_dns_query(host, { { name = host, address = "192.168.1.1", ttl = 3600, } } )
|
||||
|
||||
local old_set = cache.set
|
||||
cache.set = function(self, key, value, ttl)
|
||||
assert.equal("example.com", key)
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, value)
|
||||
assert.equal(60, ttl)
|
||||
return old_set(self, key, value, ttl)
|
||||
end
|
||||
assert.are.same({ "192.168.1.1" }, dns_lookup(host))
|
||||
assert.are.same({ "192.168.1.1" }, dns._cache:get(host))
|
||||
end)
|
||||
|
||||
return cache
|
||||
end
|
||||
it("starts with first search entry when number of dots is less than configured ndots", function()
|
||||
local host = "example.com.ingress-nginx.svc.cluster.local"
|
||||
helpers.mock_resty_dns_query(host, { { name = host, address = "192.168.1.1", ttl = 3600, } } )
|
||||
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns.resolve("example.com"))
|
||||
assert.are.same({ "192.168.1.1" }, dns_lookup(host))
|
||||
assert.are.same({ "192.168.1.1" }, dns._cache:get(host))
|
||||
end)
|
||||
|
||||
dns_helper.mock_new(function(...)
|
||||
error("expected to short-circuit and return response from cache")
|
||||
end)
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns.resolve("example.com"))
|
||||
it("it caches with minimal ttl", function()
|
||||
helpers.mock_resty_dns_query("example.com.", {
|
||||
{
|
||||
name = "example.com.",
|
||||
address = "192.168.1.1",
|
||||
ttl = 3600,
|
||||
},
|
||||
{
|
||||
name = "example.com.",
|
||||
address = "1.2.3.4",
|
||||
ttl = 60,
|
||||
}
|
||||
})
|
||||
|
||||
local spy_cache_set = spy.on(dns._cache, "set")
|
||||
|
||||
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns_lookup("example.com."))
|
||||
assert.spy(spy_cache_set).was_called_with(match.is_table(), "example.com.", { "192.168.1.1", "1.2.3.4" }, 60)
|
||||
end)
|
||||
end)
|
||||
|
|
|
|||
64
rootfs/etc/nginx/lua/test/util/resolv_conf_test.lua
Normal file
64
rootfs/etc/nginx/lua/test/util/resolv_conf_test.lua
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
local original_io_open = io.open
|
||||
|
||||
describe("resolv_conf", function()
|
||||
after_each(function()
|
||||
package.loaded["util.resolv_conf"] = nil
|
||||
io.open = original_io_open
|
||||
end)
|
||||
|
||||
it("errors when file can not be opened", function()
|
||||
io.open = function(...)
|
||||
return nil, "file does not exist"
|
||||
end
|
||||
|
||||
assert.has_error(function() require("util.resolv_conf") end, "could not open /etc/resolv.conf: file does not exist")
|
||||
end)
|
||||
|
||||
it("opens '/etc/resolv.conf' with mode 'r'", function()
|
||||
io.open = function(path, mode)
|
||||
assert.are.same("/etc/resolv.conf", path)
|
||||
assert.are.same("r", mode)
|
||||
|
||||
return original_io_open(path, mode)
|
||||
end
|
||||
|
||||
assert.has_no.errors(function() require("util.resolv_conf") end)
|
||||
end)
|
||||
|
||||
it("correctly parses resolv.conf", function()
|
||||
local conf = [===[
|
||||
# This is a comment
|
||||
nameserver 10.96.0.10
|
||||
nameserver 10.96.0.99
|
||||
search ingress-nginx.svc.cluster.local svc.cluster.local cluster.local
|
||||
options ndots:5
|
||||
]===]
|
||||
|
||||
helpers.with_resolv_conf(conf, function()
|
||||
local resolv_conf = require("util.resolv_conf")
|
||||
assert.are.same({
|
||||
nameservers = { "10.96.0.10", "10.96.0.99" },
|
||||
search = { "ingress-nginx.svc.cluster.local", "svc.cluster.local", "cluster.local" },
|
||||
ndots = 5,
|
||||
}, resolv_conf)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("ignores options that it does not understand", function()
|
||||
local conf = [===[
|
||||
nameserver 10.96.0.10
|
||||
search example.com
|
||||
options debug
|
||||
options ndots:3
|
||||
]===]
|
||||
|
||||
helpers.with_resolv_conf(conf, function()
|
||||
local resolv_conf = require("util.resolv_conf")
|
||||
assert.are.same({
|
||||
nameservers = { "10.96.0.10" },
|
||||
search = { "example.com" },
|
||||
ndots = 3,
|
||||
}, resolv_conf)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -12,24 +12,87 @@ end
|
|||
describe("lua_ngx_var", function()
|
||||
local util = require("util")
|
||||
|
||||
before_each(function()
|
||||
mock_ngx({ var = { remote_addr = "192.168.1.1", [1] = "nginx/regexp/1/group/capturing" } })
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
reset_ngx()
|
||||
package.loaded["monitor"] = nil
|
||||
end)
|
||||
|
||||
it("returns value of nginx var by key", function()
|
||||
assert.equal("192.168.1.1", util.lua_ngx_var("$remote_addr"))
|
||||
describe("lua_ngx_var", function()
|
||||
before_each(function()
|
||||
mock_ngx({ var = { remote_addr = "192.168.1.1", [1] = "nginx/regexp/1/group/capturing" } })
|
||||
end)
|
||||
|
||||
it("returns value of nginx var by key", function()
|
||||
assert.equal("192.168.1.1", util.lua_ngx_var("$remote_addr"))
|
||||
end)
|
||||
|
||||
it("returns value of nginx var when key is number", function()
|
||||
assert.equal("nginx/regexp/1/group/capturing", util.lua_ngx_var("$1"))
|
||||
end)
|
||||
|
||||
it("returns nil when variable is not defined", function()
|
||||
assert.equal(nil, util.lua_ngx_var("$foo_bar"))
|
||||
end)
|
||||
end)
|
||||
|
||||
it("returns value of nginx var when key is number", function()
|
||||
assert.equal("nginx/regexp/1/group/capturing", util.lua_ngx_var("$1"))
|
||||
end)
|
||||
describe("diff_endpoints", function()
|
||||
it("returns removed and added endpoints", function()
|
||||
local old = {
|
||||
{ address = "10.10.10.1", port = "8080" },
|
||||
{ address = "10.10.10.2", port = "8080" },
|
||||
{ address = "10.10.10.3", port = "8080" },
|
||||
}
|
||||
local new = {
|
||||
{ address = "10.10.10.1", port = "8080" },
|
||||
{ address = "10.10.10.2", port = "8081" },
|
||||
{ address = "11.10.10.2", port = "8080" },
|
||||
{ address = "11.10.10.3", port = "8080" },
|
||||
}
|
||||
local expected_added = { "10.10.10.2:8081", "11.10.10.2:8080", "11.10.10.3:8080" }
|
||||
table.sort(expected_added)
|
||||
local expected_removed = { "10.10.10.2:8080", "10.10.10.3:8080" }
|
||||
table.sort(expected_removed)
|
||||
|
||||
it("returns nil when variable is not defined", function()
|
||||
assert.equal(nil, util.lua_ngx_var("$foo_bar"))
|
||||
local added, removed = util.diff_endpoints(old, new)
|
||||
table.sort(added)
|
||||
table.sort(removed)
|
||||
|
||||
assert.are.same(expected_added, added)
|
||||
assert.are.same(expected_removed, removed)
|
||||
end)
|
||||
|
||||
it("returns empty results for empty inputs", function()
|
||||
local added, removed = util.diff_endpoints({}, {})
|
||||
|
||||
assert.are.same({}, added)
|
||||
assert.are.same({}, removed)
|
||||
end)
|
||||
|
||||
it("returns empty results for same inputs", function()
|
||||
local old = {
|
||||
{ address = "10.10.10.1", port = "8080" },
|
||||
{ address = "10.10.10.2", port = "8080" },
|
||||
{ address = "10.10.10.3", port = "8080" },
|
||||
}
|
||||
local new = util.deepcopy(old)
|
||||
|
||||
local added, removed = util.diff_endpoints(old, new)
|
||||
|
||||
assert.are.same({}, added)
|
||||
assert.are.same({}, removed)
|
||||
end)
|
||||
|
||||
it("handles endpoints with nil attribute", function()
|
||||
local old = {
|
||||
{ address = nil, port = "8080" },
|
||||
{ address = "10.10.10.2", port = "8080" },
|
||||
{ address = "10.10.10.3", port = "8080" },
|
||||
}
|
||||
local new = util.deepcopy(old)
|
||||
new[2].port = nil
|
||||
|
||||
local added, removed = util.diff_endpoints(old, new)
|
||||
assert.are.same({ "10.10.10.2:nil" }, added)
|
||||
assert.are.same({ "10.10.10.2:8080" }, removed)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
local string_len = string.len
|
||||
local string_sub = string.sub
|
||||
local string_format = string.format
|
||||
|
||||
local _M = {}
|
||||
|
||||
|
|
@ -26,6 +27,44 @@ function _M.lua_ngx_var(ngx_var)
|
|||
return ngx.var[var_name]
|
||||
end
|
||||
|
||||
-- normalize_endpoints takes endpoints as an array of endpoint objects
|
||||
-- and returns a table where keys are string that's endpoint.address .. ":" .. endpoint.port
|
||||
-- and values are all true
|
||||
local function normalize_endpoints(endpoints)
|
||||
local normalized_endpoints = {}
|
||||
|
||||
for _, endpoint in pairs(endpoints) do
|
||||
local endpoint_string = string_format("%s:%s", endpoint.address, endpoint.port)
|
||||
normalized_endpoints[endpoint_string] = true
|
||||
end
|
||||
|
||||
return normalized_endpoints
|
||||
end
|
||||
|
||||
-- diff_endpoints compares old and new
|
||||
-- and as a first argument returns what endpoints are in new
|
||||
-- but are not in old, and as a second argument it returns
|
||||
-- what endpoints are in old but are in new.
|
||||
-- Both return values are normalized (ip:port).
|
||||
function _M.diff_endpoints(old, new)
|
||||
local endpoints_added, endpoints_removed = {}, {}
|
||||
local normalized_old, normalized_new = normalize_endpoints(old), normalize_endpoints(new)
|
||||
|
||||
for endpoint_string, _ in pairs(normalized_old) do
|
||||
if not normalized_new[endpoint_string] then
|
||||
table.insert(endpoints_removed, endpoint_string)
|
||||
end
|
||||
end
|
||||
|
||||
for endpoint_string, _ in pairs(normalized_new) do
|
||||
if not normalized_old[endpoint_string] then
|
||||
table.insert(endpoints_added, endpoint_string)
|
||||
end
|
||||
end
|
||||
|
||||
return endpoints_added, endpoints_removed
|
||||
end
|
||||
|
||||
-- this implementation is taken from
|
||||
-- https://web.archive.org/web/20131225070434/http://snippets.luacode.org/snippets/Deep_Comparison_of_Two_Values_3
|
||||
-- and modified for use in this project
|
||||
|
|
|
|||
|
|
@ -1,25 +1,49 @@
|
|||
local resolver = require("resty.dns.resolver")
|
||||
local lrucache = require("resty.lrucache")
|
||||
local configuration = require("configuration")
|
||||
local util = require("util")
|
||||
local resolv_conf = require("util.resolv_conf")
|
||||
|
||||
local ngx_log = ngx.log
|
||||
local ngx_INFO = ngx.INFO
|
||||
local ngx_ERR = ngx.ERR
|
||||
local string_format = string.format
|
||||
local table_concat = table.concat
|
||||
local table_insert = table.insert
|
||||
local ipairs = ipairs
|
||||
local tostring = tostring
|
||||
|
||||
local _M = {}
|
||||
local CACHE_SIZE = 10000
|
||||
local MAXIMUM_TTL_VALUE = 2147483647 -- maximum value according to https://tools.ietf.org/html/rfc2181
|
||||
-- for every host we will try two queries for the following types with the order set here
|
||||
local QTYPES_TO_CHECK = { resolver.TYPE_A, resolver.TYPE_AAAA }
|
||||
|
||||
local cache, err = lrucache.new(CACHE_SIZE)
|
||||
if not cache then
|
||||
return error("failed to create the cache: " .. (err or "unknown"))
|
||||
local cache
|
||||
do
|
||||
local err
|
||||
cache, err = lrucache.new(CACHE_SIZE)
|
||||
if not cache then
|
||||
return error("failed to create the cache: " .. (err or "unknown"))
|
||||
end
|
||||
end
|
||||
|
||||
local function a_records_and_max_ttl(answers)
|
||||
local function cache_set(host, addresses, ttl)
|
||||
cache:set(host, addresses, ttl)
|
||||
ngx_log(ngx_INFO, string_format("cache set for '%s' with value of [%s] and ttl of %s.",
|
||||
host, table_concat(addresses, ", "), ttl))
|
||||
end
|
||||
|
||||
local function is_fully_qualified(host)
|
||||
return host:sub(-1) == "."
|
||||
end
|
||||
|
||||
local function a_records_and_min_ttl(answers)
|
||||
local addresses = {}
|
||||
local ttl = MAXIMUM_TTL_VALUE -- maximum value according to https://tools.ietf.org/html/rfc2181
|
||||
|
||||
for _, ans in ipairs(answers) do
|
||||
if ans.address then
|
||||
table.insert(addresses, ans.address)
|
||||
if ttl > ans.ttl then
|
||||
table_insert(addresses, ans.address)
|
||||
if ans.ttl < ttl then
|
||||
ttl = ans.ttl
|
||||
end
|
||||
end
|
||||
|
|
@ -28,71 +52,109 @@ local function a_records_and_max_ttl(answers)
|
|||
return addresses, ttl
|
||||
end
|
||||
|
||||
local function resolve_host(host, r, qtype)
|
||||
local answers
|
||||
answers, err = r:query(host, { qtype = qtype }, {})
|
||||
local function resolve_host_for_qtype(r, host, qtype)
|
||||
ngx_log(ngx_INFO, string_format("resolving %s with qtype %s", host, qtype))
|
||||
|
||||
local answers, err = r:query(host, { qtype = qtype }, {})
|
||||
if not answers then
|
||||
return nil, -1, tostring(err)
|
||||
return nil, -1, err
|
||||
end
|
||||
|
||||
if answers.errcode then
|
||||
return nil, -1, string.format("server returned error code: %s: %s", answers.errcode, answers.errstr)
|
||||
return nil, -1, string_format("server returned error code: %s: %s", answers.errcode, answers.errstr)
|
||||
end
|
||||
|
||||
local addresses, ttl = a_records_and_max_ttl(answers)
|
||||
local addresses, ttl = a_records_and_min_ttl(answers)
|
||||
if #addresses == 0 then
|
||||
return nil, -1, "no record resolved"
|
||||
local msg = "no A record resolved"
|
||||
if qtype == resolver.TYPE_AAAA then msg = "no AAAA record resolved" end
|
||||
return nil, -1, msg
|
||||
end
|
||||
|
||||
return addresses, ttl, nil
|
||||
end
|
||||
|
||||
function _M.resolve(host)
|
||||
local function resolve_host(r, host)
|
||||
local dns_errors = {}
|
||||
|
||||
for _, qtype in ipairs(QTYPES_TO_CHECK) do
|
||||
local addresses, ttl, err = resolve_host_for_qtype(r, host, qtype)
|
||||
if addresses and #addresses > 0 then
|
||||
return addresses, ttl, nil
|
||||
end
|
||||
table_insert(dns_errors, tostring(err))
|
||||
end
|
||||
|
||||
return nil, nil, dns_errors
|
||||
end
|
||||
|
||||
function _M.lookup(host)
|
||||
local cached_addresses = cache:get(host)
|
||||
if cached_addresses then
|
||||
local message = string.format(
|
||||
"addresses %s for host %s was resolved from cache",
|
||||
table.concat(cached_addresses, ", "), host)
|
||||
ngx.log(ngx.INFO, message)
|
||||
return cached_addresses
|
||||
end
|
||||
|
||||
local r
|
||||
r, err = resolver:new{
|
||||
nameservers = util.deepcopy(configuration.nameservers),
|
||||
local r, err = resolver:new{
|
||||
nameservers = resolv_conf.nameservers,
|
||||
retrans = 5,
|
||||
timeout = 2000, -- 2 sec
|
||||
}
|
||||
|
||||
if not r then
|
||||
ngx.log(ngx.ERR, "failed to instantiate the resolver: " .. tostring(err))
|
||||
ngx_log(ngx_ERR, string_format("failed to instantiate the resolver: %s", err))
|
||||
return { host }
|
||||
end
|
||||
|
||||
local dns_errors = {}
|
||||
local addresses, ttl, dns_errors
|
||||
|
||||
local addresses, ttl
|
||||
addresses, ttl, err = resolve_host(host, r, r.TYPE_A)
|
||||
if not addresses then
|
||||
table.insert(dns_errors, tostring(err))
|
||||
elseif #addresses > 0 then
|
||||
cache:set(host, addresses, ttl)
|
||||
return addresses
|
||||
-- when the queried domain is fully qualified
|
||||
-- then we don't go through resolv_conf.search
|
||||
-- NOTE(elvinefendi): currently FQDN as externalName will be supported starting
|
||||
-- with K8s 1.15: https://github.com/kubernetes/kubernetes/pull/78385
|
||||
if is_fully_qualified(host) then
|
||||
addresses, ttl, dns_errors = resolve_host(r, host)
|
||||
if addresses then
|
||||
cache_set(host, addresses, ttl)
|
||||
return addresses
|
||||
end
|
||||
|
||||
ngx_log(ngx_ERR, "failed to query the DNS server:\n" .. table_concat(dns_errors, "\n"))
|
||||
|
||||
return { host }
|
||||
end
|
||||
|
||||
addresses, ttl, err = resolve_host(host, r, r.TYPE_AAAA)
|
||||
if not addresses then
|
||||
table.insert(dns_errors, tostring(err))
|
||||
elseif #addresses > 0 then
|
||||
cache:set(host, addresses, ttl)
|
||||
return addresses
|
||||
-- for non fully qualified domains if number of dots in
|
||||
-- the queried host is less than resolv_conf.ndots then we try
|
||||
-- with all the entries in resolv_conf.search before trying the original host
|
||||
--
|
||||
-- if number of dots is not less than resolv_conf.ndots then we start with
|
||||
-- the original host and then try entries in resolv_conf.search
|
||||
local _, host_ndots = host:gsub("%.", "")
|
||||
local search_start, search_end = 0, #resolv_conf.search
|
||||
if host_ndots < resolv_conf.ndots then
|
||||
search_start = 1
|
||||
search_end = #resolv_conf.search + 1
|
||||
end
|
||||
|
||||
for i = search_start, search_end, 1 do
|
||||
local new_host = resolv_conf.search[i] and string_format("%s.%s", host, resolv_conf.search[i]) or host
|
||||
|
||||
addresses, ttl, dns_errors = resolve_host(r, new_host)
|
||||
if addresses then
|
||||
cache_set(host, addresses, ttl)
|
||||
return addresses
|
||||
end
|
||||
end
|
||||
|
||||
if #dns_errors > 0 then
|
||||
ngx.log(ngx.ERR, "failed to query the DNS server:\n" .. table.concat(dns_errors, "\n"))
|
||||
ngx_log(ngx_ERR, "failed to query the DNS server:\n" .. table_concat(dns_errors, "\n"))
|
||||
end
|
||||
|
||||
return { host }
|
||||
end
|
||||
|
||||
if _TEST then
|
||||
_M._cache = cache
|
||||
end
|
||||
|
||||
return _M
|
||||
|
|
|
|||
79
rootfs/etc/nginx/lua/util/resolv_conf.lua
Normal file
79
rootfs/etc/nginx/lua/util/resolv_conf.lua
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
local ngx_re_split = require("ngx.re").split
|
||||
|
||||
local ngx_log = ngx.log
|
||||
local ngx_ERR = ngx.ERR
|
||||
|
||||
local CONF_PATH = "/etc/resolv.conf"
|
||||
|
||||
local nameservers, search, ndots = {}, {}, 1
|
||||
|
||||
local function set_search(parts)
|
||||
local length = #parts
|
||||
|
||||
for i = 2, length, 1 do
|
||||
search[i-1] = parts[i]
|
||||
end
|
||||
end
|
||||
|
||||
local function set_ndots(parts)
|
||||
local option = parts[2]
|
||||
if not option then
|
||||
return
|
||||
end
|
||||
|
||||
local option_parts, err = ngx_re_split(option, ":")
|
||||
if err then
|
||||
ngx_log(ngx_ERR, err)
|
||||
return
|
||||
end
|
||||
|
||||
if option_parts[1] ~= "ndots" then
|
||||
return
|
||||
end
|
||||
|
||||
ndots = tonumber(option_parts[2])
|
||||
end
|
||||
|
||||
local function is_comment(line)
|
||||
return line:sub(1, 1) == "#"
|
||||
end
|
||||
|
||||
local function parse_line(line)
|
||||
if is_comment(line) then
|
||||
return
|
||||
end
|
||||
|
||||
local parts, err = ngx_re_split(line, "\\s+")
|
||||
if err then
|
||||
ngx_log(ngx_ERR, err)
|
||||
end
|
||||
|
||||
local keyword, value = parts[1], parts[2]
|
||||
|
||||
if keyword == "nameserver" then
|
||||
nameservers[#nameservers + 1] = value
|
||||
elseif keyword == "search" then
|
||||
set_search(parts)
|
||||
elseif keyword == "options" then
|
||||
set_ndots(parts)
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local f, err = io.open(CONF_PATH, "r")
|
||||
if not f then
|
||||
error("could not open " .. CONF_PATH .. ": " .. tostring(err))
|
||||
end
|
||||
|
||||
for line in f:lines() do
|
||||
parse_line(line)
|
||||
end
|
||||
|
||||
f:close()
|
||||
end
|
||||
|
||||
return {
|
||||
nameservers = nameservers,
|
||||
search = search,
|
||||
ndots = ndots,
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ http {
|
|||
lua_package_path "/usr/local/openresty/site/lualib/?.ljbc;/usr/local/openresty/site/lualib/?/init.ljbc;/usr/local/openresty/lualib/?.ljbc;/usr/local/openresty/lualib/?/init.ljbc;/usr/local/openresty/site/lualib/?.lua;/usr/local/openresty/site/lualib/?/init.lua;/usr/local/openresty/lualib/?.lua;/usr/local/openresty/lualib/?/init.lua;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua;/usr/local/lib/lua/?.lua;;";
|
||||
lua_package_cpath "/usr/local/openresty/site/lualib/?.so;/usr/local/openresty/lualib/?.so;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;;";
|
||||
|
||||
{{ buildLuaSharedDictionaries $servers $all.Cfg.DisableLuaRestyWAF }}
|
||||
{{ buildLuaSharedDictionaries $cfg $servers $all.Cfg.DisableLuaRestyWAF }}
|
||||
|
||||
init_by_lua_block {
|
||||
collectgarbage("collect")
|
||||
|
|
@ -77,7 +77,6 @@ http {
|
|||
error("require failed: " .. tostring(res))
|
||||
else
|
||||
configuration = res
|
||||
configuration.nameservers = { {{ buildResolversForLua $cfg.Resolver $cfg.DisableIpv6DNS }} }
|
||||
end
|
||||
|
||||
ok, res = pcall(require, "balancer")
|
||||
|
|
@ -96,14 +95,12 @@ http {
|
|||
end
|
||||
{{ end }}
|
||||
|
||||
{{ if $all.EnableDynamicCertificates }}
|
||||
ok, res = pcall(require, "certificate")
|
||||
if not ok then
|
||||
error("require failed: " .. tostring(res))
|
||||
else
|
||||
certificate = res
|
||||
end
|
||||
{{ end }}
|
||||
|
||||
ok, res = pcall(require, "plugins")
|
||||
if not ok then
|
||||
|
|
@ -172,6 +169,7 @@ http {
|
|||
$geoip2_dma_code source=$the_real_ip location metro_code;
|
||||
$geoip2_latitude source=$the_real_ip location latitude;
|
||||
$geoip2_longitude source=$the_real_ip location longitude;
|
||||
$geoip2_time_zone source=$the_real_ip location time_zone;
|
||||
$geoip2_region_code source=$the_real_ip subdivisions 0 iso_code;
|
||||
$geoip2_region_name source=$the_real_ip subdivisions 0 names en;
|
||||
}
|
||||
|
|
@ -253,7 +251,7 @@ http {
|
|||
|
||||
# Custom headers for response
|
||||
{{ range $k, $v := $addHeaders }}
|
||||
add_header {{ $k }} "{{ $v }}";
|
||||
add_header {{ $k }} {{ $v | quote }};
|
||||
{{ end }}
|
||||
|
||||
server_tokens {{ if $cfg.ShowServerTokens }}on{{ else }}off{{ end }};
|
||||
|
|
@ -353,6 +351,8 @@ http {
|
|||
|
||||
ssl_protocols {{ $cfg.SSLProtocols }};
|
||||
|
||||
ssl_early_data {{ if $cfg.SSLEarlyData }}on{{ else }}off{{ end }};
|
||||
|
||||
# turn on session caching to drastically improve performance
|
||||
{{ if $cfg.SSLSessionCache }}
|
||||
ssl_session_cache builtin:1000 shared:SSL:{{ $cfg.SSLSessionCacheSize }};
|
||||
|
|
@ -380,12 +380,12 @@ http {
|
|||
ssl_dhparam {{ $cfg.SSLDHParam }};
|
||||
{{ end }}
|
||||
|
||||
{{ if not $cfg.EnableDynamicTLSRecords }}
|
||||
ssl_dyn_rec_size_lo 0;
|
||||
{{ end }}
|
||||
|
||||
ssl_ecdh_curve {{ $cfg.SSLECDHCurve }};
|
||||
|
||||
# PEM sha: {{ $cfg.DefaultSSLCertificate.PemSHA }}
|
||||
ssl_certificate {{ $cfg.DefaultSSLCertificate.PemFileName }};
|
||||
ssl_certificate_key {{ $cfg.DefaultSSLCertificate.PemFileName }};
|
||||
|
||||
{{ if gt (len $cfg.CustomHTTPErrors) 0 }}
|
||||
proxy_intercept_errors on;
|
||||
{{ end }}
|
||||
|
|
@ -472,36 +472,14 @@ http {
|
|||
{{ range $redirect := .RedirectServers }}
|
||||
## start server {{ $redirect.From }}
|
||||
server {
|
||||
{{ range $address := $all.Cfg.BindAddressIpv4 }}
|
||||
listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }};
|
||||
listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} ssl;
|
||||
{{ else }}
|
||||
listen {{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }};
|
||||
listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} ssl;
|
||||
{{ end }}
|
||||
{{ if $IsIPV6Enabled }}
|
||||
{{ range $address := $all.Cfg.BindAddressIpv6 }}
|
||||
listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }};
|
||||
listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }};
|
||||
{{ else }}
|
||||
listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }};
|
||||
listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }};
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
server_name {{ $redirect.From }};
|
||||
|
||||
{{ if not (empty $redirect.SSLCert.PemFileName) }}
|
||||
{{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}}
|
||||
# PEM sha: {{ $redirect.SSLCert.PemSHA }}
|
||||
ssl_certificate {{ $redirect.SSLCert.PemFileName }};
|
||||
ssl_certificate_key {{ $redirect.SSLCert.PemFileName }};
|
||||
{{ buildHTTPListener $all $redirect.From }}
|
||||
{{ buildHTTPSListener $all $redirect.From }}
|
||||
|
||||
{{ if $all.EnableDynamicCertificates}}
|
||||
ssl_certificate_by_lua_block {
|
||||
certificate.call()
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if gt (len $cfg.BlockUserAgents) 0 }}
|
||||
if ($block_ua) {
|
||||
|
|
@ -604,9 +582,8 @@ http {
|
|||
}
|
||||
|
||||
location /configuration {
|
||||
# this should be equals to configuration_data dict
|
||||
client_max_body_size 10m;
|
||||
client_body_buffer_size 10m;
|
||||
client_max_body_size {{ luaConfigurationRequestBodySize $cfg }}m;
|
||||
client_body_buffer_size {{ luaConfigurationRequestBodySize $cfg }}m;
|
||||
proxy_buffering off;
|
||||
|
||||
content_by_lua_block {
|
||||
|
|
@ -639,7 +616,6 @@ stream {
|
|||
error("require failed: " .. tostring(res))
|
||||
else
|
||||
configuration = res
|
||||
configuration.nameservers = { {{ buildResolversForLua $cfg.Resolver $cfg.DisableIpv6DNS }} }
|
||||
end
|
||||
|
||||
ok, res = pcall(require, "tcp_udp_configuration")
|
||||
|
|
@ -685,7 +661,7 @@ stream {
|
|||
listen unix:{{ .StreamSocket }};
|
||||
|
||||
access_log off;
|
||||
|
||||
|
||||
content_by_lua_block {
|
||||
tcp_udp_configuration.call()
|
||||
}
|
||||
|
|
@ -764,7 +740,7 @@ stream {
|
|||
proxy_set_header X-Request-ID $req_id;
|
||||
proxy_set_header Host $best_http_host;
|
||||
|
||||
set $proxy_upstream_name {{ $upstreamName }};
|
||||
set $proxy_upstream_name {{ $upstreamName | quote }};
|
||||
|
||||
rewrite (.*) / break;
|
||||
|
||||
|
|
@ -804,50 +780,15 @@ stream {
|
|||
{{ define "SERVER" }}
|
||||
{{ $all := .First }}
|
||||
{{ $server := .Second }}
|
||||
{{ range $address := $all.Cfg.BindAddressIpv4 }}
|
||||
listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server {{ if $all.Cfg.ReusePort }}reuseport{{ end }} backlog={{ $all.BacklogSize }}{{end}};
|
||||
{{ else }}
|
||||
listen {{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server {{ if $all.Cfg.ReusePort }}reuseport{{ end }} backlog={{ $all.BacklogSize }}{{end}};
|
||||
{{ end }}
|
||||
{{ if $all.IsIPV6Enabled }}
|
||||
{{ range $address := $all.Cfg.BindAddressIpv6 }}
|
||||
listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server {{ if $all.Cfg.ReusePort }}reuseport{{ end }} backlog={{ $all.BacklogSize }}{{ end }};
|
||||
{{ else }}
|
||||
listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server {{ if $all.Cfg.ReusePort }}reuseport{{ end }} backlog={{ $all.BacklogSize }}{{ end }};
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ buildHTTPListener $all $server.Hostname }}
|
||||
{{ buildHTTPSListener $all $server.Hostname }}
|
||||
|
||||
set $proxy_upstream_name "-";
|
||||
set $pass_access_scheme $scheme;
|
||||
set $pass_server_port $server_port;
|
||||
set $best_http_host $http_host;
|
||||
set $pass_port $pass_server_port;
|
||||
|
||||
{{/* Listen on {{ $all.ListenPorts.SSLProxy }} because port {{ $all.ListenPorts.HTTPS }} is used in the TLS sni server */}}
|
||||
{{/* This listener must always have proxy_protocol enabled, because the SNI listener forwards on source IP info in it. */}}
|
||||
{{ if not (empty $server.SSLCert.PemFileName) }}
|
||||
{{ range $address := $all.Cfg.BindAddressIpv4 }}
|
||||
listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server {{ if $all.Cfg.ReusePort }}reuseport{{ end }} backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }};
|
||||
{{ else }}
|
||||
listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server {{ if $all.Cfg.ReusePort }}reuseport{{ end }} backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }};
|
||||
{{ end }}
|
||||
{{ if $all.IsIPV6Enabled }}
|
||||
{{ range $address := $all.Cfg.BindAddressIpv6 }}
|
||||
{{ if not (empty $server.SSLCert.PemFileName) }}listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server {{ if $all.Cfg.ReusePort }}reuseport{{ end }} backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }};
|
||||
{{ else }}
|
||||
{{ if not (empty $server.SSLCert.PemFileName) }}listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server {{ if $all.Cfg.ReusePort }}reuseport{{ end }} backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }};
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}}
|
||||
# PEM sha: {{ $server.SSLCert.PemSHA }}
|
||||
ssl_certificate {{ $server.SSLCert.PemFileName }};
|
||||
ssl_certificate_key {{ $server.SSLCert.PemFileName }};
|
||||
|
||||
{{ if $all.EnableDynamicCertificates}}
|
||||
ssl_certificate_by_lua_block {
|
||||
certificate.call()
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if not (empty $server.AuthTLSError) }}
|
||||
# {{ $server.AuthTLSError }}
|
||||
|
|
@ -855,7 +796,7 @@ stream {
|
|||
{{ else }}
|
||||
|
||||
{{ if not (empty $server.CertificateAuth.CAFileName) }}
|
||||
# PEM sha: {{ $server.CertificateAuth.PemSHA }}
|
||||
# PEM sha: {{ $server.CertificateAuth.CASHA }}
|
||||
ssl_client_certificate {{ $server.CertificateAuth.CAFileName }};
|
||||
ssl_verify_client {{ $server.CertificateAuth.VerifyClient }};
|
||||
ssl_verify_depth {{ $server.CertificateAuth.ValidationDepth }};
|
||||
|
|
@ -930,7 +871,7 @@ stream {
|
|||
# ngx_auth_request module overrides variables in the parent request,
|
||||
# therefore we have to explicitly set this variable again so that when the parent request
|
||||
# resumes it has the correct value set for this variable so that Lua can pick backend correctly
|
||||
set $proxy_upstream_name "{{ buildUpstreamName $location }}";
|
||||
set $proxy_upstream_name {{ buildUpstreamName $location | quote }};
|
||||
|
||||
proxy_pass_request_body off;
|
||||
proxy_set_header Content-Length "";
|
||||
|
|
@ -1000,16 +941,21 @@ stream {
|
|||
|
||||
location {{ $path }} {
|
||||
{{ $ing := (getIngressInformation $location.Ingress $server.Hostname $location.Path) }}
|
||||
set $namespace "{{ $ing.Namespace }}";
|
||||
set $ingress_name "{{ $ing.Rule }}";
|
||||
set $service_name "{{ $ing.Service }}";
|
||||
set $service_port "{{ $location.Port }}";
|
||||
set $location_path "{{ $location.Path | escapeLiteralDollar }}";
|
||||
set $namespace {{ $ing.Namespace | quote}};
|
||||
set $ingress_name {{ $ing.Rule | quote }};
|
||||
set $service_name {{ $ing.Service | quote }};
|
||||
set $service_port {{ $location.Port | quote }};
|
||||
set $location_path {{ $location.Path | escapeLiteralDollar | quote }};
|
||||
|
||||
{{ if $all.Cfg.EnableOpentracing }}
|
||||
{{ opentracingPropagateContext $location }};
|
||||
{{ end }}
|
||||
|
||||
{{ if $location.Mirror.URI }}
|
||||
mirror {{ $location.Mirror.URI }};
|
||||
mirror_request_body {{ $location.Mirror.RequestBody }};
|
||||
{{ end }}
|
||||
|
||||
rewrite_by_lua_block {
|
||||
lua_ingress.rewrite({{ locationConfigForLua $location $server $all }})
|
||||
balancer.rewrite()
|
||||
|
|
@ -1025,7 +971,7 @@ stream {
|
|||
local lua_resty_waf = require("resty.waf")
|
||||
local waf = lua_resty_waf:new()
|
||||
|
||||
waf:set_option("mode", "{{ $location.LuaRestyWAF.Mode }}")
|
||||
waf:set_option("mode", {{ $location.LuaRestyWAF.Mode | quote }})
|
||||
waf:set_option("storage_zone", "waf_storage")
|
||||
|
||||
{{ if $location.LuaRestyWAF.AllowUnknownContentTypes }}
|
||||
|
|
@ -1054,7 +1000,7 @@ stream {
|
|||
{{ end }}
|
||||
|
||||
{{ range $ruleset := $location.LuaRestyWAF.IgnoredRuleSets }}
|
||||
waf:set_option("ignore_ruleset", "{{ $ruleset }}")
|
||||
waf:set_option("ignore_ruleset", {{ $ruleset | quote }})
|
||||
{{ end }}
|
||||
|
||||
{{ if gt (len $location.LuaRestyWAF.ExtraRulesetString) 0 }}
|
||||
|
|
@ -1096,13 +1042,12 @@ stream {
|
|||
plugins.run()
|
||||
}
|
||||
|
||||
{{ if (and (not (empty $server.SSLCert.PemFileName)) $all.Cfg.HSTS) }}
|
||||
{{ if (and $server.SSLCert $all.Cfg.HSTS) }}
|
||||
if ($scheme = https) {
|
||||
more_set_headers "Strict-Transport-Security: max-age={{ $all.Cfg.HSTSMaxAge }}{{ if $all.Cfg.HSTSIncludeSubdomains }}; includeSubDomains{{ end }}{{ if $all.Cfg.HSTSPreload }}; preload{{ end }}";
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{ if not $location.Logs.Access }}
|
||||
access_log off;
|
||||
{{ end }}
|
||||
|
|
@ -1118,8 +1063,12 @@ stream {
|
|||
port_in_redirect {{ if $location.UsePortInRedirects }}on{{ else }}off{{ end }};
|
||||
|
||||
set $balancer_ewma_score -1;
|
||||
set $proxy_upstream_name "{{ buildUpstreamName $location }}";
|
||||
set $proxy_host $proxy_upstream_name;
|
||||
set $proxy_upstream_name {{ buildUpstreamName $location | quote }};
|
||||
set $proxy_host $proxy_upstream_name;
|
||||
set $pass_access_scheme $scheme;
|
||||
set $pass_server_port $server_port;
|
||||
set $best_http_host $http_host;
|
||||
set $pass_port $pass_server_port;
|
||||
|
||||
set $proxy_alternative_upstream_name "";
|
||||
|
||||
|
|
@ -1139,7 +1088,7 @@ stream {
|
|||
{{ end }}
|
||||
|
||||
{{ if (not (empty $location.ModSecurity.TransactionID)) }}
|
||||
modsecurity_transaction_id "{{ $location.ModSecurity.TransactionID }}";
|
||||
modsecurity_transaction_id {{ $location.ModSecurity.TransactionID | quote }};
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
|
|
@ -1168,10 +1117,10 @@ stream {
|
|||
|
||||
{{ if $location.BasicDigestAuth.Secured }}
|
||||
{{ if eq $location.BasicDigestAuth.Type "basic" }}
|
||||
auth_basic "{{ $location.BasicDigestAuth.Realm }}";
|
||||
auth_basic {{ $location.BasicDigestAuth.Realm | quote }};
|
||||
auth_basic_user_file {{ $location.BasicDigestAuth.File }};
|
||||
{{ else }}
|
||||
auth_digest "{{ $location.BasicDigestAuth.Realm }}";
|
||||
auth_digest {{ $location.BasicDigestAuth.Realm | quote }};
|
||||
auth_digest_user_file {{ $location.BasicDigestAuth.File }};
|
||||
{{ end }}
|
||||
proxy_set_header Authorization "";
|
||||
|
|
@ -1205,7 +1154,7 @@ stream {
|
|||
{{/* By default use vhost as Host to upstream, but allow overrides */}}
|
||||
{{ if not (eq $proxySetHeader "grpc_set_header") }}
|
||||
{{ if not (empty $location.UpstreamVhost) }}
|
||||
{{ $proxySetHeader }} Host "{{ $location.UpstreamVhost }}";
|
||||
{{ $proxySetHeader }} Host {{ $location.UpstreamVhost | quote }};
|
||||
{{ else }}
|
||||
{{ $proxySetHeader }} Host $best_http_host;
|
||||
{{ end }}
|
||||
|
|
@ -1253,7 +1202,7 @@ stream {
|
|||
|
||||
# Custom headers to proxied server
|
||||
{{ range $k, $v := $all.ProxySetHeaders }}
|
||||
{{ $proxySetHeader }} {{ $k }} "{{ $v }}";
|
||||
{{ $proxySetHeader }} {{ $k }} {{ $v | quote }};
|
||||
{{ end }}
|
||||
|
||||
proxy_connect_timeout {{ $location.Proxy.ConnectTimeout }}s;
|
||||
|
|
@ -1263,6 +1212,9 @@ stream {
|
|||
proxy_buffering {{ $location.Proxy.ProxyBuffering }};
|
||||
proxy_buffer_size {{ $location.Proxy.BufferSize }};
|
||||
proxy_buffers {{ $location.Proxy.BuffersNumber }} {{ $location.Proxy.BufferSize }};
|
||||
{{ if isValidByteSize $location.Proxy.ProxyMaxTempFileSize true }}
|
||||
proxy_max_temp_file_size {{ $location.Proxy.ProxyMaxTempFileSize }};
|
||||
{{ end }}
|
||||
proxy_request_buffering {{ $location.Proxy.RequestBuffering }};
|
||||
proxy_http_version {{ $location.Proxy.ProxyHTTPVersion }};
|
||||
|
||||
|
|
@ -1306,6 +1258,16 @@ stream {
|
|||
{{ range $errCode := $location.CustomHTTPErrors }}
|
||||
error_page {{ $errCode }} = @custom_{{ $location.DefaultBackendUpstreamName }}_{{ $errCode }};{{ end }}
|
||||
|
||||
{{ if (eq $location.BackendProtocol "FCGI") }}
|
||||
include /etc/nginx/fastcgi_params;
|
||||
{{ end }}
|
||||
{{- if $location.FastCGI.Index -}}
|
||||
fastcgi_index {{ $location.FastCGI.Index | quote }};
|
||||
{{- end -}}
|
||||
{{ range $k, $v := $location.FastCGI.Params }}
|
||||
fastcgi_param {{ $k }} {{ $v | quote }};
|
||||
{{ end }}
|
||||
|
||||
{{ buildProxyPass $server.Hostname $all.Backends $location }}
|
||||
{{ if (or (eq $location.Proxy.ProxyRedirectFrom "default") (eq $location.Proxy.ProxyRedirectFrom "off")) }}
|
||||
proxy_redirect {{ $location.Proxy.ProxyRedirectFrom }};
|
||||
|
|
@ -1313,7 +1275,7 @@ stream {
|
|||
proxy_redirect {{ $location.Proxy.ProxyRedirectFrom }} {{ $location.Proxy.ProxyRedirectTo }};
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
# Location denied. Reason: {{ $location.Denied | printf "%q" }}
|
||||
# Location denied. Reason: {{ $location.Denied | quote }}
|
||||
return 503;
|
||||
{{ end }}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue