Trouble with DNS Resolvers sticking to single IP address

Hi @z0mb1ek

Yes, please, give a try to 1.8dev2. The responses are now stored in a local cache and each time a record is consumed, it’s moved to the back of the list:
http://git.haproxy.org/?p=haproxy.git;a=commitdiff;h=8ea0bcc911809e77560bdd937c02a0b832526ef7

Note that it’s a bit limited in some use-cases:

  • does not look for records in other IP family (under dev)
  • when adding a new IP in the response, only 1 server will use it (we don’t change many servers at the mean time unless there is a good reason for it)

Baptiste

when adding a new IP in the response, only 1 server will use it (we don’t change many servers at the mean time unless there is a good reason for it)

what does it mean?

Let’s take a backend like the one below:
backend my_app server s1 myapp.domain.com:80 resolvers mydns server s2 myapp.domain.com:80 resolvers mydns server s3 myapp.domain.com:80 resolvers mydns server s4 myapp.domain.com:80 resolvers mydns server s5 myapp.domain.com:80 resolvers mydns server s6 myapp.domain.com:80 resolvers mydns

if myapp.domain.com returns 10.0.0.1 and 10.0.0.2, then 3 servers will be affected to each IP.
Now, if you add one more record to myapp.domain.com (10.0.0.1, 10.0.0.2 and 10.0.0.3), then only 1 server will pick up this third IP address.

We can’t do better for now, but we’ll work on improving this situation.

if myapp.domain.com returns 10.0.0.1 and 10.0.0.2, then 3 servers will be affected to each IP.

s1,s2,s3 for 10.0.0.1 and s4,s5,s6 for 10.0.0.2? And then s1,s2,s3-10.0.0.1; s4,s5-10.0.0.2 and s6-10.0.0.3 this way?

Thus if a have two ips for one dns record I need two same server record like this?

backend my_app
server s1 myapp.domain.com:80 resolvers mydns
server s2 myapp.domain.com:80 resolvers mydns

or can only have one record?

@Baptiste can you answer please?)

you need 2 lines. Actually, you need the number you expect to have.
Again, we’re working on improving this situation. Stay tuned.
(I mean that soon, the number of UP server will match the number of records in the DNS response, without any duplication unless you allow, for backward compatibility).

Baptiste

@Baptiste big thx.

Can I use dev version in production?

Hello list,

This are quite exciting news and I am trying to give it at shot with 1.8-dev2

So far i have created a resolvers entry on my haproxy.cfg and some basic frontend/backend

global
    log /dev/log daemon
    log /dev/log daemon notice
    maxconn 100

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

listen stats
    bind *:1936
    stats enable
    stats hide-version
    stats refresh 5s
    stats show-node
    stats realm HAProxy\ Statistics
    stats uri /
    http-request set-log-level silent

resolvers dns-consul
  nameserver dns1 127.0.0.1:8600
  resolve_retries 3
  hold valid 100ms

frontend http
  bind *:80
  default_backend site-backend

backend site-backend
  balance roundrobin
  server site frontend-api-frontend.service.dc1.consul check resolvers dns-consul resolve-prefer ipv

I can see some messages of DNS requests in Consul (my dns srv source)

    2017/10/04 12:44:04 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 28 1} (139.981µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:04 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 1 1} (107.348µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:05 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 28 1} (121.594µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:05 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 1 1} (57.816µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:06 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 28 1} (124.103µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:06 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 1 1} (78.518µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:07 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 28 1} (128.503µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:07 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 1 1} (109.668µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:08 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 28 1} (190.55µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:08 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 1 1} (113.934µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:09 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 28 1} (123.446µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:09 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 1 1} (91.523µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:09 [DEBUG] http: Request GET /v1/agent/self (568.817µs) from=127.0.0.1:53393
    2017/10/04 12:44:10 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 28 1} (127.082µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:10 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 1 1} (108.02µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:11 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 28 1} (133.022µs) from client 127.0.0.1:63649 (udp)
    2017/10/04 12:44:11 [DEBUG] dns: request for {frontend-api-frontend.service.dc1.consul. 1 1} (97.165µs) from client 127.0.0.1:63649 (udp)

I’ve also verified that I see the list of servers on my dns request.

dig @127.0.0.1 -p 8600 frontend-api-frontend.service.consul SRV

; <<>> DiG 9.11.2 <<>> @127.0.0.1 -p 8600 frontend-api-frontend.service.consul SRV
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 750
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;frontend-api-frontend.service.consul. IN SRV

;; ANSWER SECTION:
frontend-api-frontend.service.consul. 0	IN SRV	1 1 21587 7f000001.addr.dc1.consul.
frontend-api-frontend.service.consul. 0	IN SRV	1 1 22242 7f000001.addr.dc1.consul.

;; ADDITIONAL SECTION:
7f000001.addr.dc1.consul. 0	IN	A	127.0.0.1
7f000001.addr.dc1.consul. 0	IN	A	127.0.0.1

;; Query time: 0 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Wed Oct 04 12:44:55 CEST 2017
;; MSG SIZE  rcvd: 155

But i can not get the haproxy backend to be dynamic. Not sure what I am doing wrong and how to debug the haproxy itself.

So far starting it like this:

haproxy -f haproxy.cfg -d -V

But not errors messages or hints have spit into stdout.
Any helps or hints are appreciated.

Thanks in advance

Hi,

You must use the latest -dev from git (code has been commited after dev2
has been released).
Second, your request is not sent as SRV since it does not follow-up the
RFC. The “fqdn” must be ...
Consul accepts the only (as kubernetes does), but it shouldn’t.
Last, you should use the srv-template directive to provision X server with
the same configuration.
It would be something like this:

server-template red 20 _http._tcp.red.default.svc.cluster.local:8080
inter 1s resolvers kube resolve-prefer ipv4 check

Baptiste

If anyone wants to try out 1.8-dev2 dns resolvers, I have setup a docker demo:

You would probably need to reconfigure the hard coded IP addresses. Will get around to making it env based soon.

Asking @willy to release 1.8-dev3, so that all those changes can be easier tested in the field.

Asking myself the same question.

From the Docker Docs:

To bypass the routing mesh, you can start a service using DNS Round Robin (DNSRR) mode, by setting the --endpoint-mode flag to dnsrr. You must run your own load balancer in front of the service. A DNS query for the service name on the Docker host returns a list of IP addresses for the nodes running the service. Configure your load balancer to consume this list and balance the traffic across the nodes.

How do I actually do that with HAProxy?

Well, either you set multiple server line with the same name:
backend myapp

[…]

server s1 myapp.domain.com:80 check resolvers mydns

server s2 myapp.domain.com:80 check resolvers mydns

server s3 myapp.domain.com:80 check resolvers mydns

Or you use server-template directive:

backend myapp

[…]

server s 3 myapp.domain.com:80 check resolvers mydns

Adjust the number of servers to your need.

In each case, the resolvers should turn on one server per IP found in the response.

Baptiste, I tried to use multiple servers but the requests are not balanced evenly between the servers.

I built an example using Docker and docker-compose. The backend servers count each request they receive and print the number of requests received after 60 seconds.

Code:

The output is

api_4      | f8e1414ea551 0
api_1      | 860eb040651c 0
api_2      | 6af96d901ea8 179
api_5      | 1f15abd0d461 60
api_3      | 271ae04ff5cc 60

One API server receives 180 requests, two receive 60 each and another two receive 0.

Is it possible to round robin to servers which were resolved with DNS?

Did you start 5 instances of ‘api’ service?

Yes. Five instances of the API service using.

docker-compose up --scale api=5

Hi,

You’re missing the “resolvers docker” statement on your server-template line.

With this enabled, I have the following result:

docker-compose up --scale api=5

Starting debug_api_1 …

Starting debug_api_1 … done

Starting debug_api_2 … done

Starting debug_api_3 … done

Starting debug_api_4 … done

Starting debug_api_5 … done

Attaching to debug_haproxy_1, debug_api_1, debug_api_2, debug_api_3, debug_api_4, debug_api_5

api_3 | cd51492adab9 63

api_4 | f5532b40ea80 61

debug_api_3 exited with code 0

debug_api_4 exited with code 0

api_2 | a81602129221 61

api_1 | f8202d903d1b 61

debug_api_2 exited with code 0

debug_api_1 exited with code 0

api_5 | 02f56990bbd4 62

debug_api_5 exited with code 0

You are correct @Baptiste. Apologies!

Thank you so much @z0mb1ek and @baptiste for explaining this I never would have guessed I need multiple server lines!

I’ve spent all weekend trying to figure out why haproxy wouldn’t load balance my docker service even though simple curls show it cycling among the different ip’s that my service name resolves to.

In my case I don’t know how large my service will be scaled. How do I know how many duplicate service lines to use? Is there any downside to using whatever maximum I expect?

What is the upcoming better way to do this is there a github issue I can follow or blog post explaining it?

I’ve right now implementing something similar on our infrastructure.

In our case, we have it pointing at an AWS ALB. Since those IPs can change, we don’t want it to hold onto that IP forever.

The final configuration looks something like this:

resolvers default
  parse-resolv-conf
  timeout resolve 1m

backend be_gw
  mode http
  http-request set-header host aws-gw.contoso.com
  option httpchk GET /srv/status HTTP/1.1
  http-check send hdr "host" "aws-gw.contoso.com"

  default-server init-addr none resolvers default check downinter 2s fastinter 2s inter 3m ssl ca-file /usr/local/etc/haproxy/ca-certificates.crt
  server-template gw4- 1-3 aws-gw.contoso.com:443 backup resolve-prefer ipv4
  server-template gw6- 1-3 aws-gw.contoso.com:443 resolve-prefer ipv6

Here’s the options applied to these servers and why:

  • init-addr none: the server starts off blank and then gets populated by the DNS lookup
  • resolvers default: use the “default” resolver group defined at the top of the file
  • ssl ca-file …: use TLS and validate with the specified CA file
  • resolve-opts prevent-dup-ip: one IP per server
  • backup: only use IPv4 as backup; this is the year of BOTH the Linux desktop and IPv6
  • resolve-prefer ipv[46]: use these addresses for these servers

The combination of these options gives us the following internal configuration:

'show servers state be_gw' | socat STDIO UNIX-CONNECT:/tmp/haproxy.sock
1
# be_id be_name srv_id srv_name srv_addr srv_op_state srv_admin_state srv_uweight srv_iweight srv_time_since_last_change srv_check_status srv_check_result srv_check_health srv_check_state srv_agent_state bk_f_forced_id srv_f_forced_id srv_fqdn srv_port srvrecord srv_use_ssl srv_check_port srv_check_addr srv_agent_addr srv_agent_port
5 be_gw 1 gw4-1 192.0.2.53 2 0 1 1 5 1 0 0 0 0 0 0 aws-gw.contoso.com 443 - 1 0 - - 0
5 be_gw 2 gw4-2 198.51.100.42 2 0 1 1 5 1 0 0 0 0 0 0 aws-gw.contoso.com 443 - 1 0 - - 0
5 be_gw 3 gw4-3 203.0.113.60 2 0 1 1 5 1 0 0 0 0 0 0 aws-gw.contoso.com 443 - 1 0 - - 0
5 be_gw 4 gw6-1 2001:db8:0:1:bf56:d653:4d5f:5254 2 0 1 1 5 1 0 0 0 0 0 0 aws-gw.contoso.com 443 - 1 0 - - 0
5 be_gw 5 gw6-2 2001:db8:0:2:37ce:beb1:dbd1:78a6 2 0 1 1 5 1 0 0 0 0 0 0 aws-gw.contoso.com 443 - 1 0 - - 0
5 be_gw 6 gw6-3 2001:db8:0:3:92b5:98b9:a514:9f8e 2 0 1 1 5 1 0 0 0 0 0 0 aws-gw.contoso.com 443 - 1 0 - - 0

Note that we’re using two separate sections for IPv4 and IPv6 addresses - this is due to resolve-prefer (default: ipv6) causing only IPv6 addresses to be used (tested on 2.5.7)