haproxy icon indicating copy to clipboard operation
haproxy copied to clipboard

Documentation: Clarify the meaning of 'hold' in the 'resolvers' section

Open NickMRamirez opened this issue 2 years ago • 4 comments

Detailed Description of the Problem

The documentation describes the hold directive in a resolvers sections as:

hold <status> <period>

Defines <period> during which the last name resolution should be kept based 
on last resolution <status>

  <status> : last name resolution status. Acceptable values are "nx",
             "other", "refused", "[timeout](https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#)", "valid", "obsolete".
  <period> : interval between two successive name resolution when the last
             answer was in <status>. It follows the HAProxy time format.
             <period> is in milliseconds by default.

This description has led some users to believe that HAProxy caches DNS responses and that hold sets a TTL for expiring that cache entry in HAProxy. For example, one might think that hold valid 30s will cache the DNS response for 30 seconds, causing HAProxy to only query the DNS server again after that time.

However, the meaning, as I understand it, is actually that the up/down status of the server will be held for 30 seconds.

In my own notes, I have described it this way:

How long to "hold" a backend server's up/down status depending on the name resolution status.
For example, if an NXDOMAIN response is returned, keep the backend server in its current state (up) for
at least another 30 seconds (`hold nx 30s`) before marking it as down due to DNS not having a record for it.

If I have accurately described it, then I think the official documentation should be clarified so that users don't believe hold means a DNS cache TTL.

I am still unclear on the use case of hold valid 10s...is its purpose for how long to maintain a server's down status before bringing it back up after receiving a valid DNS response?

Expected Behavior

I was confused myself about this directive, but I have also seen other users express the same confusion. I discovered it because HAProxy was sending DNS queries more often than I expected, since I had set a long hold period.

Steps to Reproduce the Behavior

Not applicable

Do you have any idea what may have caused this?

No response

Do you have an idea how to solve the issue?

I recommend changing the description for hold to:

hold <status> <period>

Defines a <period> for which the backend server that relies on the DNS query for address resolution will keep its current up/down state when the DNS response is <status>. For example, if "hold nx 30s" and the DNS response is NXDOMAIN, maintain the server's up state for another 30 seconds before marking it as down due to the DNS being unable to resolve the domain.

  <status> : last name resolution status. Acceptable values are "nx",
             "other", "refused", "timeout", "valid", "obsolete".
  <period> : interval between two successive name resolution when the last
             answer was in <status>. It follows the HAProxy time format.
             <period> is in milliseconds by default.

What is your configuration?

Here is a `resolvers` section:


    resolvers mynameservers
       nameserver ns1 192.168.2.10:53
       nameserver ns2 192.168.3.10:53
       accepted_payload_size 512
       parse-resolv-conf
       hold valid    10s
       hold other    30s
       hold refused  30s
       hold nx       30s
       hold timeout  30s
       hold obsolete 30s
       resolve_retries 3
       timeout retry 1s
       timeout resolve 1s




### Output of `haproxy -vv`

```plain
$ haproxy -vv
HAProxy version 2.6-dev9-314e6e-13 2022/05/11 - https://haproxy.org/
Status: development branch - not safe for use in production.        
Known bugs: https://github.com/haproxy/haproxy/issues?q=is:issue+is:open
Running on: Linux 4.15.0-176-generic #185-Ubuntu SMP Tue Mar 29 17:40:04 UTC 2022 x86_64
Build options :
  TARGET  = linux-glibc
  CPU     = generic
  CC      = cc
  CFLAGS  = -O2 -g -Wall -Wextra -Wundef -Wdeclaration-after-statement -Wfatal-errors -Wtype-limits -Wshift-negative-value -Wshift-overflow=2 -Wduplicated-cond -Wnull-dereference -fwrapv -Wno-address-of-packed-member -Wno-unused-label -Wno-sign-compare -Wno-unused-parameter -Wno-clobbered -Wno-missing-field-initializers -Wno-cast-function-type -Wno-string-plus-int -Wno-atomic-alignment
  OPTIONS = USE_PCRE=1 USE_OPENSSL=1 USE_LUA=1 USE_ZLIB=1 USE_SYSTEMD=1 USE_PROMEX=1
  DEBUG   = -DDEBUG_STRICT -DDEBUG_MEMORY_POOLS

Feature list : +EPOLL -KQUEUE +NETFILTER +PCRE -PCRE_JIT -PCRE2 -PCRE2_JIT +POLL +THREAD +BACKTRACE -STATIC_PCRE -STATIC_PCRE2 +TPROXY +LINUX_TPROXY +LINUX_SPLICE +LIBCRYPT +CRYPT_H -ENGINE +GETADDRINFO +OPENSSL +LUA +ACCEPT4 -CLOSEFROM +ZLIB -SLZ +CPU_AFFINITY +TFO +NS +DL +RT -DEVICEATLAS -51DEGREES -WURFL +SYSTEMD -OBSOLETE_LINKER +PRCTL -PROCCTL +THREAD_DUMP -EVPORTS -OT -QUIC +PROMEX -MEMORY_PROFILING

Default settings :
  bufsize = 16384, maxrewrite = 1024, maxpollevents = 200

Built with multi-threading support (MAX_THREADS=64, default=2).
Built with OpenSSL version : OpenSSL 1.1.1  11 Sep 2018
Running on OpenSSL version : OpenSSL 1.1.1  11 Sep 2018
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
Built with Lua version : Lua 5.3.3
Built with the Prometheus exporter as a service
Built with network namespace support.
Built with zlib version : 1.2.11
Running on zlib version : 1.2.11
Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip")
Support for malloc_trim() is enabled.
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND
Built with PCRE version : 8.39 2016-06-14
Running on PCRE version : 8.39 2016-06-14
PCRE library supports JIT : no (USE_PCRE_JIT not set)
Encrypted password support via crypt(3): yes
Built with gcc compiler version 7.5.0

Available polling systems :
      epoll : pref=300,  test result OK
       poll : pref=200,  test result OK
     select : pref=150,  test result OK
Total: 3 (3 usable), will use epoll.

Available multiplexer protocols :
(protocols marked as <default> cannot be specified using 'proto' keyword)
         h2 : mode=HTTP  side=FE|BE  mux=H2    flags=HTX|HOL_RISK|NO_UPG
       fcgi : mode=HTTP  side=BE     mux=FCGI  flags=HTX|HOL_RISK|NO_UPG
  <default> : mode=HTTP  side=FE|BE  mux=H1    flags=HTX
         h1 : mode=HTTP  side=FE|BE  mux=H1    flags=HTX|NO_UPG
  <default> : mode=TCP   side=FE|BE  mux=PASS  flags=
       none : mode=TCP   side=FE|BE  mux=PASS  flags=NO_UPG

Available services : prometheus-exporter
Available filters :
        [CACHE] cache
        [COMP] compression
        [FCGI] fcgi-app
        [SPOE] spoe
        [TRACE] trace


### Last Outputs and Backtraces

_No response_

### Additional Information

_No response_

NickMRamirez avatar May 11 '22 15:05 NickMRamirez

CCing @bedis

wtarreau avatar May 12 '22 07:05 wtarreau

Hi there, I think @NickMRamirez is correct and his description describes better the behavior of the resolver.

bedis avatar Jul 28 '22 14:07 bedis

Just wanted to confirm I just spent about 15-30 minutes trying to confirm what was happening, so I fully support this update to the docs. Thanks @NickMRamirez. :)

To further clarify, could you answer the detail of what happens when a server is down due to a failure? For example, given:

hold timeout 60s
hold valid 10s

If the server is down after 61 seconds of timeout status and then receives a valid response, does it immediately flip to UP or does it wait for 10s?

lseelenbinder avatar Aug 11 '22 21:08 lseelenbinder

@NickMRamirez, your description is only true for UP->DOWN updates. But DOWN->UP updates are immediately performed when a valid response is received. And your right for all hold status except for hold.obsolete and hold.valid. hold.valid is different than others because it only says how long a valid response can be used by do-resolve action without triggering a new resolution. However it does not affect dynamic resolutions for servers. hold.obsolete is only used to remove obsolete items. It is only checked when a response containing at least an answer item is received. So it is not evaluated on NX responses or on timeout. If an item is considered as obsolete, it is removed. And, at this stage, the server state is only changed for SRV records. When a A/AAAA item is considered as obsolete, the server state is not changed. Then the result of the valid response is applied. It explains why it takes hold.obsolete seconds to change a server IP and why, when a SRV record is updated, there is a UP -> DOWN -> UP. Sorry it is probably a bit foggy but it is really a mess to describe :)

For others hold status, your description should probably be updated too. Because, based on your example, it suggests (at least it is my understanding :) that when a NX response is received, HAProxy will wait 30sec before setting the server to DOWN. In fact, the server is set to DOWN when the NX response is received if no valid response was received on the last 30sec. The action is not delayed, it is performed when the NX response is received.

@lseelenbinder, with your configuration, a server will be set to DOWN if a response timeout occurs when no valid response was received on the last 60sec. If a valid response is received, it is immediately set to UP. hold.valid is not used for server resolution.

capflam avatar Sep 07 '22 10:09 capflam

@capflam For that last part, I think we are saying something similar. You said:

In fact, the server is set to DOWN when the NX response is received if no valid response was received on the last 30sec.

UPDATE on my comment: Checking my understanding -- In that case, hold will depend wholly on how often name resolutions occur, as set by timeout resolve. If hold is looking backwards in time, then it must receive at least two NX responses within 30 seconds, for example.

I suppose this is a true statement:

<period> defines the subsequent span of time during which a NOERROR 
  response must be received, or else <status> will cause the server to be
  taken out of the load balancing rotation.

The idea being that it doesn't matter if you look back 30 seconds or forward 30 seconds. It only matters that you have a span of 30 seconds with no good (NOERROR) responses within it. Whether the point of reference is at the beginning of that span or the end of it, shouldn't matter?

NickMRamirez avatar Oct 08 '22 16:10 NickMRamirez

Well, it matters because only the last error code is processed, when it is received, not after the <period> delay. Previous error codes are not saved. For instance, with these timeouts:

hold nx 30s
hold timeout 300s

If you receive a first NXDOMAIN 1 second after the last valid resolution, the server status is not updated and this is expected. 30 seconds after the last valid resolution, if you receive a NXDOMAIN, the server will be set to DOWN. But if, instead of a NXDOMAIN, the resolution timed out, nothing happens because you should wait 300s to switch a server to DOWN on a timeout error. The previous NXDOMAIN error is lost. When an error is received, there is no timer. So I guess the doc should make it clear. The error is evaluated when it is received, comparing the current date against the date of the last valid resolution. If the delta exceeds <period>, the server is set to DOWN. Otherwise, the error is ignored.

Reading your proposal, I understand that an error is processed after the '<period> delay if no valid response is received in the mean time and independently on other errors. It is not pretty clear, at least for me, that the same error must be received again after a <period> delay (or more) to be really processed.

capflam avatar Oct 13 '22 08:10 capflam

Adding @bedis to the discussion to know what to put in the doc regarding this. In any case it's clear that this needs to be improved but we must be sure to write something realistic.

wtarreau avatar Nov 25 '22 08:11 wtarreau

ping ?

capflam avatar Jan 23 '23 07:01 capflam

Guys, is there a way to move forward on this issue ?

capflam avatar Feb 24 '23 08:02 capflam

I have revised my proposed definition to take into account the points raised by @capflam. How does this look?

hold <status> <period>

Upon receiving the DNS response <status>, determines whether a server's state 
should change from UP to DOWN. To make that determination, it checks whether 
any valid status has been received during the past <period> in order to counteract
the just received invalid status.

<status> : last name resolution status. 

           nx        After receiving an NXDOMAIN status, check for any valid 
                     status during the concluding period.

           refused   After receiving a REFUSED status, check for any valid
                     status during the concluding period.

           timeout   After the "timeout retry" has struck, check for any 
                     valid status during the concluding period.

           other     After receiving any other invalid status, check for any
                     valid status during the concluding period.

           valid     Applies only to "http-request do-resolve" and 
                     "tcp-request content do-resolve" actions. It defines the 
                     period for which the server will maintain a valid response
                     before triggering another resolution. It does not affect 
                     dynamic resolution of servers.

           obsolete  Defines how long to wait before removing obsolete DNS 
                     records after an updated answer record is received. It 
                     applies to SRV records.
 
<period> : Amount of time into the past during which a valid response must have 
           been received. It follows the HAProxy time format and is in milliseconds 
           by default.

For a server that relies on dynamic DNS resolution to determine its IP address, 
receiving an invalid DNS response, such as NXDOMAIN, will lead to changing the
server's state from UP to DOWN. The hold directives define how far into the past
to look for a valid response. If a valid response has been received within <period>,
the just received invalid status will be ignored.

Unless a valid response has been receiving during the concluding period, the server
will be marked as DOWN. For example, if "hold nx 30s" is set and the last received DNS
response was NXDOMAIN, the server will be marked DOWN unless a valid response has been
received during the last 30 seconds.

A server in the DOWN state will be marked UP immediately upon receiving a valid status
from the DNS server.

A separate behavior exists for "hold valid" and "hold obsolete".

NickMRamirez avatar Feb 26 '23 19:02 NickMRamirez

It seems good to me. Thanks @NickMRamirez, I will update the documentation.

capflam avatar Feb 27 '23 07:02 capflam

Now pushed. The patch will be backported with other fixes as usual. Many thanks @NickMRamirez !

capflam avatar Feb 27 '23 17:02 capflam