Until recently, I did databases in a controversial manner - a MySQL pod in a deployment (not even a statefulset) with a PVC. It is simple. And it works. But it's not the Cloud Native Way.
So, as I was installing Immich, which specifically asked for a postgres database, I finally gave CloudNativePG a try.
What is CNPG
CloudNative PostGres got abbreviated to CloudNativePG. And even that is too long, so it's CNPG for this blogpost. If you want the official explanation of CNPG, they have a website, Github, and blog. This article may be of greatest interest to you.
Basically, CNPG lets you deploy highly available postgres clusters in a cloud native way, focused on Kubernetes. And while you have bigger options like Vitess, which are overly complicated, CNPG makes your life easy and I regret not trying it sooner. Primarily, CNPG is made of an operator called cnpg-controller-manager
Installing CNPG
The operator is installed by:
kubectl apply -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-1.25.1.yaml
And that's all.
Using CNPG
In Kubernetes style, you declare Postgres clusters with more YAML. Here's an example from the CNPG website:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: this-was-easy
spec:
instances: 3
storage:
size: 1Gi
This creates 3 servers, one as primary, and 2 as read replicas, each with 1GB of storage. You k apply
the yaml and wait a minute for CNPG to do some magic and then you have a resilient, highly available postgres cluster.
Oh and I was curious how hard it is to expand the storage on a pre-existing postgres cluster. As long as the storageclass (longhorn in my case) allows volume expansion, you just change the spec.storage.size value and apply. I didn't even notice the expansion. - Future Alex
What does it look like?

And I can delete the primary and watch CNPG failover the primary to another of the main-N pods:

To access your cluster, cnpg also makes 3 services for you, and the R, RO, RW are fairly self-explanatory :)

Going Further
Here's the configuration I used for the Immich Postgres cluster, so you can tailor-make a cluster to fit your needs:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: immich-postgres
spec:
imageName: ghcr.io/tensorchord/cloudnative-pgvecto.rs:16.5-v0.3.0
instances: 1
postgresql:
shared_preload_libraries:
- "vectors.so"
managed:
roles:
- name: immich
superuser: true
login: true
bootstrap:
initdb:
database: immich
owner: immich
secret:
name: immich-postgres-user
postInitSQL:
- CREATE EXTENSION IF NOT EXISTS "vectors";
- CREATE EXTENSION IF NOT EXISTS "cube" CASCADE;
- CREATE EXTENSION IF NOT EXISTS "earthdistance" CASCADE;
storage:
size: 5Gi