OLS-3192 Add preStop lifecycle hook to postgres pod to prevent crash-loop afte…#1726
OLS-3192 Add preStop lifecycle hook to postgres pod to prevent crash-loop afte…#1726sriroopar wants to merge 2 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughAdds PostgreSQL shutdown lifecycle wiring with a ChangesPostgres graceful shutdown
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
/retest |
1 similar comment
|
/retest |
9359380 to
f3f335d
Compare
f3f335d to
a93a653
Compare
sriroopar
left a comment
There was a problem hiding this comment.
Adversarial Review
The preStop hook approach is sound — pg_ctl stop -m fast -w is the right tool for clean Postgres shutdown in Kubernetes. Here are concerns to address:
CI Note
The ci/prow/bundle-e2e-4-21 failure is unrelated to this PR. It's a pre-existing flaky proxy test (proxy_test.go:162) — the Squid PVC isn't cleaned up between retries (squid-volume-claim already exists), causing all retry attempts to fail. This eventually hits the 2-hour Prow timeout. A /retest should pass.
| Command: []string{ | ||
| "/bin/sh", "-c", | ||
| fmt.Sprintf("pg_ctl stop -D %s -m fast -w -t %d", utils.PostgresUserDataDir, utils.PostgresShutdownTimeoutSeconds), | ||
| }, |
There was a problem hiding this comment.
Allocation for a pointer to constant: &[]int64{utils.PostgresTerminationGracePeriodSeconds}[0] allocates a slice just to get a *int64. This is a known Go pattern but it's fragile and non-obvious.
Consider a simple helper or inline variable:
terminationGracePeriod := utils.PostgresTerminationGracePeriodSeconds
// ...
TerminationGracePeriodSeconds: &terminationGracePeriod,Or use k8s.io/utils/ptr.To(utils.PostgresTerminationGracePeriodSeconds) which is already a transitive dependency.
| // PostgresShutdownTimeoutSeconds is the timeout for pg_ctl stop command. | ||
| // This must be less than PostgresTerminationGracePeriodSeconds to complete before SIGKILL. | ||
| PostgresShutdownTimeoutSeconds = 55 | ||
|
|
There was a problem hiding this comment.
Type inconsistency: PostgresTerminationGracePeriodSeconds is int64(60) but PostgresShutdownTimeoutSeconds is untyped 55 (defaults to int). These two constants have a mathematical relationship (grace period must exceed shutdown timeout), but the type mismatch means you can't directly compute the delta without a cast.
Consider making both int64, or at minimum adding a compile-time assertion:
PostgresShutdownTimeoutSeconds = int64(55)|
|
||
| // PostgresUserDataDir is the PostgreSQL data directory path (relative to PostgresDataVolumeMountPath). | ||
| // PostgreSQL's initdb creates this structure: PGDATA/data/userdata | ||
| PostgresUserDataDir = "/var/lib/pgsql/data/userdata" |
There was a problem hiding this comment.
Path duplication risk: PostgresUserDataDir = "/var/lib/pgsql/data/userdata" hardcodes a path whose prefix is already defined as PostgresDataVolumeMountPath = "/var/lib/pgsql". If someone changes the mount path, this constant won't track.
Consider deriving it:
PostgresUserDataDir = PostgresDataVolumeMountPath + "/data/userdata"Also — is data/userdata always the correct PGDATA subdirectory for the Red Hat postgres image? This path is an internal implementation detail of the container image's initdb. If the image ever changes its PGDATA layout, this will silently break (pg_ctl will report 'not a database directory' to stderr, but the preStop hook has no error propagation).
| @@ -234,10 +235,21 @@ func GeneratePostgresDeployment(r reconciler.Reconciler, ctx context.Context, cr | |||
| Value: strconv.Itoa(cr.Spec.OLSConfig.ConversationCache.Postgres.MaxConnections), | |||
| }, | |||
There was a problem hiding this comment.
Silent failure mode: If pg_ctl is not in $PATH inside the container (different base image, stripped image, etc.), or if the data directory doesn't exist yet (fresh pod that crashed before initdb), this preStop hook fails silently. Kubernetes will still send SIGTERM after the hook exits with a non-zero code, but you lose the clean-shutdown guarantee.
Consider adding a guard:
/bin/sh -c 'command -v pg_ctl >/dev/null 2>&1 && pg_ctl stop -D ... -m fast -w -t 55 || true'The || true ensures the hook doesn't block termination if pg_ctl isn't available.
| // AlertsAdapterComponentLabel is the app.kubernetes.io/component label value for alerts adapter resources | ||
| AlertsAdapterComponentLabel = "alerts-adapter" | ||
| // PostgresTerminationGracePeriodSeconds is the grace period for postgres pod termination. | ||
| // This must be greater than PostgresShutdownTimeoutSeconds to allow pg_ctl to complete. |
There was a problem hiding this comment.
5-second margin may be tight: The 5-second gap between PostgresShutdownTimeoutSeconds (55) and PostgresTerminationGracePeriodSeconds (60) needs to cover:
- Kubelet receives termination signal
- Kubelet invokes the preStop hook shell (
/bin/sh -c ...) - pg_ctl starts and begins shutdown
Steps 1-3 happen before the 55-second pg_ctl timer starts. If these steps take >5 seconds (which can happen under node pressure), pg_ctl will be SIGKILLed mid-shutdown — exactly the crash-loop scenario this PR aims to prevent.
Consider either increasing the grace period to 90s (with 55s pg_ctl timeout) or reducing the pg_ctl timeout to 45s.
| *a.Replicas != *b.Replicas || // check replicas | ||
| a.Template.Spec.ServiceAccountName != b.Template.Spec.ServiceAccountName { // check service account name | ||
| a.Template.Spec.ServiceAccountName != b.Template.Spec.ServiceAccountName || // check service account name | ||
| !apiequality.Semantic.DeepEqual(a.Template.Spec.TerminationGracePeriodSeconds, b.Template.Spec.TerminationGracePeriodSeconds) { // check termination grace period |
There was a problem hiding this comment.
Consistency note: The existing *a.Replicas != *b.Replicas on line 196 will panic if either Replicas pointer is nil. The new TerminationGracePeriodSeconds comparison correctly uses apiequality.Semantic.DeepEqual which is nil-safe. This is good — but worth noting the pre-existing nil-unsafety on Replicas as a follow-up.
…r unclean shutdown
- Increase termination grace period from 60s to 90s for adequate kubelet overhead
- Make PostgresShutdownTimeoutSeconds int64 for type consistency
- Derive PostgresUserDataDir from PostgresDataVolumeMountPath to avoid path duplication
- Add || true guard to preStop command to prevent hook failure blocking termination
- Replace &[]int64{...}[0] hack with local variable for pointer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
a93a653 to
b67cf1d
Compare
|
@sriroopar: The following test failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
…r unclean shutdown
Description
Type of change
Related Tickets & Documents
Checklist before requesting a review
Testing
Summary by CodeRabbit
Release Notes
Bug Fixes
Tests