Skip to content

fix(proxy): promote shutdown-manager to native sidecar to close preStop drain race on k8s 1.33+ #9484

Open

Description

@ChrisJBurns

On Kubernetes 1.33+, envoy and shutdown-manager are ordinary sibling containers with no termination ordering guarantee. When kubelet sends SIGTERM during a pod rollout, both containers receive it simultaneously.

Envoy's preStop hook (httpGet :19002/shutdown/ready) is designed to block until shutdown-manager confirms that in-flight connections have drained. But because shutdown-manager can exit before the hook completes its polling loop, the hook fails immediately with FailedPreStopHook rather than waiting out the drain window. Kubelet then kills envoy with requests still in flight, producing client-visible 5xx errors on every proxy rollout.

Confirmed on: Envoy AI Gateway v1.7.0, Kubernetes 1.33

Reproduce

  1. Deploy a proxy with active traffic
  2. Trigger a proxy pod rollout (update EnvoyProxy or any setting causing pod recreation)
  3. Observe FailedPreStopHook events followed by connection-refused on port 19002:
Warning  FailedPreStopHook  pod/envoy-<hash>  preStop hook for container "envoy" failed

Surfaced during a routine gateway rollout — a cluster of 5xx responses traceable to FailedPreStopHook events immediately followed by refused connections to the shutdown-manager port.

Expected behaviour

shutdown-manager should outlive envoy so the preStop drain handshake always completes before kubelet kills the proxy process.

Proposed fix

On Kubernetes 1.33+, promote shutdown-manager from spec.containers to spec.initContainers with restartPolicy: Always. This makes it a native sidecar, which kubelet guarantees will keep running until all non-sidecar containers (envoy) have fully exited, closing the race entirely.

The change belongs in the proxy pod-spec builder (roughly internal/infrastructure/kubernetes/proxy/resource.go) behind a version gate:

if k8sVersion >= 1.33 {
    shutdownMgr.RestartPolicy = ptr.To(corev1.ContainerRestartPolicyAlways)
    spec.InitContainers = append(spec.InitContainers, shutdownMgr)
    // remove from spec.Containers
} else {
    spec.Containers = append(spec.Containers, shutdownMgr)
}

Why not make the preStop hook retry-tolerant?

If shutdown-manager has already exited, drain has not completed and Envoy should not be killed. Masking a missing shutdown-manager in the hook would paper over the race rather than fix it. The correct invariant is that shutdown-manager must be alive for the entire duration of Envoy's preStop window.

Notes

  • This requires Kubernetes 1.33+ and should be a no-op on older clusters
  • It would be useful to export shutdownManagerContainerName (and envoyContainerName) as package-level constants — downstream consumers currently have to hardcode "shutdown-manager" as a bare string with no compile-time contract

Activity

  1. github-actions commented on Aug 12, 2026

    @github-actions
    Contributor

    This issue has been automatically marked as stale because it has not had activity in the last 30 days.

  2. ChrisJBurns commented on Aug 12, 2026

    @ChrisJBurns
    Author

    not stale

  3. github-actions commented on Sep 12, 2026

    @github-actions
    Contributor

    This issue has been automatically marked as stale because it has not had activity in the last 30 days.

  4. cilindrox commented on Oct 19, 2026

    @cilindrox
    Contributor

    Getting hit by this too - willing to open a PR but I'd like to hear from the maintainers first

  5. zirain commented on Oct 20, 2026

    @zirain
    Member

    If there's no way to detect the kubernetes version in controller, we should fallback.

  6. Ryan02I5 commented on Oct 30, 2026

    @Ryan02I5

    Another data point: this also reproduces with plain Envoy Gateway (not only Envoy AI Gateway) on Kubernetes 1.35.

    Description:
    During a proxy rollout, kubelet emitted FailedPreStopHook for the envoy container about 1s before the old pod was gone, and clients saw errors while the replacement started. This matches the race described above: shutdown-manager exited before the envoy preStop httpGet :19002/shutdown/ready finished, so the drain handshake did not complete.

    Repro steps:

    1. Single-replica proxy Deployment managed by Envoy Gateway (strategy: Recreate, hostPort 80/443 on one node), default shutdown-manager sidecar.
    2. kubectl -n envoy-gateway-system rollout restart deployment/<envoy-proxy-deployment>
    3. Watch pod events.

    Environment:

    • Envoy Gateway v1.9.1
    • Envoy distroless-v1.39.1
    • Kubernetes v1.35.9+k3s1 (K3s, arm64)

    Logs (Kubernetes events, UTC, from a single rollout):

    07:50:09 Unhealthy          Readiness probe failed: HTTP probe failed with statuscode: 503
    07:50:14 Unhealthy          Readiness probe failed: HTTP probe failed with statuscode: 503
    07:50:16 FailedPreStopHook  PreStopHook failed
    07:50:17 SuccessfulCreate   Created pod: <envoy-proxy-pod>
    07:50:18 Started            Container started
    07:50:19 Started            Container started
    

    External probing once per second saw 3 failed requests (07:50:17–07:50:19) before traffic recovered. With a single replica some gap is expected, but the failed preStop means the drain was cut short rather than completed.

    On @zirain's question about version detection: on this cluster the server reports v1.35.9+k3s1 through the standard discovery /version endpoint, so a semver check against 1.33 should work even for distributions with a +k3s build suffix. Happy to test a build or a PR here.

  7. mvanhorn commented on Oct 31, 2026

    @mvanhorn
    Contributor

    shutdown-manager was rendered as a regular container, so kubelet could terminate it concurrently with Envoy. The preStop hook depends on shutdown-manager remaining available while connections drain.

    Kubernetes 1.33+ API server versions now cause shutdown-manager to be rendered as an init container with restartPolicy Always, making it a native sidecar that outlives Envoy. Older, unavailable, or invalid versions retain the existing layout.

    #10469

  8. added
    area/infra-mgrIssues related to the provisioner used for provisioning the managed Envoy Proxy fleet.
    and removed
    help wantedExtra attention is needed
    on Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area/infra-mgrIssues related to the provisioner used for provisioning the managed Envoy Proxy fleet.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions