Skip to content

Commit 3a03cce

Browse files
committed
feat: startup probes
Signed-off-by: Gabriele Bartolini <[email protected]>
1 parent 776509b commit 3a03cce

File tree

8 files changed

+352
-0
lines changed

8 files changed

+352
-0
lines changed
199 KB
Loading
98.1 KB
Loading
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
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+
![A simplified view of the startup probe implemented by CloudNativePG](images/startup-probe.png)
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+
101 KB
Loading
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
apiVersion: postgresql.cnpg.io/v1
2+
kind: Cluster
3+
metadata:
4+
name: freddie
5+
spec:
6+
instances: 1
7+
8+
storage:
9+
size: 1Gi
10+
11+
probes:
12+
startup:
13+
periodSeconds: 5
14+
timeoutSeconds: 3
15+
failureThreshold: 120
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: postgresql.cnpg.io/v1
2+
kind: Cluster
3+
metadata:
4+
name: freddie
5+
spec:
6+
instances: 1
7+
8+
storage:
9+
size: 1Gi
10+
11+
probes:
12+
startup:
13+
type: query
14+
periodSeconds: 5
15+
timeoutSeconds: 3
16+
failureThreshold: 120
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: postgresql.cnpg.io/v1
2+
kind: Cluster
3+
metadata:
4+
name: freddie
5+
spec:
6+
instances: 3
7+
8+
storage:
9+
size: 1Gi
10+
11+
probes:
12+
startup:
13+
type: streaming
14+
maximumLag: 32Mi
15+
periodSeconds: 5
16+
timeoutSeconds: 3
17+
failureThreshold: 120
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: postgresql.cnpg.io/v1
2+
kind: Cluster
3+
metadata:
4+
name: freddie
5+
spec:
6+
instances: 1
7+
8+
storage:
9+
size: 1Gi
10+
11+
startDelay: 600

0 commit comments

Comments
 (0)