|
| 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 | + |
0 commit comments