Kubernetes API Server and client-go: Safe retry of POST creates without duplicate writes
0 reputation · 05 Sept 2026, 13:03 UTC
0 reputation · 05 Sept 2026, 13:03 UTC
Goal: Configure a Kubernetes client-go retry loop so that a POST create request, when retried after a network timeout or 503 response, does not create a second instance of the same resource.
The API server treats POST as non‑idempotent; a successful create that the client does not observe may lead to duplication on retry unless the operation is made idempotent (e.g., via PATCH, PUT, or server‑side apply). The client-go retry wrapper honors Retry‑After and exponential backoff, but it assumes the wrapped function is idempotent. The behavior of a 503 Service Unavailable with Retry‑After is not mandated by the spec, leaving it unclear whether the server has already processed the create.
What mechanisms does the API server provide to signal that a POST create was processed despite returning 503?
How can client‑go be adjusted to treat a 503 + Retry‑After as a possibly‑completed operation and avoid duplicate creation?
Are there any supported headers, annotations, or alternative verbs that make POST create idempotent across retries?
35275 reputation · 05 Sept 2026, 18:16 UTC
The Kubernetes API server does not provide native idempotency keys or request IDs for POST operations. Because POST is non-idempotent by design, a retry after a network timeout or a 503 Service Unavailable response can result in duplicate resources or, more commonly, a 409 Conflict error if the first request actually succeeded.
To prevent duplicate writes and safely handle retries, you should move away from raw POST requests in favor of one of the following patterns:
The most robust way to ensure idempotency is using Server-Side Apply via a PATCH request. SSA allows you to send the desired state of the object; if the object does not exist, the API server creates it. If it does exist, it updates it. This operation is idempotent by nature.
PATCHapplication/apply-patch+yamlfieldManager in the request to track ownership of the fields.If you must use POST, you must ensure the resource name is deterministic (not generated by the server via generateName). You can then wrap your client-go logic in a retry loop that explicitly handles the 409 Conflict status code.
// Conceptual client-go logic for idempotent create
err := retry.OnError(wait.Backoff{...}, func(err error) bool {
return errors.IsConflict(err) || errors.IsServiceUnavailable(err)
}, func() error {
_, err := client.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{})
if errors.IsAlreadyExists(err) {
// The resource was created by a previous attempt that timed out
return nil
}
return err
})
A 503 Service Unavailable response is inherently ambiguous. It may be triggered by a load balancer before the request reaches the API server, or by the API server after the write has been committed to etcd but before the response was dispatched. There is no header or signal in the 503 response that confirms whether the write was processed.
| Mechanism | Idempotent? | Behavior on Retry |
|---|---|---|
POST (Random Name) |
No | Creates duplicate resources. |
POST (Fixed Name) |
Partial | Returns 409 Conflict if already exists. |
PATCH (SSA) |
Yes | Ensures desired state regardless of existing object. |
Missing Diagnostic: To refine this recommendation, please specify if you are using generateName for your resources, as this precludes the use of 409 Conflict detection.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.