MEDIUM: tcpcheck/server: Add healthcheck server keyword

Thanks to this patch, it is now possible to specify an healthcheck section
on the server line. In that case, the server will use the tcpcheck as
defined in the correspoding healthcheck section instead of the proxy's one.
This commit is contained in:
Christopher Faulet 2026-03-27 16:24:16 +01:00
parent 89da4e7d6e
commit b49200ca31
5 changed files with 168 additions and 1 deletions

View file

@ -18561,6 +18561,18 @@ hash-key <key>
uses "hash-type consistent", and the quality of the distribution will depend
on the quality of the keys.
healthchec <name>
May be used in the following contexts: tcp, http
Specify the health-check sectino to use to perform check on the server.
Argument :
<name> is the health-check section name.
Thanks to this option, it is possible to use a pre-server health-check
configuration instead of using the proxy configuration. See also "healthcheck
section".
id <value>
May be used in the following contexts: tcp, http, log
@ -32251,6 +32263,81 @@ Example:
map virt@acme
12.9. Healthchecks
------------------
It is possible to globally declare several health-checks that could be used by
servers across all the configuration, overriding the local proxy configuration.
healthcheck <name>
Created a new healthcheck with name <name>. This name must be unique. It
should be used on server line to reference a specific health-check section.
type <type>
Defines the health-check type. This parameter is mandatory. Following types
of health-check are supported:
* tcp-check
* httpchk
* ssl-hello-chk
* smtpchk
* pgsql-check
* redis-check
* mysql-check
* ldap-check
* spop-check
Each type uses the same parameters, if any, than the corresponding proxy's
option. For instance, the method, the uri... may be speficied for the
"httpchk" type:
Examples :
healthcheck my-http-check
type httpchk GET /health HTTP/1.1 %[srv_name]
See also : "option tcp-check", "option httpchk", "option ssl-hello-chk",
"option smtpchk", "option mysql-check", "option pgsql-check",
"option redis-check", "option ldap-check and "option spop-check"
http-check comment <string>
http-check connect [default] [port <expr>] [addr <ip>] [send-proxy]
[via-socks4] [ssl] [sni <sni>] [alpn <alpn>] [linger]
[proto <name>] [comment <msg>]
http-check disable-on-404
http-check expect [min-recv <int>] [comment <msg>]
[ok-status <st>] [error-status <st>] [tout-status <st>]
[on-success <fmt>] [on-error <fmt>] [status-code <expr>]
[!] <match> <pattern>
http-check send [meth <method>] [{ uri <uri> | uri-lf <fmt> }>] [ver <version>]
[hdr <name> <fmt>]* [{ body <string> | body-lf <fmt> }]
[comment <msg>]
http-check send-state
http-check set-var(<var-name>[,<cond>...]) <expr>
http-check set-var-fmt(<var-name>[,<cond>...]) <fmt>
http-check unset-var(<var-name>)
Add a specific http-check rule for a "httpchk" healthcheck. The same syntax
than the corresponding proxy's directives is used. See the corresponding proxy
documentation for details.
tcp-check comment <string>
tcp-check connect [default] [port <expr>] [addr <ip>] [send-proxy] [via-socks4]
[ssl] [sni <sni>] [alpn <alpn>] [linger]
[proto <name>] [comment <msg>]
tcp-check expect [min-recv <int>] [comment <msg>]
[ok-status <st>] [error-status <st>] [tout-status <st>]
[on-success <fmt>] [on-error <fmt>] [status-code <expr>]
[!] <match> <pattern>
tcp-check send <data> [comment <msg>]
tcp-check send-lf <fmt> [comment <msg>]
tcp-check send-binary <hexstring> [comment <msg>]
tcp-check send-binary-lf <hexfmt> [comment <msg>]
tcp-check set-var(<var-name>[,<cond>...]) <expr>
tcp-check set-var-fmt(<var-name>[,<cond>...]) <fmt>
tcp-check unset-var(<var-name>)
Add a specific tcp-check rule for a "tcp-check" healthcheck. The same syntax
than the corresponding proxy's directives is used. See the corresponding proxy
documentation for details.
/*
* Local variables:
* fill-column: 79

View file

@ -248,6 +248,7 @@ struct tcpcheck_ruleset {
struct tcpcheck {
struct tcpcheck_ruleset *rs; /* The tcp-check ruleset to use */
char *healthcheck; /* name of the healthcheck section (NULL if not used) */
struct list preset_vars; /* The list of variable to preset before executing the ruleset */
unsigned int flags; /* TCPCHECK_FL_* */
};

View file

@ -1552,8 +1552,9 @@ void free_check(struct check *check)
* done for health-check : the proxy is the owner of the rules / vars
* in this case.
*/
if (check->state & CHK_ST_AGENT) {
if (check->state & CHK_ST_AGENT || check->tcpcheck->healthcheck) {
free_tcpcheck_vars(&check->tcpcheck->preset_vars);
ha_free(&check->tcpcheck->healthcheck);
ha_free(&check->tcpcheck);
}

View file

@ -2537,6 +2537,10 @@ int proxy_finalize(struct proxy *px, int *err_code)
srv_minmax_conn_apply(newsrv);
*err_code |= check_server_tcpcheck(newsrv);
if (*err_code & (ERR_ABORT|ERR_FATAL))
goto out;
/* this will also properly set the transport layer for
* prod and checks
* if default-server have use_ssl, prerare ssl init

View file

@ -4164,6 +4164,35 @@ static int check_proxy_tcpcheck(struct proxy *px)
return ret;
}
int check_server_tcpcheck(struct server *srv)
{
struct tcpcheck_ruleset *rs;
int err_code = 0;
if (srv->check.tcpcheck->healthcheck) {
rs = find_tcpcheck_ruleset(srv->check.tcpcheck->healthcheck);
if (!rs) {
ha_alert("parsing [%s:%d]: healthcheck section '%s' not found for server '%s'.\n",
srv->conf.file, srv->conf.line, srv->check.tcpcheck->healthcheck, srv->id);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
if (!dup_tcpcheck_vars(&srv->check.tcpcheck->preset_vars, &rs->conf.preset_vars)) {
ha_alert("parsing [%s:%d]: unable to duplicate preset variables for server '%s'.\n",
srv->conf.file, srv->conf.line, srv->id);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
srv->check.tcpcheck->rs = rs;
srv->check.tcpcheck->flags = rs->conf.flags;
err_code = check_tcpcheck_ruleset(srv->proxy, rs);
}
out:
return err_code;
}
void deinit_proxy_tcpcheck(struct proxy *px)
{
free_tcpcheck_vars(&px->tcpcheck.preset_vars);
@ -5920,6 +5949,43 @@ int cfg_post_parse_healthchecks()
return err_code;
}
/* Parse the "healthcheck" server keyword */
static int srv_parse_healthcheck(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
char **errmsg)
{
int err_code = 0;
if (!*args[*cur_arg+1]) {
memprintf(errmsg, "'%s' expects a healthcheck name as argument.", args[*cur_arg]);
err_code |= ERR_ALERT | ERR_ABORT;
goto out;
}
if (srv->check.tcpcheck->healthcheck) {
/* a healthcheck section was already defined. Replace it */
ha_free(&srv->check.tcpcheck->healthcheck);
}
else {
srv->check.tcpcheck = calloc(1, sizeof(*srv->check.tcpcheck));
if (srv->check.tcpcheck == NULL) {
memprintf(errmsg, "out of memory");
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
LIST_INIT(&srv->check.tcpcheck->preset_vars);
}
if (!memprintf(&srv->check.tcpcheck->healthcheck, "*healthcheck-%s", args[*cur_arg+1])) {
ha_free(&srv->check.tcpcheck);
memprintf(errmsg, "out of memory");
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
out:
return err_code;
}
static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_LISTEN, "http-check", proxy_parse_httpcheck },
{ CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
@ -5932,3 +5998,11 @@ REGISTER_POST_DEINIT(deinit_tcpchecks);
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
REGISTER_CONFIG_SECTION("healthcheck", cfg_parse_healthchecks, cfg_post_parse_healthchecks);
/* register "server" line keywords */
static struct srv_kw_list srv_kws = { "CHK", { }, {
{ "healthcheck", srv_parse_healthcheck, 1, 1, 1 }, /* The healthcheck section to use */
{ NULL, NULL, 0 },
}};
INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);