Skip to content

Commit 108891b

Browse files
committed
docs: extension_control_path and image volumes
Signed-off-by: Gabriele Bartolini <[email protected]>
1 parent e1c00b5 commit 108891b

File tree

3 files changed

+298
-0
lines changed

3 files changed

+298
-0
lines changed
511 KB
Loading
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
---
2+
title: "The Immutable Future of PostgreSQL Extensions in Kubernetes with CloudNativePG"
3+
date: 2025-03-03T11:38:14+01:00
4+
description: "How managing Postgres extensions in Kubernetes will change"
5+
tags: ["postgresql", "postgres", "kubernetes", "k8s", "cloudnativepg", "cnpg", "postgresql", "postgres", "dok", "data on kubernetes", "extensions", "container images", "sbom", "pgvector", "imagevolume", "extension_control_path"]
6+
cover: cover.jpg
7+
thumb: thumb.jpg
8+
draft: false
9+
---
10+
11+
_Managing extensions is one of the biggest challenges in running PostgreSQL on
12+
Kubernetes. In this article, I explain why I believe [CloudNativePG](https://cloudnative-pg.io/)
13+
—now a CNCF Sandbox project—is on the verge of a breakthrough.
14+
Two important new features for both PostgreSQL and Kubernetes—the
15+
`extension_control_path` option and image volumes—will guarantee immutability
16+
to extension container images._
17+
18+
<!--more-->
19+
20+
---
21+
22+
A core principle of Kubernetes is the immutability of container images. With
23+
[CloudNativePG, we embraced this approach from day one](https://www.enterprisedb.com/blog/why-edb-chose-immutable-application-containers),
24+
firmly adhering to best practices such as immutable infrastructure,
25+
Infrastructure as Code (IaC), and shifting security left.
26+
27+
This immutability means that PostgreSQL container images must remain read-only
28+
to ensure security and reliable change management. However, this approach
29+
clashes with one of PostgreSQL’s greatest strengths: **extensibility**.
30+
Extensions have indeed played a crucial role in PostgreSQL’s rise as the most
31+
popular and versatile database engine.
32+
33+
While core extensions like `pg_stat_statements` and `postgres_fdw` are
34+
included in every [PostgreSQL operand image](https://github.com/cloudnative-pg/postgres-containers),
35+
third-party extensions—such as PostGIS, TimescaleDB, and pgvector—are separate
36+
projects and are not bundled with PostgreSQL by default.
37+
38+
In traditional virtual machine (VM) deployments, extensions can be installed at
39+
runtime using the package manager of the Linux distribution. However, with
40+
CloudNativePG, modifying the container image is not possible; you must rely on
41+
declarative configuration and pre-existing images. Additionally, deployment is
42+
only part of the challenge: updates as part of Day 2 operations are usually
43+
handled case-by-case, impacting scalability and maintainability of
44+
infrastructures and applications sensibly.
45+
46+
## The Current Approach: Pre-built Operand Images
47+
48+
Until now, the only way to run third-party extensions with CloudNativePG (and
49+
most PostgreSQL operators) without breaking immutability has been to embed them
50+
in the operand image. However, this approach has significant downsides:
51+
52+
- **Increased image size and footprint** – The more extensions included, the
53+
larger the image.
54+
- **Rigid extension selection** – Users are often left frustrated because:
55+
- Their required extension isn’t included.
56+
- They want a lighter image without unnecessary extensions.
57+
- They don’t want specific libraries due to CVEs or security concerns.
58+
- **Operational complexity** – Users must maintain custom images, as outlined
59+
in our guide on [creating container images](https://cloudnative-pg.io/blog/creating-container-images/) and
60+
following our [image requirements](https://cloudnative-pg.io/documentation/current/container_images/).
61+
62+
Not everyone has the resources or expertise to do this. Compliance is another
63+
critical factor.
64+
65+
We have long been exploring ways to enable dynamic loading of extensions in
66+
CloudNativePG. I recall insightful discussions with **Tembo**, an early
67+
[adopter of CloudNativePG](https://github.com/cloudnative-pg/cloudnative-pg/blob/main/ADOPTERS.md),
68+
about reserving a volume for dynamically installing third-party extensions.
69+
However, we decided not to pursue that idea, as it violated our immutability
70+
principle (Tembo later pursued their [Trunk](https://github.com/tembo-io/trunk)
71+
project).
72+
73+
To achieve dynamic extension loading **without** breaking immutability, we
74+
needed improvements in **both PostgreSQL and Kubernetes**.
75+
76+
Fortunately, these improvements are finally happening.
77+
78+
## What’s Missing in PostgreSQL?
79+
80+
Currently, PostgreSQL requires extensions to be installed in a system
81+
directory, such as `/usr/share/postgresql/17/extension`, where it looks for
82+
[extension control files](https://www.postgresql.org/docs/current/extend-extensions.html).
83+
84+
In traditional setups, Linux package managers (Debian/RPM) install extension
85+
packages, placing control files in this directory. While this works in a
86+
mutable environment, it is incompatible with an immutable setup like
87+
CloudNativePG, where the system directory is **read-only** for security.
88+
89+
To overcome this limitation, PostgreSQL needs to support alternative extension
90+
locations. This is precisely what the patch developed by my [EDB](https://enterprisedb.com) colleagues
91+
Peter Eisentraut, Andrew Dunstan, and Matheus Alcantara and
92+
[proposed for PostgreSQL 18 ](https://commitfest.postgresql.org/patch/4913/)
93+
aims to do. The patch is based on an initial work by Christoph Berg.
94+
It is part of a broader [discussion started by David Wheeler](https://www.postgresql.org/message-id/flat/[email protected])
95+
(Tembo), which I contributed to to align it with Kubernetes' immutability
96+
needs. We further refined this direction during in-person conversations at
97+
PostgreSQL Europe in Athens in October 2024
98+
([David wrote a great recap here](https://justatheory.com/2024/11/rfc-extension-packaging-lookup/)).
99+
100+
### Introducing `extension_control_path`
101+
102+
The proposed patch introduces a new PostgreSQL configuration option (GUC),
103+
`extension_control_path`, which allows users to specify additional
104+
directories for extension control files. It is set to `$system` by default,
105+
but users can define multiple paths—just like other path-like configuration
106+
options in PostgreSQL.
107+
108+
Combined with `dynamic_library_path`, this feature enables PostgreSQL to
109+
locate control files and shared libraries from multiple directories, **breaking
110+
free from the single system-wide location constraint**.
111+
112+
I hope this patch is merged into PostgreSQL and becomes part of **PostgreSQL
113+
18**, ensuring that future versions fully support this approach.
114+
115+
## What’s Missing in Kubernetes?
116+
117+
Even with PostgreSQL supporting multiple extension paths, we still need a way
118+
to dynamically mount extensions into a running CloudNativePG cluster **without
119+
modifying the primary container image**. This is where Kubernetes is stepping
120+
up.
121+
122+
### Introducing `ImageVolume` resources
123+
124+
The [ImageVolume feature](https://kubernetes.io/blog/2024/08/16/kubernetes-1-31-image-volume-source/)
125+
was introduced as an **alpha feature in Kubernetes 1.31** and is expected to
126+
reach beta soon. Its goal is to be promoted to a beta release state in
127+
Kubernetes 1.33.
128+
129+
This feature allows us to mount a container image as a **read-only and
130+
immutable volume** inside a running pod. It will enable PostgreSQL extensions
131+
that are packaged as **independent OCI-compliant container images** to be
132+
mounted inside CloudNativePG clusters at a known directory (e.g.,
133+
`/extensions/<EXTENSION_NAME>`).
134+
135+
At this point, all we’ll need to do is set the ``extension_control_path`` and
136+
``dynamic_library_path`` options accordingly. An operator like CloudNativePG
137+
can automate this process, making it seamless for users. The same approach can
138+
be repeated multiple times, once per required extension.
139+
140+
Image extension images at that point must be compatible with the Postgres base
141+
image that the CloudNativePG has deployed (for example, the same distribution
142+
and architecture).
143+
144+
145+
## How We Tested These Features in CloudNativePG
146+
147+
Although we weren’t directly involved in developing these new PostgreSQL
148+
features, **some of us at CloudNativePG contributed to testing the
149+
corresponding patch**.
150+
151+
I want to give special thanks to **Niccolò Fei** for his outstanding work
152+
validating the patch developed by Peter, Andrew, and Matheus. This includes
153+
patches for the CloudNativePG operator and extension images.
154+
155+
Our testing focused on:
156+
157+
- **Streamlining the build process** for PostgreSQL operand images that
158+
incorporate patches from the PostgreSQL commit fest (for details, see my
159+
[previous article](https://www.gabrielebartolini.it/articles/2024/09/how-to-test-a-postgresql-commitfest-patch-in-kubernetes/)).
160+
- **Developing a pilot patch for CloudNativePG** to declaratively add
161+
PostgreSQL extensions via a container image. The operator mounts the image as
162+
an `ImageVolume`, automatically configuring `extension_control_path` and
163+
`dynamic_library_path`.
164+
See [CloudNativePG PR #6546](https://github.com/cloudnative-pg/cloudnative-pg/pull/6546).
165+
- **Creating self-contained extension images**, such as one for `pgvector`.
166+
167+
### Lightweight Extension Images
168+
169+
Focusing on the last point, Niccolò proposed a
170+
[Dockerfile for pgvector](https://github.com/EnterpriseDB/pgvector/blob/dev/5645/Dockerfile.cnpg)
171+
that produces a **minimal** image—containing only the `lib` and `share`
172+
directories—at just **1.6MB**.
173+
174+
To inspect its contents, use:
175+
176+
```sh
177+
dive ghcr.io/cloudnative-pg/pgvector-18-testing:latest
178+
```
179+
180+
### Deploying Extensions Declaratively
181+
182+
To illustrate the proposed solution, consider this example (note: the format is
183+
still in alpha and may change):
184+
185+
```yaml
186+
apiVersion: postgresql.cnpg.io/v1
187+
kind: Cluster
188+
metadata:
189+
name: postgresql-with-extensions
190+
spec:
191+
instances: 1
192+
# This points to the temporary build of the commit fest patch
193+
imageName: ghcr.io/cloudnative-pg/postgresql-trunk:18-cf-4913
194+
postgresql:
195+
extensions:
196+
- name: pgvector
197+
image:
198+
reference: ghcr.io/cloudnative-pg/pgvector-18-testing:latest
199+
storage:
200+
storageClass: standard
201+
size: 1Gi
202+
```
203+
204+
205+
The key addition is the `.spec.postgresql.extensions` section, where users can
206+
define extensions and their corresponding images (each an `ImageVolume`
207+
resource). This configuration can be updated dynamically.
208+
209+
### What Happens Under the Hood
210+
211+
When an extension image is specified:
212+
213+
1. The operator triggers a rolling update, starting with replicas.
214+
215+
2. Each referenced image is mounted as a **read-only volume** using Kubernetes'
216+
`ImageVolume` resource.
217+
218+
3. In this example, `ghcr.io/cloudnative-pg/pgvector-18-testing:latest` is
219+
mounted on `/extensions/pgvector`.
220+
221+
4. CloudNativePG updates `dynamic_library_path` and `extension_control_path` to
222+
include the `/extensions/pgvector/lib` and `/extensions/pgvector/share`
223+
directories, respectively.
224+
225+
Once deployed, you can verify the extension’s availability:
226+
227+
```sql
228+
postgres=# SELECT * FROM pg_available_extensions WHERE name = 'vector';
229+
-[ RECORD 1 ]-----+-----------------------------------------------------
230+
name | vector
231+
default_version | 0.8.0
232+
installed_version |
233+
comment | vector data type and ivfflat and hnsw access methods
234+
```
235+
236+
**Important:** This feature is not yet available out of the box. To use `ImageVolume`, you need:
237+
238+
- **Kubernetes 1.31+** with the [ImageVolume feature gate](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/)
239+
enabled.
240+
- **CRI-O** as the container runtime (*containerd support has been merged but
241+
is not yet available*).
242+
243+
We applied the same approach to **PostGIS during our pilot project**, creating
244+
a self-contained extension image. The method proved effective even for complex
245+
extensions.
246+
247+
## Conclusion
248+
249+
This is just the beginning. This initiative marks the first iteration toward a
250+
new approach to managing PostgreSQL extensions in Kubernetes—bridging the gap
251+
between extension developers and consumers. The most important takeaway is that
252+
we’re heading in the right direction, opening the door to new possibilities and
253+
allowing the best solutions to emerge through exploration.
254+
255+
While PostgreSQL’s `extension_control_path` and Kubernetes'
256+
`ImageVolume` feature are fundamental pieces, there’s another
257+
critical component: **the distribution of PostgreSQL extensions**.
258+
259+
It's time for PostgreSQL extension developers to embrace **OCI images as
260+
first-class artifacts**, alongside traditional RPM and Debian packages.
261+
Ideally, every extension should provide a self-contained image for mainstream
262+
Linux distributions. Whether these images are built from existing packages or
263+
directly from source (e.g., via `Makefile`) is a decision best left to those
264+
with deeper expertise in the packaging ecosystem.
265+
266+
With **PostgreSQL 18** supporting configurable extension paths and **Kubernetes
267+
1.33** introducing `ImageVolume`, CloudNativePG is entering a **new era of
268+
dynamic, immutable, and scalable extension management**. By packaging
269+
extensions as independent OCI-compliant images, we can finally **decouple
270+
PostgreSQL operand images from extensions**, keeping them minimal and flexible.
271+
This unlocks several key benefits:
272+
273+
- **Install third-party extensions dynamically**—no need to rebuild container
274+
images.
275+
- **Facilitate testing and validation of extensions**
276+
- **Simplify PostgreSQL extension upgrades** without affecting the core
277+
database image.
278+
- **Ensure strict immutability** while enhancing security, change management,
279+
scalability, and maintainability.
280+
281+
This is a game-changer for **CloudNativePG** and the broader
282+
**PostgreSQL-on-Kubernetes ecosystem**.
283+
284+
The future of PostgreSQL extensions in Kubernetes is **immutable, yet
285+
flexible—just as it should be**.
286+
287+
---
288+
289+
Stay tuned for the upcoming recipes! For the latest updates, consider
290+
subscribing to my [LinkedIn](https://www.linkedin.com/in/gbartolini/) and
291+
[Twitter](https://twitter.com/_GBartolini_) channels.
292+
293+
If you found this article informative, feel free to share it within your
294+
network on social media using the provided links below. Your support is
295+
immensely appreciated!
296+
297+
_Cover Picture: [“Sofyan Efendi Dipinggiran Sungai“](https://commons.wikimedia.org/wiki/File:Sofyan_Efendi_Dipinggiran_Sungai_IMG_3249.jpg)._
298+
197 KB
Loading

0 commit comments

Comments
 (0)