Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add dns discovery #3629

Merged
merged 1 commit into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ install: default
$(INSTALL) -d $(INST_LUADIR)/apisix/core
$(INSTALL) apisix/core/*.lua $(INST_LUADIR)/apisix/core/

$(INSTALL) -d $(INST_LUADIR)/apisix/core/dns
$(INSTALL) apisix/core/dns/*.lua $(INST_LUADIR)/apisix/core/dns

$(INSTALL) -d $(INST_LUADIR)/apisix/cli
$(INSTALL) apisix/cli/*.lua $(INST_LUADIR)/apisix/cli/

Expand Down
1 change: 1 addition & 0 deletions apisix/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ return {
timer = require("apisix.core.timer"),
id = require("apisix.core.id"),
utils = utils,
dns_client = require("apisix.core.dns.client"),
etcd = require("apisix.core.etcd"),
tablepool = require("tablepool"),
empty_tab = {},
Expand Down
81 changes: 81 additions & 0 deletions apisix/core/dns/client.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local require = require
local log = require("apisix.core.log")
local json = require("apisix.core.json")
local table = require("apisix.core.table")
local math_random = math.random
local package_loaded = package.loaded
local setmetatable = setmetatable


local _M = {
RETURN_RANDOM = 1,
RETURN_ALL = 2,
}


function _M.resolve(self, domain, selector)
local client = self.client

-- this function will dereference the CNAME records
local answers, err = client.resolve(domain)
if not answers then
return nil, "failed to query the DNS server: " .. err
end

if answers.errcode then
return nil, "server returned error code: " .. answers.errcode
.. ": " .. answers.errstr
end

if selector == _M.RETURN_ALL then
log.info("dns resolve ", domain, ", result: ", json.delay_encode(answers))
return table.deepcopy(answers)
end

local idx = math_random(1, #answers)
local answer = answers[idx]
local dns_type = answer.type
if dns_type == client.TYPE_A or dns_type == client.TYPE_AAAA then
log.info("dns resolve ", domain, ", result: ", json.delay_encode(answer))
return table.deepcopy(answer)
end

return nil, "unsupport DNS answer"
end


function _M.new(opts)
opts.ipv6 = true
opts.timeout = 2000 -- 2 sec
opts.retrans = 5 -- 5 retransmissions on receive timeout

-- make sure each client has its separate room
package_loaded["resty.dns.client"] = nil
local dns_client_mod = require("resty.dns.client")

local ok, err = dns_client_mod.init(opts)
if not ok then
return nil, "failed to init the dns client: " .. err
end

return setmetatable({client = dns_client_mod}, {__index = _M})
end


return _M
42 changes: 10 additions & 32 deletions apisix/core/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,14 @@
local config_local = require("apisix.core.config_local")
local core_str = require("apisix.core.string")
local table = require("apisix.core.table")
local json = require("apisix.core.json")
local log = require("apisix.core.log")
local string = require("apisix.core.string")
local dns_client = require("apisix.core.dns.client")
local ngx_re = require("ngx.re")
local dns_client = require("resty.dns.client")
local ipmatcher = require("resty.ipmatcher")
local ffi = require("ffi")
local base = require("resty.core.base")
local open = io.open
local math = math
local sub_str = string.sub
local str_byte = string.byte
local tonumber = tonumber
Expand All @@ -43,6 +41,7 @@ local ngx_sleep = ngx.sleep
local hostname
local dns_resolvers
local current_inited_resolvers
local current_dns_client
local max_sleep_interval = 1

ffi.cdef[[
Expand Down Expand Up @@ -84,54 +83,33 @@ function _M.split_uri(uri)
end


local function dns_parse(domain)
local function dns_parse(domain, selector)
if dns_resolvers ~= current_inited_resolvers then
local local_conf = config_local.local_conf()
local valid = table.try_read_attr(local_conf, "apisix", "dns_resolver_valid")
local enable_resolv_search_opt = table.try_read_attr(local_conf, "apisix",
"enable_resolv_search_opt")

local opts = {
ipv6 = true,
nameservers = table.clone(dns_resolvers),
retrans = 5, -- 5 retransmissions on receive timeout
timeout = 2000, -- 2 sec
order = {"last", "A", "AAAA", "CNAME"}, -- avoid querying SRV (we don't support it yet)
validTtl = valid,
order = {"last", "A", "AAAA", "CNAME"}, -- avoid querying SRV
}

opts.validTtl = valid

if not enable_resolv_search_opt then
opts.search = {}
end

local ok, err = dns_client.init(opts)
if not ok then
local client, err = dns_client.new(opts)
if not client then
return nil, "failed to init the dns client: " .. err
end

current_dns_client = client
current_inited_resolvers = dns_resolvers
end

-- this function will dereference the CNAME records
local answers, err = dns_client.resolve(domain)
if not answers then
return nil, "failed to query the DNS server: " .. err
end

if answers.errcode then
return nil, "server returned error code: " .. answers.errcode
.. ": " .. answers.errstr
end

local idx = math.random(1, #answers)
local answer = answers[idx]
local dns_type = answer.type
if dns_type == dns_client.TYPE_A or dns_type == dns_client.TYPE_AAAA then
log.info("dns resolve ", domain, ", result: ", json.delay_encode(answer))
return table.deepcopy(answer)
end

return nil, "unsupport DNS answer"
return current_dns_client:resolve(domain, selector)
end
_M.dns_parse = dns_parse

Expand Down
90 changes: 90 additions & 0 deletions apisix/discovery/dns.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

local core = require("apisix.core")
local config_local = require("apisix.core.config_local")
local ipairs = ipairs
local error = error


local dns_client
local schema = {
type = "object",
properties = {
servers = {
type = "array",
minItems = 1,
items = {
type = "string",
},
},
},
required = {"servers"}
}


local _M = {}


function _M.nodes(service_name)
local host, port = core.utils.parse_addr(service_name)
core.log.info("discovery dns with host ", host, ", port ", port)

local records, err = dns_client:resolve(host, core.dns_client.RETURN_ALL)
if not records then
return nil, err
end

local nodes = core.table.new(#records, 0)
for i, r in ipairs(records) do
if r.address then
nodes[i] = {host = r.address, weight = 1, port = port}
end
end

return nodes
end


function _M.init_worker()
local local_conf = config_local.local_conf()
local ok, err = core.schema.check(schema, local_conf.discovery.dns)
if not ok then
error("invalid dns discovery configuration: " .. err)
return
end

local servers = core.table.try_read_attr(local_conf, "discovery", "dns", "servers")

local opts = {
hosts = {},
resolvConf = {},
nameservers = servers,
order = {"last", "A", "AAAA", "CNAME"}, -- avoid querying SRV (we don't support it yet)
}

local client, err = core.dns_client.new(opts)
if not client then
error("failed to init the dns client: ", err)
return
end

dns_client = client
end


return _M
7 changes: 6 additions & 1 deletion apisix/upstream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,12 @@ function _M.set_by_route(route, api_ctx)
if not dis then
return 500, "discovery " .. up_conf.discovery_type .. " is uninitialized"
end
local new_nodes = dis.nodes(up_conf.service_name)

local new_nodes, err = dis.nodes(up_conf.service_name)
if not new_nodes then
return http_code_upstream_unavailable, "no valid upstream node: " .. (err or "nil")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http_code_upstream_unavailable:

For static variables, use capital letters

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@membphis
I have submitted another PR to use the number literal directly: #3672, which is more consistent in the style.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed my idea and followed your suggestion.

end

local same = upstream_util.compare_upstream_node(up_conf.nodes, new_nodes)
if not same then
up_conf.nodes = new_nodes
Expand Down
3 changes: 3 additions & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ etcd:
# the default value is true, e.g. the certificate will be verified strictly.

# discovery: # service discovery center
# dns:
# resolver:
# - "127.0.0.1:8600" # use the real address of your dns server
# eureka:
# host: # it's possible to define multiple eureka hosts addresses of the same eureka cluster.
# - "http://127.0.0.1:8761"
Expand Down
1 change: 1 addition & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
* [Stream Proxy](stream-proxy.md)
* [gRPC Proxy](grpc-proxy.md)
* [Customize Nginx Configuration](./customize-nginx-configuration.md)
* [DNS](./dns.md)
* [Changelog](../CHANGELOG.md)
* [Benchmark](benchmark.md)
* [Code Style](../CODE_STYLE.md)
Expand Down
13 changes: 11 additions & 2 deletions doc/discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
# Integration service discovery registry

* [**Summary**](#summary)
* [**How extend the discovery client?**](#how-extend-the-discovery-client)
* [**Supported discovery registries**](#supported-discovery-registries)
* [**How to extend the discovery client?**](#how-to-extend-the-discovery-client)
* [**Basic steps**](#basic-steps)
* [**the example of Eureka**](#the-example-of-eureka)
* [**Implementation of eureka.lua**](#implementation-of-eurekalua)
Expand All @@ -43,7 +44,15 @@ When system traffic changes, the number of servers of the upstream service also

Common registries: Eureka, Etcd, Consul, Zookeeper, Nacos etc.

## How extend the discovery client?
## Supported discovery registries

Currently we support Eureka and service discovery via DNS, like Consul.

For service discovery via DNS, see [service discovery via DNS](dns.md#service-discovery-via-dns).

For Eureka, see below.

## How to extend the discovery client?

### Basic steps

Expand Down
Loading