Once a database is running, the operational work starts: adding memory, adding read capacity, or moving to a new version — ideally without an outage. With the CYBERTEC PG Operator (CPO), each of those is a single field change in a manifest, and the operator performs the rollout while the cluster stays available.
Changing resources and parameters
Resizing is done by editing the resource requests and limits:
|
1 2 3 4 |
kubectl -n cpo patch pg pg-cluster --type=merge -p '{ "spec": { "resources": { "requests": { "cpu": "500m", "memory": "1Gi" }, "limits": { "cpu": "1", "memory": "1Gi" } } } }' |
Rather than restarting the whole cluster, the operator works through the instances incrementally, and the progress is visible as it goes:
|
1 2 3 4 5 6 7 |
NAME READY STATUS ROLE pg-cluster-0 1/1 Running master pg-cluster-1 1/1 Terminating replica # replica updated FIRST pg-cluster-1 1/1 Running (none) pg-cluster-0 1/1 Terminating (none) # then a SWITCHOVER... pg-cluster-1 1/1 Running master # ...pg-cluster-1 is now leader pg-cluster-0 1/1 Running replica # old leader rejoins as replica |
The order is replicas first, then a switchover onto the updated node, then the former leader — so there is never a moment when the entire cluster is down. In testing, the new limits were confirmed applied ("memory":"1Gi") and the cluster remained healthy for the duration.
PostgreSQL parameters follow the same model: set the value and Patroni applies it across the cluster.
|
1 2 3 4 5 |
kubectl -n cpo patch pg pg-cluster --type=merge \ -p '{"spec":{"postgresql":{"parameters":{"max_connections":"200"}}}}' # a few seconds later: kubectl -n cpo exec pg-cluster-0 -- psql -U postgres -tAc 'show max_connections;' # 200 |
Adding and removing replicas
Read capacity is controlled by numberOfInstances:
|
1 |
kubectl -n cpo patch pg pg-cluster --type=merge -p {"spec":{"numberOfInstances":3}} |
A new node is cloned from the leader, then begins streaming — it has the data as soon as it joins:
|
1 2 3 |
| pg-cluster-0 | Leader | running | | pg-cluster-1 | Replica | streaming | | pg-cluster-2 | Replica | streaming | # the new one, zero lag |
|
1 2 |
kubectl -n cpo exec pg-cluster-2 -- psql -U postgres -tAc 'select count() from t;' # 3 |
Reducing the count (for example numberOfInstances: 2) drains the highest-numbered node cleanly. If that node is the leader, Patroni fails over to a surviving node first, so neither the primary nor data is lost. A test cycle of 2 → 3 → 2 left the row count at 3 throughout.
Minor version updates
A minor update means changing the image field. With data loaded into the cluster, the image is replaced:
|
1 2 3 4 5 |
kubectl -n cpo exec pg-cluster-0 -- psql -U postgres \ -c "create table before_update(note text); insert into before_update values ('hello from 17.5');" kubectl -n cpo patch pg pg-cluster --type=merge \ -p {"spec":{"dockerImage":"docker.io/cybertecpostgresql/cybertec-pg-container:rocky9-18.4-1"}} |
The pod restarts onto the new image — roughly 25 seconds in this run — and the data is intact:
|
1 2 3 4 5 |
kubectl -n cpo exec pg-cluster-0 -- psql -U postgres -tAc select version(); # PostgreSQL 18.X on x86_64-pc-linux-gnu, ... kubectl -n cpo exec pg-cluster-0 -- psql -U postgres -tAc select note from before_update; # hello from 17.5 |
The switchover that follows uses the same zero-downtime pattern as the resize. Major upgrades, such as 17 → 18, are a larger operation: the operator runs pg_upgrade in place.
Sizing up the model
All three operations come down to one move: change the desired state in the manifest and let the operator reconcile it with a rolling update. Practically, that turns routine database maintenance into kubectl patch commands, or better, pull requests against the manifests kept in Git — while keeping the cluster up remains the operator's responsibility rather than the DBA's.



