Drain API pods before Kubernetes shutdown
This commit is contained in:
@@ -270,6 +270,10 @@ record under the installation evidence directory and never retains the API key.
|
|||||||
|
|
||||||
Use `--exercise-api-pod-loss` in an approved drill window to delete one API pod,
|
Use `--exercise-api-pod-loss` in an approved drill window to delete one API pod,
|
||||||
observe the public readiness path continuously, and record its replacement.
|
observe the public readiness path continuously, and record its replacement.
|
||||||
|
Generated API workloads use a ten-second pre-stop drain so Kubernetes can remove
|
||||||
|
the terminating endpoint from ingress and service routing before Uvicorn exits.
|
||||||
|
Do not remove or shorten this drain without repeating the public-path pod-loss
|
||||||
|
test against the target ingress controller and network implementation.
|
||||||
This proves the bounded stateless-node-loss slice only. Session continuity,
|
This proves the bounded stateless-node-loss slice only. Session continuity,
|
||||||
accepted-job redelivery, state-service failover, and coordinated restore remain
|
accepted-job redelivery, state-service failover, and coordinated restore remain
|
||||||
separate target exercises whose signed evidence is governed by
|
separate target exercises whose signed evidence is governed by
|
||||||
|
|||||||
@@ -460,6 +460,22 @@ class DeploymentInstallerTests(unittest.TestCase):
|
|||||||
"containers"
|
"containers"
|
||||||
][0]["readinessProbe"]["httpGet"]["httpHeaders"],
|
][0]["readinessProbe"]["httpGet"]["httpHeaders"],
|
||||||
)
|
)
|
||||||
|
api_pod_spec = deployments["govoplan-cluster-api"]["spec"]["template"][
|
||||||
|
"spec"
|
||||||
|
]
|
||||||
|
self.assertEqual(30, api_pod_spec["terminationGracePeriodSeconds"])
|
||||||
|
self.assertEqual(
|
||||||
|
["/bin/sh", "-c", "sleep 10"],
|
||||||
|
api_pod_spec["containers"][0]["lifecycle"]["preStop"]["exec"][
|
||||||
|
"command"
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.assertNotIn(
|
||||||
|
"lifecycle",
|
||||||
|
deployments["govoplan-cluster-worker"]["spec"]["template"]["spec"][
|
||||||
|
"containers"
|
||||||
|
][0],
|
||||||
|
)
|
||||||
worker_command = deployments["govoplan-cluster-worker"]["spec"]["template"][
|
worker_command = deployments["govoplan-cluster-worker"]["spec"]["template"][
|
||||||
"spec"
|
"spec"
|
||||||
]["containers"][0]["command"]
|
]["containers"][0]["command"]
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ def render_kubernetes(
|
|||||||
readiness_path="/health/ready",
|
readiness_path="/health/ready",
|
||||||
liveness_path="/health",
|
liveness_path="/health",
|
||||||
probe_host=public_host,
|
probe_host=public_host,
|
||||||
|
graceful_shutdown_seconds=10,
|
||||||
extra_environment=_role_database_environment(environment, "API"),
|
extra_environment=_role_database_environment(environment, "API"),
|
||||||
),
|
),
|
||||||
_service(
|
_service(
|
||||||
@@ -658,6 +659,7 @@ def _deployment(
|
|||||||
readiness_path: str | None = None,
|
readiness_path: str | None = None,
|
||||||
liveness_path: str | None = None,
|
liveness_path: str | None = None,
|
||||||
probe_host: str | None = None,
|
probe_host: str | None = None,
|
||||||
|
graceful_shutdown_seconds: int = 0,
|
||||||
extra_environment: Mapping[str, str] | None = None,
|
extra_environment: Mapping[str, str] | None = None,
|
||||||
selector_labels: Mapping[str, str] | None = None,
|
selector_labels: Mapping[str, str] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -715,6 +717,18 @@ def _deployment(
|
|||||||
container_port or 8000,
|
container_port or 8000,
|
||||||
host=probe_host,
|
host=probe_host,
|
||||||
)
|
)
|
||||||
|
if graceful_shutdown_seconds:
|
||||||
|
container["lifecycle"] = {
|
||||||
|
"preStop": {
|
||||||
|
"exec": {
|
||||||
|
"command": [
|
||||||
|
"/bin/sh",
|
||||||
|
"-c",
|
||||||
|
f"sleep {graceful_shutdown_seconds}",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
pod_spec: dict[str, Any] = {
|
pod_spec: dict[str, Any] = {
|
||||||
"serviceAccountName": service_account,
|
"serviceAccountName": service_account,
|
||||||
"automountServiceAccountToken": False,
|
"automountServiceAccountToken": False,
|
||||||
@@ -734,6 +748,11 @@ def _deployment(
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
if graceful_shutdown_seconds:
|
||||||
|
pod_spec["terminationGracePeriodSeconds"] = max(
|
||||||
|
30,
|
||||||
|
graceful_shutdown_seconds + 20,
|
||||||
|
)
|
||||||
container["volumeMounts"] = [{"name": "tmp", "mountPath": "/tmp"}]
|
container["volumeMounts"] = [{"name": "tmp", "mountPath": "/tmp"}]
|
||||||
if s3_ca_secret_name:
|
if s3_ca_secret_name:
|
||||||
pod_spec["volumes"].append(_s3_ca_volume(s3_ca_secret_name))
|
pod_spec["volumes"].append(_s3_ca_volume(s3_ca_secret_name))
|
||||||
|
|||||||
Reference in New Issue
Block a user