utils.docker.healthcheck package

Submodules

utils.docker.healthcheck.compose module

Turns a service’s healthcheck declaration into a rendered compose block.

A declaration names exactly one probe and any number of prefixes, either as one string or as a list. This module owns that grammar: the aliases, the validation and the timing arithmetic that decides what a composed probe inherits.

utils.docker.healthcheck.compose.build(flavor: str | list[str], overrides: dict[str, Any], **context: Any) → str

Render a service’s healthcheck block as YAML, starting at column zero.

Parameters:
  • flavor – a name, a list of prefixes plus one probe, or empty for an explicit test argv.

  • overrides – the service’s healthcheck entry from services.yml.

  • context – port, path, hostname, samples and any flavor specific extras.

Raises:

KeyError – a name is unknown.

utils.docker.healthcheck.compose.compose(flavors: str | list[str], **context: Any) → Probe

Build the probe a declaration asks for, prefixes applied.

Parameters:
  • flavors – one name, or a list of prefixes plus exactly one probe.

  • context – port, path, hostname, samples and any extras a part needs.

Returns:

The probe; with prefixes it is wrapped so test renders CMD-SHELL.

A prefix cannot judge the container, so the probe keeps the verdict and the prefix only ever RAISES a timing – the composed start period is the longest of the two, never the prefix’s alone. Lowering it once would have cut a fifteen minute start window down to two.

utils.docker.healthcheck.compose.known_flavors() → str
utils.docker.healthcheck.compose.resolve_flavors(flavors: str | list[str]) → list[str]

Expand what a service declared into a flat list of names.

Parameters:

flavors – one name, or the list form [msmtp, connect].

Returns:

The names in declaration order, aliases substituted.

utils.docker.healthcheck.compose.timing_rank(value: Any) → float

Order timings so the longest wins, whatever unit it is spelled in.

Parameters:

value – a docker duration such as 20s or 15m, or a bare count.

utils.docker.healthcheck.prefixes module

Prefixes: shell that runs before a probe without judging the container.

A prefix does something on the way past – sending a smoke mail is the only one so far – and deliberately cannot decide liveness. That separation is what lets a probe stay the single verdict.

class utils.docker.healthcheck.prefixes.MsmtpPrefix

Bases: object

Sends one test mail before the liveness probe it is composed with.

Not a probe: it never judges the container. Its timings are floors the composition may raise the probe’s to, because the first run has a relay handshake in front of it.

name = 'msmtp'
static render(context: dict[str, Any]) → str
retries = 5
start_period = '120s'
timeout = '20s'
utils.docker.healthcheck.prefixes.mail_branch(context: dict[str, Any]) → str

The one-shot smoke mail the msmtp prefix puts in front of a probe.

Parameters:

context – the probe context; needs email_enabled to do anything, plus domain – the EHLO identity msmtp otherwise defaults to localhost, which a strict relay refuses – and blackhole for the recipient.

Returns:

The shell prefix, or an empty string when email is disabled.

Two joins carry the whole design and neither is accidental. The branch ends in ; `` so a relay that refuses the message cannot decide liveness -- that verdict belongs to the probe this prefixes, and coupling them once left live web apps marked dead until the swarm converge gate gave up (ebdadd91b). The ``&& before touch stays, so a refused mail leaves no marker and the next probe retries instead of the smoke test becoming one-shot.

utils.docker.healthcheck.probes module

Probes: one class per flavor, each deciding whether the container serves.

Pure and Ansible free. Every probe targets the loopback INSIDE the container, so it proves that this container serves, not that some replica somewhere does.

class utils.docker.healthcheck.probes.Connect(**context: Any)

Bases: Probe

Proves only that the port accepts a TCP connection.

For a service that speaks something other than HTTP there – php-fpm answers FastCGI – so neither curl nor an HTTP request over the socket can judge it. /dev/tcp is a bash builtin, hence the explicit interpreter.

flavor: ClassVar[str] = 'connect'
retries: ClassVar[int] = 5
start_period: ClassVar[str] = '15m'
test() → list[str]
timeout: ClassVar[str] = '20s'
class utils.docker.healthcheck.probes.Curl(**context: Any)

Bases: Probe

curl against the loopback, optionally sampling the whole replica pool.

retries counts CONSECUTIVE failures, and a request that leaves the container is load balanced across the service VIP. With N replicas and one of them alive, a round robin never produces more than N-1 failures in a row, so a streak based check is arithmetically blind to losing all but one backend. Chaining samples requests into a single probe makes one probe cover the whole pool instead.

flavor: ClassVar[str] = 'curl'
test() → list[str]
timeout: ClassVar[str] = '10s'
class utils.docker.healthcheck.probes.Custom(**context: Any)

Bases: Probe

A service that spells its probe out instead of picking a flavor.

flavor: ClassVar[str] = 'custom'
test() → list[str]
class utils.docker.healthcheck.probes.Http(**context: Any)

Bases: Probe

Whichever of wget or curl the image happens to ship.

The wget half stays proxy-aware on purpose: busybox wget rejects --proxy=off, and this flavor exists for images where which tool ships is unknown, so hardening it there would break the very case it covers.

flavor: ClassVar[str] = 'http'
retries: ClassVar[int] = 5
test() → list[str]
class utils.docker.healthcheck.probes.HttpStatus(**context: Any)

Bases: Tcp

Like Tcp, but insists the status line is 2xx or 3xx.

flavor: ClassVar[str] = 'http_status'
test() → list[str]
class utils.docker.healthcheck.probes.Nc(**context: Any)

Bases: Probe

flavor: ClassVar[str] = 'nc'
test() → list[str]
timeout: ClassVar[str] = '3s'
class utils.docker.healthcheck.probes.Probe(**context: Any)

Bases: object

Base for every flavor: timings plus the argv docker executes.

block(overrides: dict[str, Any]) → dict[str, Any]

Assemble the healthcheck mapping.

start_interval is the cadence while start_period is still running. Without it a probe only fires every interval, so a service ready after three seconds is reported healthy at the next tick – 30s, or a minute for the curl flavors – and a stack converges no faster than its slowest quantisation. It changes when the same probe runs, never what it tests.

start_period therefore bounds it: once the window closes the cadence falls back to interval, so a service that becomes ready just after the window pays a full tick anyway. The window has to outlast a realistic boot, not merely a fast one, which is why it is measured in minutes rather than sized to interval. It costs nothing while the container works, only the time until a broken one is called unhealthy.

Parameters:

overrides – service level values that win over the flavor defaults.

flavor: ClassVar[str] = ''
interval: ClassVar[str] = '2m'
retries: ClassVar[int] = 3
shell() → str

The same command as one shell string, so a prefix can compose with it.

Returns:

The CMD-SHELL payload, or the exec argv quoted for a shell.

start_interval: ClassVar[str] = '5s'
start_period: ClassVar[str] = '10m'
test() → list[str]
timeout: ClassVar[str] = '5s'
property url: str
class utils.docker.healthcheck.probes.Tcp(**context: Any)

Bases: Probe

Speaks HTTP over a bash socket, for images without curl or wget.

flavor: ClassVar[str] = 'tcp'
request() → str
test() → list[str]
class utils.docker.healthcheck.probes.Wget(**context: Any)

Bases: Probe

flavor: ClassVar[str] = 'wget'
test() → list[str]
timeout: ClassVar[str] = '10s'
utils.docker.healthcheck.probes.curl_argv(*flags: str, url: str, hostname: str | None = None) → list[str]

Build a curl invocation as argv, for docker’s exec form.

Parameters:
  • flags – curl flags this probe wants, e.g. -f or -fsS.

  • url – the URL to request.

  • hostname – sent as a Host header when the vhost matters.

Returns:

The argv list, proxy-free.

Every probe here targets the container’s own loopback, so a request that a proxy picks up is wrong by definition – the Wget probe has said so with --proxy=off since it was written. Both spellings come from here so the exec form and the shell form cannot drift: the shell form needs the wildcard quoted and the exec form needs it bare, which is exactly the kind of difference that rots when it is written out three times.

utils.docker.healthcheck.probes.curl_shell(*flags: str, url: str, hostname: str | None = None) → str

The CMD-SHELL spelling of curl_argv(), quoted for a shell.

Module contents