clusterd: SIGTERM handler + in-process gRPC FQDN (Variant A)#36100
clusterd: SIGTERM handler + in-process gRPC FQDN (Variant A)#36100jasonhernandez wants to merge 1 commit into
Conversation
|
Thanks for opening this PR! Here are a few tips to help make the review process smooth for everyone. PR title guidelines
Pre-merge checklist
|
f7adf0b to
6aaafcb
Compare
Distroless images have no shell, so there's no init process to forward signals. Add a direct SIGTERM handler in clusterd (using nix::signal) and environmentd so graceful shutdown works without a wrapper script. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6aaafcb to
c23ae38
Compare
Distroless images run as nonroot (UID 65534) instead of root. Add version-gating so orchestratord sets the correct runAsUser/runAsGroup based on the Materialize version, avoiding UID mismatches during rolling upgrades from Debian-based to distroless images. Gate versions (verified against release history, 2026-06): - balancerd: V26_18_0. Its ci/Dockerfile switched to distroless-prod-base in v26.18.0 (prod-base in v26.17.x). The original V26_19_0 was off by one and would have forced UID 999 onto v26.18.x balancerd pods that actually run as 65534. - environmentd/clusterd: V26_28_0, matching the release that ships their distroless migration (#36099). The original V26_20_0 predated the actual landing by ~8 releases (main is now 26.28-dev) and would have applied UID 65534 to v26.20-v26.27 images that still run as UID 999. NOTE: the env/clusterd gate assumes #36099 lands in the 26.28 cycle. If it slips, bump V26_28_0 to the actual release. The three distroless PRs (#36099 image, #36100 SIGTERM, #36101 this) must ship in the same release. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🔗 Distroless migration — coordination noteOne of three PRs that ship together: #36099 (distroless image), #36100 (this — SIGTERM handler), #36101 (UID/GID gating). Merge this before or with #36099: distroless runs the binary as PID 1, which silently ignores SIGTERM without an explicit handler. Safe to merge first (harmless on current images). environmentd already handles termination signals, so this change is correctly clusterd-only despite the commit subject. |
Distroless containers run the binary directly as PID 1 (no tini/shell), so two things from the removed entrypoint.sh are handled here: 1. PID 1 ignores signals with a SIG_DFL disposition, so SIGTERM from Kubernetes is silently dropped. Add an explicit termination-signal handler in clusterd (environmentd already has one). The StatefulSet ordinal (CLUSTERD_PROCESS) is derived in-process from the hostname. 2. entrypoint.sh set CLUSTERD_GRPC_HOST via `hostname --fqdn`. That value fed the CTP handshake's optional `server_fqdn` check: clusterd advertised its FQDN and the controller compared it against the address it dialed (transport.rs::handshake). The check only fired when the value was set, is unrelated to gRPC despite the name, and guards only against reaching a misrouted replica. Rather than re-plumb it for distroless, remove it: drop `--grpc-host`/`CLUSTERD_GRPC_HOST` and the `server_fqdn` field from the CTP `Hello`, along with the now-unused `host_from_address` helper and the `test_handshake_fqdn_mismatch` test. test_metrics byte-count bounds loosened since the handshake shrank. This is the "rip it out" alternative to the FQDN-handling PRs (#36100 in-process resolve, #36872 orchestrator-injected). Pick one. Part of SEC-236 distroless migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Closing in favor of the #36872 → #36876 stack. The SIGTERM handler from this PR now lives in #36872 (minimal). The FQDN question this PR solved with an in-process |
Design question: where should clusterd's gRPC FQDN come from?
This is Variant A of a two-version pair (see #36872 for Variant B). Both add the SIGTERM handler that distroless clusterd needs as PID 1. They differ only in how
CLUSTERD_GRPC_HOSTis set onceentrypoint.shis removed by the distroless migration (#36099).entrypoint.shpreviously did, in Kubernetes only:getaddrinfo(AI_CANONNAME)(resolve_fqdn)MZ_POD_NAME+ env interpolationgetaddrinfohas no timeout; if CoreDNS is briefly down at pod start, clusterd's main thread blocksmain()resolve_fqdn+CLUSTERD_PROCESSCLUSTERD_PROCESSonlyEnvVars in the pod specWhy Variant A
Keeps the change confined to the
clusterdbinary — no orchestrator changes, and it works under any orchestrator (not just k8s) that gives the pod a resolvable hostname. It directly mirrors whatentrypoint.shdid (hostname --fqdn).Known downside
resolve_fqdncallsgetaddrinfowith no timeout on the main thread at startup (flagged in the code comment). If DNS is unavailable at pod start, clusterd blocks. This is the same behavior the old shell had, so it's not a regression — but Variant B avoids it entirely by injecting the value from the orchestrator, which already computes the same FQDN.Part of the distroless migration (#36099 image, this/#36872 lifecycle, #36101 UID/GID).
Test plan
cargo check -p mz-clusterd(rustc 1.96.0)resolve_fqdnyields the pod FQDN and gRPC host validation passes🤖 Generated with Claude Code