refactor: the image carries its own healthcheck and picks it by role (4295)
CI / extension-version (push) Successful in 4s
CI / lint (push) Successful in 4s
Build images / sign-extension (push) Successful in 5s
Build images / build-agent (push) Successful in 7s
extension / lint (push) Successful in 18s
CI / frontend-build (push) Successful in 24s
CI / backend-lint-and-test (push) Successful in 33s
Build images / build-web (push) Successful in 1m42s
CI / integration (push) Successful in 2m11s
Build images / smoke-web (push) Successful in 57s
Build images / promote (push) Skipped
CI / extension-version (push) Successful in 4s
CI / lint (push) Successful in 4s
Build images / sign-extension (push) Successful in 5s
Build images / build-agent (push) Successful in 7s
extension / lint (push) Successful in 18s
CI / frontend-build (push) Successful in 24s
CI / backend-lint-and-test (push) Successful in 33s
Build images / build-web (push) Successful in 1m42s
CI / integration (push) Successful in 2m11s
Build images / smoke-web (push) Successful in 57s
Build images / promote (push) Skipped
Operator, 2026-09-23: *"why isn't the healthcheck built into the image or
base on what command runs if one is passed in. why is it manually declared in
the stack here."*
No good reason. The container is the only thing that knows what it was asked
to run, and every compose file, stack file and README had to restate it:
web -> urllib /api/health
worker -> celery inspect ping -d celery@$HOSTNAME
all -> both, for every lane
Three checks written by hand, once per service, in every file anyone ever
wrote — none of them wrong until a role changed, and all of them silently
wrong after. The same duplication the lane table exists to remove one level
down, and I built it without noticing.
`entrypoint.sh` now records the role it started. The Dockerfile declares ONE
`HEALTHCHECK` that reads it and asks the right question: HTTP for web, a
self-addressed celery ping for a worker lane, both-for-every-lane for `all`,
and nothing for shell/alembic, which are one-shot and have no liveness to
probe. `docker-compose.single.yml` and the consolidated stack declare none.
A service that wants something else can still declare its own; docker prefers
it, so the escape hatch is the default docker behaviour rather than a flag.
Two details that are load-bearing:
* The role is written ONCE, by the outermost invocation. `all` starts the
other roles through this same script under supervisord, and a child
overwriting the container's role would turn the composite check into a
web-only one — silently, and only on the consolidated path. FC_ROLE is
exported so a child sees it set and skips.
* The celery ping is addressed to THIS node, not a bare ping. A bare one is
answered by any worker on the broker, so in a stack with replicas a dead
container would report healthy for as long as a sibling lived — the check
would be measuring the cluster rather than the container it is inside.
`healthcheck_all.py` is deleted; its two probes moved into the dispatcher
rather than being a second copy beside it.
An unrecorded role PASSES. The entrypoint always writes the file, so the only
way to miss it is bypassing the entrypoint — a debugging shape, where a check
that cannot tell what it is looking at must not assert the thing is broken
(snippet #3969). Said on stdout rather than assumed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
This commit is contained in:
@@ -31,6 +31,26 @@ ROLE="${1:-all}"
|
||||
# service in the multi-service stack — it falls back to `celery`, exactly
|
||||
# celery's own default, so `celery@$HOSTNAME` healthchecks there still work.
|
||||
: "${CELERY_NODENAME:=celery}"
|
||||
|
||||
# RECORD THE ROLE, for the image's own HEALTHCHECK to read.
|
||||
#
|
||||
# The container is the only thing that knows what it was asked to run, and
|
||||
# before this every compose and stack file had to restate it as a healthcheck
|
||||
# of its own. The Dockerfile now declares one check that dispatches on this.
|
||||
#
|
||||
# Written ONCE, by the outermost invocation. The `all` role starts the other
|
||||
# roles through this same script under supervisord, and those children must
|
||||
# not overwrite the container's role with their own — a lane starting would
|
||||
# turn the composite check into a web-only one, silently. FC_ROLE is exported,
|
||||
# so a child sees it set and skips.
|
||||
#
|
||||
# Best effort: a read-only /tmp is not a reason to refuse to boot. The
|
||||
# healthcheck treats a missing file as "nothing to check" rather than as a
|
||||
# failure, for the same reason.
|
||||
if [ -z "${FC_ROLE:-}" ]; then
|
||||
export FC_ROLE="$ROLE"
|
||||
printf '%s\n' "$ROLE" > "${FC_ROLE_FILE:-/tmp/fc-role}" 2>/dev/null || true
|
||||
fi
|
||||
shift || true
|
||||
|
||||
case "$ROLE" in
|
||||
|
||||
Reference in New Issue
Block a user