πŸ”₯Save up to $132K/month in CI costs!Try Freeβ†’
Skip to main content

PostgreSQL on Kubernetes - A Complete Guide to Deployment Methods

17 min read
Author: James Smith
Senior Kubernetes Engineer
Kubernetes is my thingβ€”I love building scalable systems and fine-tuning container workflows for better performance.

Introduction​

TL:DR: What is the Best Way to Deploy PostgreSQL on Kubernetes?

Depending on your use case, here is the best way to deploy PostgreSQL on Kubernetes:

  • Development/Test: Use StatefulSets for simplicity.
  • Small-Medium Production: The Bitnami Helm Chart can be used for managed setup.
  • Enterprise: Use CloudNativePG to provide High Availability and Advanced Features. - Fully Managed: Choose AWS RDS to select serverless PostgreSQL.

In the decade-plus journey of my DevOps engineer life, I have deployed PostgreSQL on the Kubernetes cluster with different methods. Each approach bears merits and challenges.

Here in this tutorial, I will walk you through the different strategies of deployment, right from the most basic approach through the more sophisticated solutions.

Steps we will cover:

Interactive PostgreSQL Deployment Guide​

Try our interactive tool to find the best deployment strategy of PostgreSQL that matches your use case before reading this paper in detail:

PostgreSQL Deployment Method Finder

Step 1 of 5

High Availability

How critical is high availability and automated failover?

Rate the importance:
Not ImportantVery Important

Deployment Methods Comparison​

FeatureBasic StatefulSetHelm (Bitnami)CloudNativePGAWS RDS
Production ReadinessDevelopment/TestSmall-Medium ProdEnterpriseEnterprise
Setup ComplySimpleMediumComplexSimple
HA/FailoverManualSemi-AutoAutoAuto
ScalingManualSemi-AutoAutoAuto
Backup/RecoveryManualSemi-AutoAuto + PITRAuto + PITR
MonitoringDIYBasic IncludedAdvancedAWS Native
Maintenance EffortHighMediumLowMinimal
Cost$$$$
Control/FlexibilityFullGoodGoodLimited
Team NeedsK8s BasicsHelm + K8sK8s ExpertAWS/RDS

Choose based on:

Development: Basic StatefulSet

Small Production: Helm Chart

Enterprise/Critical: CloudNativePG or AWS RDS, use RDS if already on AWS Kubernetes expertise Low β†’ RDS, High β†’ CloudNativePG

How to Deploy PostgreSQL on Kubernetes Using StatefulSets: A Basic Guide​

The most straightforward way is to use a StatefulSet for deploying PostgreSQL. This is perfect for development environments and small production workloads where high availability isn't critical.

Prerequisites​

  • A running Kubernetes cluster (1.19+)
  • kubectl configured to reach your cluster
  • Basic understanding of Kubernetes resources
  • Storage class which is of type ReadWriteOnce access mode

Creating a Namespace​

kubectl create namespace postgres
kubectl config set-context --current --namespace=postgres

Create Secrets​

# postgres-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secrets
type: Opaque
data:
POSTGRES_PASSWORD: $(echo -n "your-secure-password" | base64)
POSTGRES_USER: $(echo -n "postgres" | base64)

Apply the secrets:

kubectl apply -f postgres-secrets.yaml

Configuring PostgreSQL​

Create ConfigMap for the PostgreSQL configuration:

# postgres-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
data:
postgresql.conf: |
# Connection Settings
max_connections = 100

# Memory Settings
shared_buffers = 256MB
effective_cache_size = 768MB
maintenance_work_mem = 64MB
work_mem = 2621kB

# Write Ahead Log
wal_buffers = 7864kB
min_wal_size = 1GB
max_wal_size = 4GB

# Query Planning
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200

# Checkpointing
checkpoint_completion_target = 0.9

pg_hba.conf: |
local all all trust
host all all 0.0.0.0/0 md5
host replication all 0.0.0.0/0 md5

Apply the ConfigMap:

kubectl apply -f postgres-config.yaml

Create the StatefulSet​

Now, let's create the PostgreSQL StatefulSet with proper configuration:

# postgres-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15.3
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: POSTGRES_PASSWORD
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-secrets
key: POSTGRES_USER
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
name: postgres
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
- name: postgres-config
mountPath: /etc/postgresql/postgresql.conf
subPath: postgresql.conf
- name: postgres-config
mountPath: /etc/postgresql/pg_hba.conf
subPath: pg_hba.conf
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
livenessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 100Gi

Apply the StatefulSet:

kubectl apply -f postgres-statefulset.yaml

Create the Service​

Create a headless service to expose PostgreSQL:

# postgres-service.yaml
apiVersion: v1
kind: Service
metadata:
name: postgres
labels:
app: postgres
spec:
ports:
- port: 5432
name: postgres
clusterIP: None
selector:
app: postgres

Apply the service:

kubectl apply -f postgres-service.yaml

Setup Monitoring​

Basic monitoring with Prometheus:

# postgres-monitoring.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: postgres-monitor
spec:
selector:
matchLabels:
app: postgres
endpoints:
- port: postgres
interval: 30s
scrapeTimeout: 10s

Configure Backups​

Create a backup solution using CronJob:

# postgres-backup.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: postgres-backup
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: postgres:15.3
command:
- /bin/sh
- -c
- |
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
PGPASSWORD=$POSTGRES_PASSWORD pg_dump -h postgres -U postgres > /backup/db_$TIMESTAMP.sql
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: POSTGRES_PASSWORD
volumeMounts:
- name: backup-volume
mountPath: /backup
volumes:
- name: backup-volume
persistentVolumeClaim:
claimName: postgres-backup-pvc
restartPolicy: OnFailure

Verification Steps​

kubectl get pods -l app=postgres
kubectl get pvc
kubectl get svc
kubectl run -it --rm --image=postgres:15.3 postgres-client -- psql -h postgres -U postgres
kubectl logs -f statefulset.apps/postgres

Common Issues and Solutions​

  1. Pod won't start

    • Check PVC status: kubectl get pvc
    • Check Storage class: kubectl get sc
    • Check pod events: kubectl describe pod postgres-0
  2. Connection issues

    • Check the service: kubectl get svc postgres
    • Verify the endpoints with: kubectl get endpoints postgres
    • Test network policy: kubectl get networkpolicies
  3. Performance issues

    • Check the resources consumption: kubectl top pod postgres-0
    • Verify logs of the PostgreSQL: kubectl logs postgres-0

Best Practices for Basic Deployment​

Adjust resource limits in StatefulSet Best Practices for Basic Deployment

CategoryProsCons
Resource Managementβ€’ Predictable performance, Easy scaling, Clear allocationβ€’ Manual adjustments, Constant monitoring, Risk of misprovisioning
Data Protectionβ€’ Custom backups, Flexible schedules, Full retention controlβ€’ Manual management, Complex restores, High storage needs
Securityβ€’ Full policy control, Custom networking, Granular accessβ€’ Manual updates, Complex secrets, Large attack surface
Monitoringβ€’ Custom metrics, Detailed control, Flexible alertsβ€’ High setup effort, Infrastructure costs, Manual tuning

How to Deploy PostgreSQL Using Bitnami Helm Charts: A Production-Ready Setup​

Following on from the plain vanilla StatefulSet pattern described above, the next evolution for most teams is onto Helm charts: providing a better packaged and managed way of doing deployments. For me, this has been one of the best features of completeness and most actively maintained charts: the Bitnami PostgreSQL Chart.

Prerequisites​

  • Helm 3.x installed
  • Basic knowledge of Helm concepts
  • Kubernetes cluster with Helm up and running

Step 1: Add Bitnami Repository

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

Step 2: Create Values File​

Create a custom values file (values.yaml) for PostgreSQL configuration:

# postgres-values.yaml
global:
postgresql:
auth:
postgresPassword: "your-secure-password"
database: "your-database"

primary:
persistence:
size: 100Gi

resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"

configuration: |
max_connections = 100
shared_buffers = 256MB
effective_cache_size = 768MB
maintenance_work_mem = 64MB
checkpoint_completion_target = 0.9
wal_buffers = 7864kB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 2621kB
min_wal_size = 1GB
max_wal_size = 4GB

metrics:
enabled: true
serviceMonitor:
enabled: true

replication:
enabled: true
readReplicas: 2
synchronousCommit: "on"
numSynchronousReplicas: 1

networkPolicy:
enabled: true

backup:
enabled: true
cronjob:
schedule: "0 2 * * *"
storage:
persistentVolumeClaim:
size: 50Gi

Step 3: Install PostgreSQL​

Deploy PostgreSQL using the Helm chart:

helm install postgres bitnami/postgresql \
--namespace postgres \
--create-namespace \
--values postgres-values.yaml

Step 4: Verify Installation​

Check the deployment status:

# Check all resources
helm status postgres -n postgres
# Get PostgreSQL password
export POSTGRES_PASSWORD=$(kubectl get secret --namespace postgres postgres-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d)
echo $POSTGRES_PASSWORD

# Connect to PostgreSQL
kubectl run postgres-client --rm --tty -i --restart='Never' \
--namespace postgres \
--image docker.io/bitnami/postgresql:15.3.0 \
--env="PGPASSWORD=$POSTGRES_PASSWORD" \
--command -- psql --host postgres-postgresql -U postgres -d postgres -p 5432

Common Operations​

Scaling Read Replicas​

helm upgrade postgres bitnami/postgresql \
--namespace postgres \
--values postgres-values.yaml \
--set replication.readReplicas=3

When my application's read traffic increases, I scale up the read replicas to handle the load better. This command adds another replica to our PostgreSQL cluster.

Update Configuration​

# Edit postgres-values.yaml and then:
helm upgrade postgres bitnami/postgresql \
--namespace postgres \
--values postgres-values.yaml

I often need to tweak PostgreSQL settings as my application grows. This simple upgrade command applies any configuration changes I've made in my values file.

Backup and Restore​

Manual Backup:

kubectl exec -it postgres-postgresql-0 -n postgres -- \
pg_dump -U postgres > backup.sql

I use this command for quick, on-demand backups before making major changes. It's a straightforward way to get a point-in-time snapshot of my database.

Restore from Backup:

kubectl exec -i postgres-postgresql-0 -n postgres -- \
psql -U postgres < backup.sql

When things go wrong, I can easily restore my database using this command. It's saved me more than once during development and testing.

Monitoring and Alerts​

The Bitnami chart already includes Prometheus exporters. To configure extra monitoring, perform the following steps:

# prometheus-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: postgresql-alerts
namespace: postgres
spec:
groups:
- name: postgresql
rules:
- alert: PostgreSQLHighConnections
expr: pg_stat_activity_count > 100
for: 5m
labels:
severity: warning
annotations:
description: "PostgreSQL instance has too many connections"

- alert: PostgreSQLReplicationLag
expr: pg_replication_lag_bytes > 100000000
for: 5m
labels:
severity: critical
annotations:
description: "PostgreSQL replication is lagging"

Best Practices for Helm Deployment​

CategoryProsCons
Version Controlβ€’ Reproducible deploys, Easy rollbacks, Clear historyβ€’ Complex versioning, Dependency issues, Storage overhead
High Availabilityβ€’ Built-in replication, Auto failover, Pod anti-affinityβ€’ High resource usage, Complex setup, Network costs
Backup Strategyβ€’ Auto backups, Multiple options, Cross-zone storageβ€’ Storage costs, Performance impact, Complex policies
Securityβ€’ Built-in features, Auto rotation, SSL supportβ€’ Cert management, Complex updates, Config hardening

How to Set Up Enterprise-Grade PostgreSQL with CloudNativePG Operator​

CloudNativePG is my go-to choice for a production deployment that requires enterprise-grade features. It's a full Kubernetes-native operator that provides advanced PostgreSQL capabilities to your cluster.

Prerequisites​

  • Kubernetes 1.21+
  • kubectl and Helm installed (optional)
  • Cluster administrator privileges to install the operator

Installing Operator​

# Using kubectl
kubectl apply -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.21/releases/cnpg-1.21.0.yaml

# Or using Helm
helm repo add cloudnative-pg https://cloudnative-pg.github.io/charts
helm install cloudnative-pg cloudnative-pg/cloudnative-pg \
--namespace cnpg-system \
--create-namespace

Create a Basic Cluster​

Let's start with a basic PostgreSQL cluster:

# postgres-cluster.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgres-cluster
spec:
instances: 3

# PostgreSQL configuration
postgresql:
parameters:
max_connections: "100"
shared_buffers: "256MB"
effective_cache_size: "768MB"
maintenance_work_mem: "64MB"
checkpoint_completion_target: "0.9"
wal_buffers: "16MB"
default_statistics_target: "100"
random_page_cost: "1.1"
effective_io_concurrency: "200"
work_mem: "2621kB"
min_wal_size: "1GB"
max_wal_size: "4GB"

# Storage configuration
storage:
size: 100Gi
storageClass: standard

# Resource requirements
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"

# Backup configuration
backup:
barmanObjectStore:
destinationPath: "s3://my-bucket/backup"
endpointURL: "https://s3.amazonaws.com"
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: SECRET_ACCESS_KEY

# Monitoring configuration
monitoring:
enablePodMonitor: true

Configure Backups​

Create an S3 credentials secret:

# aws-creds.yaml
apiVersion: v1
kind: Secret
metadata:
name: aws-creds
type: Opaque
stringData:
ACCESS_KEY_ID: your-access-key
SECRET_ACCESS_KEY: your-secret-key

Deploy the Cluster​

Apply the configurations:

kubectl apply -f aws-creds.yaml
kubectl apply -f postgres-cluster.yaml

Advanced Features​

1. High Availability Configuration​

# ha-postgres-cluster.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgres-ha
spec:
instances: 3

postgresql:
parameters:
# Replication settings
max_wal_senders: "10"
max_replication_slots: "10"
wal_level: "logical"

replicationSlots:
highAvailability:
enabled: true

bootstrap:
recovery:
source: postgres-cluster

# Anti-affinity settings
affinity:
enablePodAntiAffinity: true
topologyKey: kubernetes.io/hostname

2. Point-in-Time Recovery​

# pitr-recovery.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgres-pitr
spec:
instances: 3
bootstrap:
recovery:
source: postgres-cluster
recoveryTarget:
targetTime: "2024-01-01 00:00:00.000000+00"

3. Rolling Updates​

CloudNativePG automatically handles rolling updates. Just update the cluster spec:

kubectl patch cluster postgres-cluster --type merge \
-p '{"spec":{"postgresql":{"parameters":{"max_connections":"200"}}}}'

Monitoring Setup​

Build a complete monitoring stack:

# monitoring-config.yaml
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: postgres-monitor
spec:
selector:
matchLabels:
postgresql: postgres-cluster
podMetricsEndpoints:
- port: metrics
interval: 30s

---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: postgres-alerts
spec:
groups:
- name: postgresql
rules:
- alert: PostgreSQLHighReplicationLag
expr: pg_replication_lag_bytes > 100000000
for: 5m
labels:
severity: critical
- alert: PostgreSQLHighConnections
expr: pg_stat_activity_count > 100
for: 5m
labels:
severity: warning

Backup and Recovery

1. On-demand Backup​

# backup.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
name: postgres-backup
spec:
cluster:
name: postgres-cluster

2. Planned Backups​

# scheduled-backup.yaml
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: postgres-scheduled-backup
spec:
schedule: "0 0 * * *"
cluster:
name: postgres-cluster
immediate: true

Best Practices for CloudNativePG​

CategoryProsCons
High Availabilityβ€’ Native K8s, Auto failover, Multi-zoneβ€’ High costs, Complex setup, Network overhead
Backup Strategyβ€’ Continuously archiving, PITR, multistorageβ€’ Costs of storage, Validation overhead, Complex retention
Monitoringβ€’ Native metrics, Prometheus, Custom alertsβ€’ Resource overhead, Complex dashboards, Alert tuning
Securityβ€’ Native security, Auto certs, RBACβ€’ Complex setup, Regular rotation, Policy overhead

How to Integrate AWS RDS PostgreSQL with Kubernetes: A Complete Guide​

Now, let's discuss how one might go about using AWS RDS after exploring self-managed options: a fully managed PostgreSQL Service to offload the database management tasks to AWS while it seamlessly integrates with your Kubernetes workloads.

Prerequisites​

  • AWS Account with appropriate permissions
  • AWS CLI configured
  • eksctl or similar tools for EKS management
  • AWS hosted Kubernetes cluster preferably using EKS

Create RDS Instance​

First, let's create an RDS instance using AWS RDS Operator:

# rds-instance.yaml
apiVersion: rds.services.k8s.aws/v1alpha1
kind: DBInstance
metadata:
name: postgres-rds
spec:
engine: postgres
engineVersion: "15.3"
dbInstanceClass: db.t3.large
dbInstanceIdentifier: postgres-prod
masterUsername: postgres
masterUserPassword:
name: rds-credentials
key: password
allocatedStorage: 100
maxAllocatedStorage: 200
publiclyAccessible: false
vpcSecurityGroupIDs:
- sg-xxxxxxxxxxxxxxxxx
dbSubnetGroupName: my-db-subnet-group

# High Availability Configuration
multiAZ: true

# Backup Configuration
backupRetentionPeriod: 7
preferredBackupWindow: "03:00-04:00"

# Maintenance Window
preferredMaintenanceWindow: "Mon:04:00-Mon:05:00"

# Performance Insights
enablePerformanceInsights: true
performanceInsightsRetentionPeriod: 7

# Enhanced Monitoring
monitoringInterval: 60
monitoringRoleARN: arn:aws:iam::123456789012:role/rds-monitoring-role

# Storage Configuration
storageType: gp3
iops: 3000

Create Kubernetes Secret​

Create a secret for database credentials:

# rds-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: rds-credentials
type: Opaque
stringData:
password: your-secure-password
username: postgres

Create Service for RDS​

Create a Kubernetes service to access RDS:

# rds-service.yaml
apiVersion: v1
kind: Service
metadata:
name: postgres-rds
spec:
type: ExternalName
externalName: postgres-prod.xxxxx.region.rds.amazonaws.com

Step 4: Configure Connection Pooling​

Deploy PgBouncer for connection pooling:

# pgbouncer-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgbouncer
spec:
replicas: 2
selector:
matchLabels:
app: pgbouncer
template:
metadata:
labels:
app: pgbouncer
spec:
containers:
- name: pgbouncer
image: edoburu/pgbouncer:1.18.0
env:
- name: DB_HOST
value: "postgres-prod.xxxxx.region.rds.amazonaws.com"
- name: DB_USER
valueFrom:
secretKeyRef:
name: rds-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: rds-credentials
key: password
ports:
- containerPort: 5432
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"

Step 5: Set Up Monitoring​

Create CloudWatch metrics collection:

# cloudwatch-metrics.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: cloudwatch-agent-config
data:
cwagentconfig.json: |
{
"metrics": {
"metrics_collected": {
"rds": {
"metrics_collection_interval": 60,
"resources": [
"postgres-prod"
],
"measurement": [
"CPUUtilization",
"DatabaseConnections",
"FreeStorageSpace",
"ReadIOPS",
"WriteIOPS"
]
}
}
}
}

AWS RDS Integration Best Practices​

CategoryProsCons
Network Securityβ€’ AWS security, VPC, PrivateLinkβ€’ Complex setup, Cross-account issues, Latency
High Availabilityβ€’ Multi-AZ, Auto failover, Read replicasβ€’ High costs, Region comply, Network costs
Backup Strategyβ€’ Auto backups, Cross-region, PITRβ€’ Backup windows, Storage costs, Recovery time
Performanceβ€’ Managed ops, Performance insights, Auto scalingβ€’ Limited control, Cost tradeoffs, Size limits

Common Operations​

1. Creating Read Replica​

aws rds create-db-instance-read-replica \
--db-instance-identifier postgres-prod-replica \
--source-db-instance-identifier postgres-prod

2. Scaling Storage​

aws rds modify-db-instance \
--db-instance-identifier postgres-prod \
--allocated-storage 200 \
--apply-immediately

3. Taking Manual Snapshot​

aws rds create-db-snapshot \
--db-instance-identifier postgres-prod \
--db-snapshot-identifier manual-backup-$(date +%Y%m%d)

Monitoring and Alerting​

  1. CloudWatch Alarms
# cloudwatch-alarms.yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
HighCPUAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: RDS-HighCPU
MetricName: CPUUtilization
Namespace: AWS/RDS
Statistic: Average
Period: 300
EvaluationPeriods: 2
Threshold: 80
AlarmActions:
- arn:aws:sns:region:account-id:notification-topic
Dimensions:
- Name: DBInstanceIdentifier
Value: postgres-prod
  1. Kubernetes Prometheus Integration
# prometheus-servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: rds-monitor
spec:
endpoints:
- interval: 30s
port: metrics
selector:
matchLabels:
app: cloudwatch-exporter

Cost Optimization​

  1. Instance Right-sizing

    • Monitor Performance Insights
    • Use AWS Cost Explorer
    • Consider Reserved Instances
    • Scale instance class based on usage
  2. Optimizing Storage

    • Monitor storage growth
    • Enable storage autoscaling
    • Cleanup old snapshots
    • Use gp3 storage for better performance/cost ratio

Migration to RDS​

If migrating from another solution:

  1. Preparation
# Create subnet group
aws rds create-db-subnet-group \
--db-subnet-group-name my-subnet-group \
--subnet-ids subnet-xxxxx subnet-yyyyy

# Create parameter group
aws rds create-db-parameter-group \
--db-parameter-group-name custom-postgres15 \
--db-parameter-group-family postgres15
  1. Data Migration
# Using AWS DMS
aws dms create-replication-instance \
--replication-instance-class dms.t3.large \
--replication-instance-identifier migration-instance

# Or using pg_dump/pg_restore
pg_dump -h old-postgres -U postgres | \
psql -h postgres-prod.xxxxx.region.rds.amazonaws.com -U postgres
  1. Application Migration
# Update application configurations
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: app
env:
- name: DB_HOST
value: postgres-rds
- name: DB_PORT
value: "5432"
- name: DB_USER
valueFrom:
secretKeyRef:
name: rds-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: rds-credentials
key: password

That completes this rather comprehensive tutorial about the various ways you could deploy PostgreSQL on Kubernetes. Each has its pros and use cases, and which to choose depends upon your particular needs, team skills, and operational constraints.

Conclusion​

Well, after playing around with various ways of deploying PostgreSQL on Kubernetes, my conclusion would be that each has a certain purpose: the simple StatefulSets, a very good development and learning ground; Helm charts give balanced solutions for a normal production workload, but advanced features with native integration of CloudNativePG, maybe will answer enterprise needs, and those in the AWS ecosystem would take RDS.

Choose your deployment method based on your team's expertise, operational requirements, and budget constraints. Keep in mind that you can always start simple and evolve your infrastructure as your needs grow.