Setting Up Streaming Standby for PostgreSQL Cluster
Table of Contents
Introduction
What is a Streaming Standby PostgreSQL Cluster?
A streaming standby PostgreSQL cluster is a high-availability configuration where a secondary (standby) database server continuously receives real-time updates from a primary database server. This setup provides several key benefits:
Disaster Recovery : If the primary server fails, the standby can quickly take over
Read Scaling : Standby servers can be used for read-heavy operations
Minimal Data Loss : Continuous streaming replication ensures near-real-time data synchronization
Understanding the Components
Primary Cluster : The main database server handling write operations
Standby Cluster : A replica server that receives continuous updates from the primary
TLS Certificates : Ensure secure, encrypted communication between servers
Kubernetes : Container orchestration platform managing the database clusters
Prerequisites
Before beginning, ensure you have:
A running PostgreSQL primary cluster
Access to a Kubernetes cluster
Required tools:
kubectl command-line tool
openssl for certificate generation
Basic understanding of Kubernetes and PostgreSQL concepts
Step 1: Prepare TLS Certificates
Why TLS Certificates?
TLS (Transport Layer Security) certificates ensure:
Encrypted communication between primary and standby clusters
Authentication of servers
Protection against man-in-the-middle attacks
Detailed Certificate Generation Process
Create a Directory for Keys
Create a Certificate Authority (CA) Certificate
1
2
openssl genrsa -out ca.key 4096
openssl req -new -x509 -key ca.key -out ca.crt -days 365 -subj "/CN=mydatabase-ca"
Generates a root certificate authority key and certificate
Valid for 365 days
Used to sign other certificates
Create a Server Certificate for Primary Cluster
1
2
3
openssl genrsa -out mydatabase-server.key 4096
openssl req -new -key mydatabase-server.key -out mydatabase-server.csr -subj "/CN=mydatabase-primary"
openssl x509 -req -in mydatabase-server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out mydatabase-server.crt -days 365
Generates a unique server certificate for the primary cluster
Signed by the CA certificate for added security
Create a Replication Certificate
1
2
3
openssl genrsa -out mydatabase-repl.key 4096
openssl req -new -key mydatabase-repl.key -out mydatabase-repl.csr -subj "/CN=_crunchyrepl"
openssl x509 -req -in mydatabase-repl.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out mydatabase-repl.crt -days 365
Specifically for secure replication communication
Also signed by the CA certificate
Copy Keys to Standby Cluster
Use scp or rsync to transfer the keys directory
Ensure secure transmission of sensitive certificate files
Step 2: Create Kubernetes Secrets
What are Kubernetes Secrets?
Securely store sensitive information like certificates
Prevent hardcoding credentials in deployment files
Easily managed and rotated
Create Primary Cluster TLS Secret
1
2
3
4
kubectl create secret generic -n db-ns mydatabase-cluster.tls \
--from-file = ca.crt= ca.crt \
--from-file = tls.key= mydatabase-server.key \
--from-file = tls.crt= mydatabase-server.crt
Create Replication TLS Secret
1
2
3
4
kubectl create secret generic -n db-ns mydatabase-replication.tls \
--from-file = ca.crt= ca.crt \
--from-file = tls.key= mydatabase-repl.key \
--from-file = tls.crt= mydatabase-repl.crt
Configuring the Primary PostgreSQL Cluster
Add TLS secret references to your PostgreSQL cluster configuration:
1
2
3
4
5
spec :
customTLSSecret :
name : mydatabase-cluster.tls
customReplicationTLSSecret :
name : mydatabase-replication.tls
Here is the full yaml file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
---
apiVersion : postgres-operator.crunchydata.com/v1beta1
kind : PostgresCluster
metadata :
name : mydatabase
namespace : db-ns
labels :
pg-cluster : mydatabase
pgo-version : 5.6.7
spec :
#image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-15.7-1
#image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-13.9-1
image : registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-13.8-1
postgresVersion : 13
instances :
- name : mydatabase
replicas : 1
dataVolumeClaimSpec :
accessModes : [ " ReadWriteOnce" ]
resources :
requests :
storage : 5Gi
storageClassName : rook-ceph-block
resources :
requests :
cpu : " 2"
memory : " 4Gi"
limits :
cpu : " 2"
memory : " 4Gi"
backups :
pgbackrest :
# image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.51-1
image : registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.40-1
repos :
- name : repo1
volume :
volumeClaimSpec :
accessModes : [ " ReadWriteOnce" ]
resources :
requests :
storage : 5Gi
storageClassName : rook-ceph-block
monitoring :
pgmonitor :
exporter :
image : registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-15.7-1
users :
- name : mydatabaseuser
databases : [ mydb ]
customTLSSecret :
name : mydatabase-cluster.tls
customReplicationTLSSecret :
name : mydatabase-replication.tls
patroni :
dynamicConfiguration :
postgresql :
parameters :
max_connections : " 600"
shared_buffers : " 4GB"
effective_cache_size : " 6GB"
maintenance_work_mem : " 2GB"
checkpoint_completion_target : " 0.9"
wal_buffers : " 16MB"
default_statistics_target : " 100"
random_page_cost : " 1.1"
effective_io_concurrency : " 200"
min_wal_size : " 1GB"
max_wal_size : " 4GB"
max_worker_processes : " 8"
max_parallel_workers_per_gather : " 4"
max_parallel_workers : " 8"
max_parallel_maintenance_workers : " 4"
wal_keep_size : " 2048MB"
max_standby_archive_delay : " -1"
hot_standby : " on"
Step 4: Expose Primary Cluster via NodePort
Why Use NodePort?
Allows external access to the PostgreSQL service
Assigns a high-numbered port accessible from outside the cluster
1
2
3
kubectl expose svc mydatabase-ha --port = 5432 --target-port = 5432 \
--name = mydatabase-ha-nodeport --type = NodePort \
--selector = postgres-operator.crunchydata.com/patroni= mydatabase-ha -n db-ns
Verify the Service
1
kubectl get svc mydatabase-ha-nodeport -n db-ns
Standby Cluster Configuration
Key configuration elements for the standby cluster:
1
2
3
4
5
6
7
8
9
spec :
customTLSSecret :
name : mydatabase-cluster.tls
customReplicationTLSSecret :
name : mydatabase-replication.tls
standby :
enabled : true
host : <REDACTED_IP> # Primary cluster's IP
port : 5432
Here is the full yaml file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
---
apiVersion : postgres-operator.crunchydata.com/v1beta1
kind : PostgresCluster
metadata :
name : mydatabase-standby
namespace : db-ns
labels :
pg-cluster : mydatabase
pgo-version : 5.6.7
spec :
image : registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-13.8-1
postgresVersion : 13
instances :
- name : mydatabase-standby
replicas : 1
dataVolumeClaimSpec :
accessModes : [ " ReadWriteOnce" ]
resources :
requests :
storage : 5Gi
storageClassName : rook-ceph-block
resources :
requests :
cpu : " 2"
memory : " 4Gi"
limits :
cpu : " 2"
memory : " 4Gi"
backups :
pgbackrest :
image : registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.40-1
repos :
- name : repo1
volume :
volumeClaimSpec :
accessModes : [ " ReadWriteOnce" ]
resources :
requests :
storage : 5Gi
storageClassName : rook-ceph-block
monitoring :
pgmonitor :
exporter :
image : registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-15.7-1
customTLSSecret :
name : mydatabase-cluster.tls
customReplicationTLSSecret :
name : mydatabase-replication.tls
standby :
enabled : true
host : <A node IP>
port : <Exposed NodePort>
users :
- name : mydatabaseuser
databases : [ mydb ]
patroni :
dynamicConfiguration :
postgresql :
parameters :
max_connections : " 600"
shared_buffers : " 4GB"
effective_cache_size : " 6GB"
maintenance_work_mem : " 2GB"
checkpoint_completion_target : " 0.9"
wal_buffers : " 16MB"
default_statistics_target : " 100"
random_page_cost : " 1.1"
effective_io_concurrency : " 200"
min_wal_size : " 1GB"
max_wal_size : " 4GB"
max_worker_processes : " 8"
max_parallel_workers_per_gather : " 4"
max_parallel_workers : " 8"
max_parallel_maintenance_workers : " 4"
wal_keep_size : " 2048MB"
max_standby_archive_delay : " -1"
hot_standby : " on"
Step 6: Deploy and Verify the Standby Cluster
Deployment
1
kubectl apply -f mydatabase-standby.yaml
Verification Steps
Check Pod Status
1
kubectl get pods -n db-ns
Review Replication Logs
1
kubectl logs <standby-pod-name> -n db-ns
Troubleshooting
Common Issues and Solutions
Connection Problems
Check firewall rules
Verify network connectivity
Confirm correct IP and port configurations
TLS/Authentication Errors
Verify certificate generation
Check secret creation
Ensure matching CN (Common Name) in certificates
Replication Lag
Monitor replication status
Check network bandwidth
Review PostgreSQL configuration parameters
Conclusion
By following these steps, you’ve created a robust, secure streaming standby PostgreSQL cluster. This configuration provides high availability, disaster recovery, and scalability for your database infrastructure.
Next Steps
Implement regular backup strategies
Set up monitoring and alerting
Practice failover scenarios
Continuously review and optimize configuration
Note : Always test in a staging environment before implementing in production.
Previous
Setting Up RSync in PGO Crunchy Database to Copy pgBackRest Backup Files to a New replica_ABC and install PostgreSQL, pgBackRest and Restore backups on that replica_ABC
Next
ETL Server Setup to transfer data from Production to a Example. Data Warehouse Guide: Apache Airflow & PipelineWise