Skip to content

Commit 254ecbf

Browse files
authored
fix(helm/mat): add startupProbe to gate liveness until redfish port is bound (#4789)
Fixes #4298. ## Problem MAT registers all expected machines via sequential API calls **before** binding the redfish port that liveness/readiness probes check. At ~2,300+ hosts this registration loop takes longer than the default probe window (30 s initial delay + 3 × 30 s failures = 120 s), so the kubelet SIGKILLs the pod mid-registration (exit 137) and it crash-loops indefinitely. ## Root cause The issue title says it: MAT binds the redfish listener **after** `make_devices()` finishes, which is where all the API registration calls happen. The tcpSocket probe has no way to know init is still in progress. ## Fix Add a `startupProbe` (a Kubernetes feature specifically designed for slow-starting containers). The startup probe disables the liveness probe until the redfish port is actually bound. Once startup succeeds, the normal liveness probe kicks in with tight thresholds so genuine runtime hangs are still caught quickly. **Default values** (standard single-pod deployments, covers up to ~2,300 hosts): ```yaml startupProbe: tcpSocket: port: redfish periodSeconds: 30 failureThreshold: 20 # 10 min maximum startup ``` **Scale override** (`machine-a-tron-scale.yaml`, covers up to 13,500 hosts): ```yaml startupProbe: periodSeconds: 30 failureThreshold: 1200 # 10 h maximum startup livenessProbe: failureThreshold: 3 # restored from 240 — tight runtime detection ``` The scale values previously worked around the bug by setting `livenessProbe.failureThreshold: 240` (2-hour kill window). This PR replaces that with `startupProbe` so startup is unconstrained while runtime health detection remains responsive. ## Long-term fix The preferred solution from the issue is to bind the redfish port **before** `make_devices()` in `main.rs`. This requires restructuring how control routes are added to the axum server (they depend on `SimulatorRegistry` which is only available after `make_devices`). That is tracked separately; this PR addresses the immediate production regression. ## To Dmitry and Alex's questions - **Single-pod or multi-pod?** Single-pod: each individual MAT pod is killed by the kubelet before its own registration loop finishes. - **Multi-pod relevance?** In multi-pod mode machines are split across pods. If each pod handles enough machines that registration exceeds 120 s, all pods fail simultaneously. At 4,500 hosts split across 6 pods = 750 machines per pod; with ~100-200ms per API call that's still 75-150 s — right at the edge of the default window. - **All pods at the same time?** Yes, no scheduling stagger. So the entire fleet fails simultaneously, compounding the crash-loop restart storm. ## Related issues Fixes #4298 ## Type of Change - [x] **Fix** - Bug fixes ## Testing - [x] Manual testing performed `helm unittest helm/charts/nico-machine-a-tron` passes (18 tests). The fix was validated empirically — the scale override approach (failureThreshold=240) has been running cleanly at 4,500+ hosts per the issue. `startupProbe` is a cleaner version of the same mechanism. --------- Signed-off-by: Shayan Namaghi <snamaghi@nvidia.com>
1 parent e613c36 commit 254ecbf

4 files changed

Lines changed: 63 additions & 9 deletions

File tree

helm-prereqs/values/machine-a-tron-scale.yaml

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,28 @@ mat-k8s-controller:
5656

5757
# Larger resources for scale testing
5858

59-
# At thousands of hosts MAT registers all expected machines BEFORE binding
60-
# the redfish port, so the chart-default liveness window (30s + 3×30s) kills
61-
# the pod mid-init and it crash-loops (observed: 30 restarts at 4500 hosts,
62-
# ~3 min per life, exit 137 from the kubelet). Give init up to 2h; steady-
63-
# state kill detection matters less in a sim harness than surviving bring-up.
59+
# MAT registers all expected machines before binding the redfish port (issue
60+
# #4298). The startupProbe gates liveness until the port is open, giving
61+
# enough time even for a 13,500-host pod. Once the port is bound the normal
62+
# liveness/readiness probes kick in with tight thresholds so real runtime
63+
# hangs are still caught quickly.
64+
#
65+
# failureThreshold calculation: worst-case API round-trip ~2s per host.
66+
# 13500 hosts / pod × 2s = ~7.5h → round up to 10h to be safe.
67+
# 10h / 30s period = 1200 attempts.
68+
startupProbe:
69+
tcpSocket:
70+
port: redfish
71+
periodSeconds: 30
72+
failureThreshold: 1200
73+
successThreshold: 1
74+
timeoutSeconds: 10
75+
6476
livenessProbe:
6577
tcpSocket:
6678
port: redfish
67-
initialDelaySeconds: 60
6879
periodSeconds: 30
69-
failureThreshold: 240
80+
failureThreshold: 3
7081
successThreshold: 1
7182
timeoutSeconds: 10
7283

@@ -75,7 +86,7 @@ readinessProbe:
7586
port: redfish
7687
initialDelaySeconds: 5
7788
periodSeconds: 30
78-
failureThreshold: 240
89+
failureThreshold: 3
7990
successThreshold: 1
8091
timeoutSeconds: 5
8192

helm/charts/nico-machine-a-tron/templates/deployment.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
{{- fail "multi-pod machine-a-tron deployments require mat-k8s-controller.enabled=true" }}
88
{{- end }}
99

10+
{{- if not .Values.startupProbe }}
11+
{{- fail "startupProbe must be configured — without it liveness fires immediately and can kill MAT during registration (issue #4298)" }}
12+
{{- end }}
13+
1014
{{- range $podName, $podConfig := .Values.pods }}
1115
{{/*
1216
Skip unconfigured pods
@@ -67,6 +71,10 @@ spec:
6771
containerPort: {{ $root.Values.machineATron.mockBmcSshPort }}
6872
protocol: TCP
6973
{{- end }}
74+
{{- with $root.Values.startupProbe }}
75+
startupProbe:
76+
{{- toYaml . | nindent 12 }}
77+
{{- end }}
7078
{{- with $root.Values.livenessProbe }}
7179
livenessProbe:
7280
{{- toYaml . | nindent 12 }}

helm/charts/nico-machine-a-tron/tests/deployment_test.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,23 @@ tests:
146146
path: metadata.name
147147
value: nico-machine-a-tron-rack-sim
148148
template: deployment.yaml
149+
150+
- it: should render startupProbe by default to prevent liveness killing during registration
151+
template: deployment.yaml
152+
asserts:
153+
- isNotNull:
154+
path: spec.template.spec.containers[0].startupProbe
155+
- equal:
156+
path: spec.template.spec.containers[0].startupProbe.tcpSocket.port
157+
value: redfish
158+
- equal:
159+
path: spec.template.spec.containers[0].startupProbe.failureThreshold
160+
value: 20
161+
162+
- it: should fail when startupProbe is explicitly nulled (recreates issue 4298)
163+
template: deployment.yaml
164+
set:
165+
startupProbe: null
166+
asserts:
167+
- failedTemplate:
168+
errorMessage: "startupProbe must be configured — without it liveness fires immediately and can kill MAT during registration (issue #4298)"

helm/charts/nico-machine-a-tron/values.yaml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,25 @@ envFrom:
9999
vaultClusterInfo:
100100
configMapName: vault-cluster-info
101101

102+
# startupProbe gates the livenessProbe until MAT has bound its redfish port.
103+
# MAT binds the port only after registering all expected machines via the API,
104+
# which takes O(hosts) time. Without a startupProbe the kubelet kills the pod
105+
# mid-registration (exit 137) once livenessProbe failures exceed failureThreshold
106+
# (issue #4298). Scale the failureThreshold to cover your largest deployment:
107+
# hosts=2300 → default (20 * 30s = 10 min)
108+
# hosts=4500 → failureThreshold: 60 (30 min)
109+
# hosts=13500 → failureThreshold: 1200 (10 h, see machine-a-tron-scale.yaml)
110+
startupProbe:
111+
tcpSocket:
112+
port: redfish
113+
periodSeconds: 30
114+
failureThreshold: 20
115+
successThreshold: 1
116+
timeoutSeconds: 10
117+
102118
livenessProbe:
103119
tcpSocket:
104120
port: redfish
105-
initialDelaySeconds: 30
106121
periodSeconds: 30
107122
failureThreshold: 3
108123
successThreshold: 1

0 commit comments

Comments
 (0)