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: support SRV record #3686

Merged
merged 4 commits into from
Feb 27, 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
68 changes: 68 additions & 0 deletions apisix/core/dns/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ local require = require
local log = require("apisix.core.log")
local json = require("apisix.core.json")
local table = require("apisix.core.table")
local insert_tab = table.insert
local math_random = math.random
local package_loaded = package.loaded
local ipairs = ipairs
local setmetatable = setmetatable


Expand All @@ -29,6 +31,67 @@ local _M = {
}


local function gcd(a, b)
if b == 0 then
return a
end

return gcd(b, a % b)
end


local function resolve_srv(client, answers)
if #answers == 0 then
return nil, "empty SRV record"
end

local resolved_answers = {}
local answer_to_count = {}
for _, answer in ipairs(answers) do
if answer.type ~= client.TYPE_SRV then
return nil, "mess SRV with other record"
end

local resolved, err = client.resolve(answer.target)
if not resolved then
local msg = "failed to resolve SRV record " .. answer.target .. ": " .. err
return nil, msg
end

log.info("dns resolve SRV ", answer.target, ", result: ",
json.delay_encode(resolved))

local weight = answer.weight
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the explantation to treat zero weight record to 1 https://github.com/apache/apisix/blob/3af2175629f727d0374b7f77dbdc3a510cf44d81/docs/en/latest/dns.md#srv-record, can also add here.

Copy link
Member Author

Choose a reason for hiding this comment

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

if weight == 0 then
weight = 1
end

local count = #resolved
answer_to_count[answer] = count
-- one target may have multiple resolved results
for _, res in ipairs(resolved) do
local copy = table.deepcopy(res)
copy.weight = weight / count
copy.port = answer.port
insert_tab(resolved_answers, copy)
end
end

-- find the least common multiple of the counts
local lcm = answer_to_count[answers[1]]
for i = 2, #answers do
local count = answer_to_count[answers[i]]
lcm = count * lcm / gcd(count, lcm)
end
-- fix the weight as the weight should be integer
for _, res in ipairs(resolved_answers) do
res.weight = res.weight * lcm
end

return resolved_answers
end


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

Expand All @@ -45,6 +108,11 @@ function _M.resolve(self, domain, selector)

if selector == _M.RETURN_ALL then
log.info("dns resolve ", domain, ", result: ", json.delay_encode(answers))
for _, answer in ipairs(answers) do
if answer.type == client.TYPE_SRV then
return resolve_srv(client, answers)
end
end
return table.deepcopy(answers)
end

Expand Down
4 changes: 2 additions & 2 deletions apisix/discovery/dns.lua
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function _M.nodes(service_name)
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}
nodes[i] = {host = r.address, weight = r.weight or 1, port = r.port or port}
end
end

Expand All @@ -74,7 +74,7 @@ function _M.init_worker()
hosts = {},
resolvConf = {},
nameservers = servers,
order = {"last", "A", "AAAA", "CNAME"}, -- avoid querying SRV (we don't support it yet)
order = {"last", "A", "AAAA", "SRV", "CNAME"},
}

local client, err = core.dns_client.new(opts)
Expand Down
63 changes: 60 additions & 3 deletions docs/en/latest/dns.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ title: DNS
-->

* [service discovery via DNS](#service-discovery-via-dns)
* [SRV record](#src-record)

## service discovery via DNS

Expand Down Expand Up @@ -56,17 +57,16 @@ and `test.consul.service` be resolved as `1.1.1.1` and `1.1.1.2`, this result wi
{
"id": 1,
"type": "roundrobin",
"nodes": {
"nodes": [
{"host": "1.1.1.1", "weight": 1},
{"host": "1.1.1.2", "weight": 1}
}
]
}
```

Note that all the IPs from `test.consul.service` share the same weight.

If a service has both A and AAAA records, A record is preferred.
Currently we support A / AAAA records, SRV has not been supported yet.

If you want to specify the port for the upstream server, you can add it to the `service_name`:

Expand All @@ -78,3 +78,60 @@ If you want to specify the port for the upstream server, you can add it to the `
"type": "roundrobin"
}
```

Another way to do it is via the SRV record, see below.

### SRV record

By using SRV record you can specify the port and the weight of a service.

Assumed you have the SRV record like this:

```
; under the section of blah.service
A 300 IN A 1.1.1.1
B 300 IN A 1.1.1.2
B 300 IN A 1.1.1.3
srv 86400 IN SRV 10 60 1980 A
srv 86400 IN SRV 10 20 1981 B
```

Upstream configuration like:

```json
{
"id": 1,
"discovery_type": "dns",
"service_name": "srv.blah.service",
"type": "roundrobin"
}
```

is the same as:

```json
{
"id": 1,
"type": "roundrobin",
"nodes": [
{"host": "1.1.1.1", "port": 1980, "weight": 60},
{"host": "1.1.1.2", "port": 1981, "weight": 10},
{"host": "1.1.1.3", "port": 1981, "weight": 10}
]
}
```

Note that two records of domain B split the weight evenly.

As for 0 weight SRV record, the [RFC 2782](https://www.ietf.org/rfc/rfc2782.txt) says:

> Domain administrators SHOULD use Weight 0 when there isn't any server
selection to do, to make the RR easier to read for humans (less
noisy). In the presence of records containing weights greater
than 0, records with weight 0 should have a very small chance of
being selected.

We treat weight 0 record has a weight of 1 so the node "have a very small chance of
being selected", which is also the common way to treat this type of record.

TODO: support priority.
21 changes: 21 additions & 0 deletions t/coredns/db.test.local
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,24 @@ ipv6 IN AAAA ::1

ttl 300 IN A 127.0.0.1
ttl.1s 1 IN A 127.0.0.1

; SRV
A IN A 127.0.0.1
B IN A 127.0.0.2
C IN A 127.0.0.3
C IN A 127.0.0.4
; RFC 2782 style
_sip._tcp.srv 86400 IN SRV 10 60 1980 A
_sip._tcp.srv 86400 IN SRV 10 20 1980 B
; standard style
srv 86400 IN SRV 10 60 1980 A
srv 86400 IN SRV 10 20 1980 B

port.srv 86400 IN SRV 10 60 1980 A
port.srv 86400 IN SRV 10 20 1981 B

zero-weight.srv 86400 IN SRV 10 60 1980 A
zero-weight.srv 86400 IN SRV 10 0 1980 B

split-weight.srv 86400 IN SRV 10 100 1980 A
split-weight.srv 86400 IN SRV 10 0 1980 C
80 changes: 80 additions & 0 deletions t/discovery/dns/sanity.t
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,83 @@ upstreams:
--- error_log
invalid dns discovery configuration
--- error_code: 500



=== TEST 8: SRV
--- apisix_yaml
upstreams:
- service_name: "srv.test.local"
discovery_type: dns
type: roundrobin
id: 1
--- grep_error_log eval
qr/upstream nodes: \{[^}]+\}/
--- grep_error_log_out eval
qr/upstream nodes: \{("127.0.0.1:1980":60,"127.0.0.2:1980":20|"127.0.0.2:1980":20,"127.0.0.1:1980":60)\}/
--- response_body
hello world



=== TEST 9: SRV (RFC 2782 style)
--- apisix_yaml
upstreams:
- service_name: "_sip._tcp.srv.test.local"
discovery_type: dns
type: roundrobin
id: 1
--- grep_error_log eval
qr/upstream nodes: \{[^}]+\}/
--- grep_error_log_out eval
qr/upstream nodes: \{("127.0.0.1:1980":60,"127.0.0.2:1980":20|"127.0.0.2:1980":20,"127.0.0.1:1980":60)\}/
--- response_body
hello world



=== TEST 10: SRV (different port)
--- apisix_yaml
upstreams:
- service_name: "port.srv.test.local"
discovery_type: dns
type: roundrobin
id: 1
--- grep_error_log eval
qr/upstream nodes: \{[^}]+\}/
--- grep_error_log_out eval
qr/upstream nodes: \{("127.0.0.1:1980":60,"127.0.0.2:1981":20|"127.0.0.2:1981":20,"127.0.0.1:1980":60)\}/
--- response_body
hello world



=== TEST 11: SRV (zero weight)
--- apisix_yaml
upstreams:
- service_name: "zero-weight.srv.test.local"
discovery_type: dns
type: roundrobin
id: 1
--- grep_error_log eval
qr/upstream nodes: \{[^}]+\}/
--- grep_error_log_out eval
qr/upstream nodes: \{("127.0.0.1:1980":60,"127.0.0.2:1980":1|"127.0.0.2:1980":1,"127.0.0.1:1980":60)\}/
--- response_body
hello world



=== TEST 12: SRV (split weight)
--- apisix_yaml
upstreams:
- service_name: "split-weight.srv.test.local"
discovery_type: dns
type: roundrobin
id: 1
--- grep_error_log eval
qr/upstream nodes: \{[^}]+\}/
--- grep_error_log_out eval
qr/upstream nodes: \{(,?"127.0.0.(1:1980":200|3:1980":1|4:1980":1)){3}\}/
--- response_body
hello world