|
| 1 | +--- |
| 2 | +title: "CNPG Recipe 19 – Finer Control Over Postgres Startup with Probes" |
| 3 | +date: 2025-06-17T12:17:55+02:00 |
| 4 | +description: "how CloudNativePG leverages Kubernetes startup probes to give users more reliable and configurable control over PostgreSQL instance initialisation in high-availability clusters" |
| 5 | +tags: ["postgresql", "postgres", "kubernetes", "k8s", "cloudnativepg", "cnpg", "postgresql", "postgres", "dok", "data on kubernetes", "probes", "cncf", "startup", "pg_isready"] |
| 6 | +cover: cover.jpg |
| 7 | +thumb: thumb.jpg |
| 8 | +draft: false |
| 9 | +--- |
| 10 | + |
| 11 | +_CloudNativePG 1.26 introduces enhanced support for Kubernetes startup probes, |
| 12 | +giving users finer control over how and when PostgreSQL instances are marked as |
| 13 | +"started." This article explores the new capabilities, including both basic and |
| 14 | +advanced configuration modes, and explains the different probe strategies—such |
| 15 | +as `pg_isready`, SQL `query`, and `streaming` for replicas. It provides |
| 16 | +practical guidance for improving the reliability of high-availability Postgres |
| 17 | +clusters by aligning startup conditions with actual database readiness._ |
| 18 | + |
| 19 | +<!--more--> |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +CloudNativePG 1.26 introduces an [enhanced implementation](https://github.com/cloudnative-pg/cloudnative-pg/pull/6623) |
| 24 | +of Kubernetes startup and readiness probes for each Postgres pod in a |
| 25 | +high-availability cluster—comprising a primary instance and any number of |
| 26 | +optional replicas (also known as standby instances). This improvement gives |
| 27 | +Kubernetes more accurate insight into when a Postgres instance should be |
| 28 | +considered *started* and, more importantly, *ready* to accept connections from |
| 29 | +applications or to be promoted following a failover. |
| 30 | + |
| 31 | +In this article, I’ll focus on the new **startup probe**, while deferring |
| 32 | +discussion of the updated readiness probe to a future post. |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +## Understanding Startup Probes |
| 37 | + |
| 38 | +[Startup probes](https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/#startup-probe), |
| 39 | +introduced in Kubernetes 1.20, provide better control over workloads that take |
| 40 | +time to initialise—delaying the execution of liveness and readiness probes |
| 41 | +until the application is truly up. |
| 42 | +If a startup probe fails, Kubernetes will terminate the container and attempt |
| 43 | +to restart it. |
| 44 | + |
| 45 | +Before startup probes, the typical workaround was to configure an initial delay |
| 46 | +in the readiness probe—an imprecise and often unreliable solution. |
| 47 | + |
| 48 | +Startup probes are as configurable as liveness and readiness probes, using the |
| 49 | +same key parameters: |
| 50 | + |
| 51 | +- `failureThreshold` – The number of consecutive failures allowed before the |
| 52 | + container is considered to have failed startup and is restarted. |
| 53 | +- `periodSeconds` – How often (in seconds) to perform the probe. |
| 54 | +- `successThreshold` – The number of consecutive successes required for the |
| 55 | + probe to be considered successful (typically set to 1). |
| 56 | +- `timeoutSeconds` – The number of seconds after which the probe times out if |
| 57 | + no response is received. |
| 58 | + |
| 59 | +These parameters allow for fine-tuned behaviour based on the expected startup |
| 60 | +characteristics of your application. |
| 61 | + |
| 62 | +As with other probes, the outcome (success or failure) is determined by |
| 63 | +invoking a specific endpoint or command defined in the container's |
| 64 | +configuration. |
| 65 | + |
| 66 | +For more details, refer to the official [Kubernetes documentation on |
| 67 | +probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes). |
| 68 | + |
| 69 | +Rather than reinventing the wheel with custom health checks at the PostgreSQL |
| 70 | +layer, CloudNativePG—staying true to its cloud-native principles—builds on |
| 71 | +Kubernetes’ native probe framework to manage the health of individual Postgres |
| 72 | +instances and the overall cluster. This article, along with the ones that |
| 73 | +follow, aims to explain how these probes work and how to make the most of them |
| 74 | +in your deployments. |
| 75 | + |
| 76 | +--- |
| 77 | + |
| 78 | +## Why Startup Probes Matter for Postgres |
| 79 | + |
| 80 | +Postgres is a prime example of a workload that can require significant time to |
| 81 | +start—especially in high-availability setups. Whether it's a primary or a |
| 82 | +replica, startup time can vary depending on factors such as crash recovery |
| 83 | +or recovery from a restart point, or replication lag to reach a consistent |
| 84 | +state. Replicas in particular may take longer to become ready, especially when |
| 85 | +replaying WAL segments after a restart. |
| 86 | + |
| 87 | +In the early days of EDB’s Cloud Native Postgres operator (the proprietary |
| 88 | +predecessor of CloudNativePG), this behaviour often led to problems: replicas |
| 89 | +would fail to become ready within the configured window and be prematurely |
| 90 | +restarted by Kubernetes. |
| 91 | + |
| 92 | +With startup probes, CloudNativePG can now give Kubernetes a clearer, more |
| 93 | +accurate signal about a pod's initialisation state—reducing unnecessary |
| 94 | +restarts and improving cluster stability. |
| 95 | + |
| 96 | +## How CloudNativePG Implements Startup Probes |
| 97 | + |
| 98 | +CloudNativePG offers two modes for configuring startup probes, giving users the |
| 99 | +choice between simplicity and fine-grained control: |
| 100 | + |
| 101 | +- **Basic mode** – A simplified configuration that automatically sets key probe |
| 102 | + parameters based on the value of `.spec.startDelay`. |
| 103 | +- **Advanced mode** – A fully customisable setup using the |
| 104 | + `.spec.probes.startup` stanza. |
| 105 | + |
| 106 | +By default, CloudNativePG uses `pg_isready` to check whether a PostgreSQL |
| 107 | +instance has started successfully (more on this later). |
| 108 | + |
| 109 | +The following diagram provides a simplified flow of the startup probe |
| 110 | +lifecycle, illustrating how its parameters interact and how it fits into the |
| 111 | +broader context of liveness and readiness probes. |
| 112 | + |
| 113 | + |
| 114 | + |
| 115 | +## Basic Mode: Simplicity Through `startDelay` |
| 116 | + |
| 117 | +The default and recommended approach is to use the `startDelay` parameter |
| 118 | +defined in the `Cluster` specification. This value is expressed in seconds and |
| 119 | +is set to `3600` (1 hour) by default—ensuring sufficient time for even complex |
| 120 | +or delayed PostgreSQL startup scenarios, such as replica recovery. |
| 121 | + |
| 122 | +In this mode, CloudNativePG preconfigures the startup probe with the following |
| 123 | +values: |
| 124 | + |
| 125 | +- `periodSeconds`: 10 seconds – the probe runs every 10 seconds |
| 126 | +- `successThreshold`: 1 – only one successful check is required |
| 127 | +- `timeoutSeconds`: 5 seconds – the probe will wait up to 5 seconds for a response |
| 128 | + |
| 129 | +The `failureThreshold` is automatically calculated using the formula: |
| 130 | + |
| 131 | +``` |
| 132 | +failureThreshold = startDelay / periodSeconds |
| 133 | +``` |
| 134 | + |
| 135 | +With the default `startDelay` of 3600 seconds and `periodSeconds` of 10, this |
| 136 | +results in: |
| 137 | + |
| 138 | +``` |
| 139 | +failureThreshold = 3600 / 10 = 360 |
| 140 | +``` |
| 141 | + |
| 142 | +This means Kubernetes will wait up to an hour (360 failed probes at 10-second |
| 143 | +intervals) before declaring the container as failed and restarting it, in |
| 144 | +accordance with the pod’s restart policy (`Always` with CloudNativePG). |
| 145 | + |
| 146 | +The following example creates a PostgreSQL cluster with a `startDelay` value of |
| 147 | +**600 seconds** (10 minutes), using the basic startup probe configuration mode: |
| 148 | + |
| 149 | +```yaml |
| 150 | +{{< include "yaml/freddie.yaml" >}} |
| 151 | +``` |
| 152 | + |
| 153 | +Once the cluster has been created, you can inspect the `startupProbe` |
| 154 | +configuration applied to the container in the primary pod (`freddie-1`) with |
| 155 | +the following command: |
| 156 | + |
| 157 | +```sh |
| 158 | +kubectl get pod freddie-1 \ |
| 159 | + -o jsonpath='{.spec.containers[*].startupProbe}' | jq |
| 160 | +``` |
| 161 | + |
| 162 | +The output should be similar to: |
| 163 | + |
| 164 | +```json |
| 165 | +{ |
| 166 | + "failureThreshold": 60, |
| 167 | + "httpGet": { |
| 168 | + "path": "/startupz", |
| 169 | + "port": 8000, |
| 170 | + "scheme": "HTTPS" |
| 171 | + }, |
| 172 | + "periodSeconds": 10, |
| 173 | + "successThreshold": 1, |
| 174 | + "timeoutSeconds": 5 |
| 175 | +} |
| 176 | +``` |
| 177 | + |
| 178 | +While this approach is sufficient for most use cases—providing a reliable and |
| 179 | +consistent startup window with minimal configuration—**the remainder of this |
| 180 | +article focuses on the advanced method for those who require greater control |
| 181 | +and customisation**. |
| 182 | + |
| 183 | +## Advanced Mode: Full Probe Customisation |
| 184 | + |
| 185 | +For use cases that require more granular control, CloudNativePG provides the |
| 186 | +`.spec.probes.startup` stanza. This allows you to explicitly define all the |
| 187 | +startup probe parameters introduced earlier in this article. |
| 188 | + |
| 189 | +The following configuration instructs Kubernetes to: |
| 190 | + |
| 191 | +- Probe the container every 5 seconds (`periodSeconds`) |
| 192 | +- Allow up to 120 consecutive failures (`failureThreshold`)—equivalent to 10 |
| 193 | + minutes (600 seconds)—before considering the startup probe failed |
| 194 | +- Restart the container if it hasn’t successfully started within that time |
| 195 | + |
| 196 | +```yaml |
| 197 | +{{< include "yaml/freddie-custom.yaml" >}} |
| 198 | +``` |
| 199 | + |
| 200 | +This approach is ideal when the default settings are not suitable for your |
| 201 | +workload, in particular the `periodSeconds` option. |
| 202 | + |
| 203 | +As you may have noticed, these settings apply uniformly to all PostgreSQL |
| 204 | +instance pods—whether they are primaries or standbys. Now, let’s explore what |
| 205 | +additional aspects you can customise in your setup to fine-tune behaviour even |
| 206 | +further. |
| 207 | + |
| 208 | +## Probe Strategies |
| 209 | + |
| 210 | +By default, CloudNativePG uses PostgreSQL’s |
| 211 | +[`pg_isready`](https://www.postgresql.org/docs/current/app-pg-isready.html) |
| 212 | +utility to determine whether a PostgreSQL instance—primary or replica—is |
| 213 | +accepting connections. If `pg_isready` returns `0`, the probe is considered |
| 214 | +successful; any other return code results in a failure. |
| 215 | + |
| 216 | +The strategy can be changed through the `.spec.probes.startup.type` parameter. |
| 217 | + |
| 218 | +### Type: `pg_isready` (default) |
| 219 | + |
| 220 | +This is the default behaviour for both primary and replica pods and requires no |
| 221 | +additional configuration. |
| 222 | + |
| 223 | +### Type: `query` |
| 224 | + |
| 225 | +Alternatively, you can configure the probe to execute a basic ping SQL query on |
| 226 | +the local `postgres` database (see [`query.go`](https://github.com/cloudnative-pg/cloudnative-pg/blob/main/pkg/management/postgres/webserver/probes/query.go)). |
| 227 | + |
| 228 | +To enable this mode, set `.spec.probes.startup.type` to `query`, as shown in |
| 229 | +the following example: |
| 230 | + |
| 231 | +```yaml |
| 232 | +{{< include "yaml/freddie-query.yaml" >}} |
| 233 | +``` |
| 234 | + |
| 235 | +This approach works for both primaries and replicas and may be preferred in |
| 236 | +scenarios where a successful connection isn't enough—you also want to ensure |
| 237 | +the instance can respond to a minimal query. |
| 238 | + |
| 239 | +### Type: `streaming` (replica-only) |
| 240 | + |
| 241 | +CloudNativePG also provides a third probe type, `streaming`, specifically |
| 242 | +designed for replicas. |
| 243 | + |
| 244 | +```yaml |
| 245 | +{{< include "yaml/freddie-streaming.yaml" >}} |
| 246 | +``` |
| 247 | + |
| 248 | +In this example, a replica is considered started only when it is actively |
| 249 | +streaming from the primary and its replication lag falls below a defined |
| 250 | +threshold. This threshold is controlled via the `maximumLag` parameter, which |
| 251 | +specifies the acceptable lag in bytes (in this case it is set to **32MB**—the |
| 252 | +equivalent of two WAL segments). |
| 253 | + |
| 254 | +If `maximumLag` is not set, the default behaviour, the replica is marked as |
| 255 | +started as soon as it begins streaming from the primary, regardless of its lag. |
| 256 | + |
| 257 | +I strongly recommend considering this probe strategy for production-grade high |
| 258 | +availability setups. It ensures that a replica is marked as started not merely |
| 259 | +when it accepts connections, but when it is meaningfully synchronised with the |
| 260 | +primary—either already attached and streaming, or doing so with minimal lag (as |
| 261 | +you require). This leads to a more reliable failover experience and better |
| 262 | +application consistency. |
| 263 | + |
| 264 | +## Key Takeaways |
| 265 | + |
| 266 | +Startup probes in Kubernetes are essential for ensuring that PostgreSQL |
| 267 | +instances—especially replicas—aren’t prematurely marked as healthy. With |
| 268 | +CloudNativePG 1.26, you now have the flexibility to define exactly what |
| 269 | +“started” means for your workloads, whether that’s simply accepting |
| 270 | +connections, executing a basic query, or actively streaming with minimal lag. |
| 271 | + |
| 272 | +For most production environments, the new `streaming` probe strategy for |
| 273 | +replicas is worth adopting. It allows you to delay declaring a replica as |
| 274 | +started until it’s actually useful for failover, leading to more robust and |
| 275 | +predictable high-availability behaviour. |
| 276 | + |
| 277 | +In short: startup probes are no longer a checkbox—they’re a tuning instrument. |
| 278 | +In the next article, I’ll walk you through readiness probes and how they |
| 279 | +complement startup probes to ensure your PostgreSQL workloads are both ready |
| 280 | +and resilient. |
| 281 | + |
| 282 | +--- |
| 283 | + |
| 284 | +Stay tuned for the upcoming recipes! For the latest updates, consider |
| 285 | +subscribing to my [LinkedIn](https://www.linkedin.com/in/gbartolini/) and |
| 286 | +[Twitter](https://twitter.com/_GBartolini_) channels. |
| 287 | + |
| 288 | +If you found this article informative, feel free to share it within your |
| 289 | +network on social media using the provided links below. Your support is |
| 290 | +immensely appreciated! |
| 291 | + |
| 292 | +_Cover Picture: [“Baby Sri Lankan Elephant“](https://commons.wikimedia.org/wiki/File:Baby_Sri_Lankan_elephant_%28Elephas_maximus_maximus%29.jpg)._ |
| 293 | + |
0 commit comments