
Kubernetes is a container orchestration platform that automates the deployment, scaling, and management of containerized applications, abstracting many of the manual steps of rolling upgrades and scaling. When building cloud-native applications, you’ll often need to deploy database applications like PostgreSQL so that your applications can leverage their features within the cluster.
In this guide, we’ll walk you through deploying PostgreSQL in a Kubernetes cluster, giving you a configurable and scalable cloud-native PostgreSQL setup. Whether you’re working on Google Kubernetes Engine, Amazon EKS, IBM Cloud Kubernetes Service, or a local solution like Minikube, this tutorial applies. Read on to learn how to deploy a single PostgreSQL instance on Kubernetes for testing and explore more advanced deployments using tools like Helm, Kubernetes Operators, and Bitnami PostgreSQL.
Prerequisites
To follow along, make sure you have:
- A Kubernetes cluster (on a cloud provider like DigitalOcean, Amazon Web Services, Google Cloud, or IBM Cloud, or locally on Kind or Minikube)
- Some working knowledge of kubectl, the Kubernetes API command-line tool
Before we start, let’s go through the basic steps for deploying a single instance of PostgreSQL on Kubernetes.
Simple PostgreSQL deployment
Dockerize PostgreSQL
Kubernetes pulls Docker images from a registry and deploys them based on a configuration file. You can build your own PostgreSQL Docker image following these steps, or use the official open-source image from Docker Hub. In this post, we’ll be using the latest Postgres 15.3 image.
Create your connection configuration and secrets
To securely store sensitive information like database credentials, we’ll use the Kubernetes secrets configuration, a native Kubernetes resource.
Kubernetes stores secrets in base64-encoded form by default, which isn’t secure on its own. For better security, enable encryption at rest.
Once everything is configured, create the configuration for the Kubernetes secret. We’ll use the following values for the Postgres password:
❯ echo -n "postgres" | base64
cG9zdGdyZXMK
Then, create a secrets config file and apply it to the cluster:
> cat postgres-secrets.yml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret-config
type: Opaque
data:
password: cG9zdGdyZXMK
Here, we used kind
, which is a secret that instructs Kubernetes to use a secrets provider to store the data. The name that it needs to use to store those values is under the key postgres-secret-config
. Finally, we provided the key/value pairs that we need to secretly store in the data section.
Now, we apply this config and then verify that the contents are stored correctly:
❯ kubectl apply -f postgres-secrets.yml
secret/postgres-secret-config created
❯ kubectl get secret postgres-secret-config -o yaml
apiVersion: v1
data:
password: cG9zdGdyZXMK
....
Create PersistentVolume and PersistentVolumeClaim
Next, you want to create permanent file storage for your database data, as the Docker instance doesn’t have persistent storage for any information when the container no longer exists (by default).
The solution is to mount a filesystem to store the PostgreSQL data. Kubernetes has a different configuration format for those operations, so you’d follow these steps:
- Create a PersistentVolume manifest that describes the type of volumes you want to use.
- Create a PersistentVolumeClaim that requests the usage for that particular PersistentVolume type based on the same storage class.
For our example, we will use the current node filesystem as a volume, but it’s better to use a StorageClass suitable for database operations, such as standard, gp2, or cloud provider-specific dynamic provisioners that support replication and IOPS optimization.
First, we define the configuration for the PersistentVolume:
> cat pv-volume.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
In this configuration, we instructed it to reserve 5GB of read-write storage at /mnt/data on the cluster’s node.
Now, we apply it and check that the persistent volume is available:
❯ kubectl apply -f pv-volume.yml
persistentvolume/postgres-pv-volume created
❯ kubectl get pv postgres-pv-volume
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
postgres-pv-volume 5Gi RWO Retain Available manual 51s
We need to follow up with a PersistentVolumeClaim configuration that matches the details of the previous manifest:
> cat pv-claim.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
In this configuration, we requested a PersistentVolumeClaim for 1GB of data using the same storage class name. This is an important parameter because it lets Kubernetes reserve 1GB of the available 5GB of the same storage class for this claim.
Now, we apply it and check that the persistent volume claim is bound:
❯ kubectl apply -f pv-claim.yml
persistentvolumeclaim/postgres-pv-claim created
❯ kubectl get pvc postgres-pv-claim
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
postgres-pv-claim Bound postgres-pv-volume 1Gi RWO manual 5m32s
Create a Kubernetes deployment for PostgreSQL
Using the settings from the Postgres-secret-config secret name and referencing the PersistentVolume and PersistentVolumeClaim that we created earlier, we’ll issue a deployment configuration for our Kubernetes instance.
> cat postgres-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
volumes:
- name: postgres-pv-storage
persistentVolumeClaim:
claimName: postgres-pv-claim
containers:
- name: postgres
image: postgres:11
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret-config
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-pv-storage
Here, we glued all of the configurations that we defined earlier with the Kubernetes secret config and the persistent volume mounts. We used the apiVersion: apps/v1 deployment config, which requires us to specify quite a few lines, such as the selector and metadata fields. Then, we added details of the container image and the image pull policy. This is all necessary to ensure that we have the right volume and secrets used for that container.
Now, we apply the deployment and check that it’s available and healthy:
❯ kubectl apply -f postgres-deployment.yml
deployment.apps/postgres created
❯ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
postgres 1/1 1 1 28s
Create a service to expose the PostgreSQL server
Use a Kubernetes Service to expose your PostgreSQL pod. To do so, you can configure a different port or expose the NodePort or LoadBalancer. For the sake of simplicity, we’ll show you how to use NodePort, which exposes the service on the Node’s IP at a static port.
You can use the following service manifest:
> cat postgres-service.yml
apiVersion: v1
kind: Service
metadata:
name: postgres
labels:
app: postgres
spec:
type: NodePort
ports:
- port: 5432
selector:
app: postgres
Next, apply the service and check that it’s available and has been assigned a port:
❯ kubectl apply -f postgres-service.yml
service/postgres created
❯ kubectl get service postgres
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
Test the connection to the Postgres database
You should be able to connect to the PostgreSQL database internally using the following commands:
❯ kubectl get pods
NAME READY STATUS RESTARTS AGE
postgres-57f4746d96-7z5q8 1/1 Running 0 30m
❯ kubectl exec -it postgres-57f4746d96-7z5q8 -- psql -U postgres
There’s also a handy way to store the Kubernetes pod name in a variable:
POD=$(kubectl get pods -l app=postgres -o jsonpath="{.items[0].metadata.name}")
You could also use another Docker container to connect through the psql command:
export POSTGRES_PASSWORD=$(kubectl get secret postgres-secret-config -o jsonpath="{.data.password}" | base64 --decode)
❯ kubectl run postgres-client --rm --tty -i --restart='Never' --image postgres:11 --env="PGPASSWORD=$POSTGRES_PASSWORD" --command -- psql -h postgres -U postgres
If you don’t see a command prompt, try pressing enter.
postgres=#
Now, you’re ready to perform queries.
Advanced PostgreSQL deployment
In the previous example, we only deployed a single instance of PostgreSQL for development purposes. For production and hybrid cloud environments, you could use:
- Bitnami PostgreSQL Deployment: Bitnami supports multiple types of deployments (including Helm, a Kubernetes community-maintained package manager) and supports many configuration options for large-scale deployments.
- Zalando PostgreSQL Operator: This is a high-availability Kubernetes operator that relies on their existing Patroni-based cluster template.
These options help support enterprise-ready infrastructure and cloud native computing foundation best practices.
Next steps
Now that you’ve deployed a simple PostgreSQL instance on Kubernetes, you’re ready to integrate monitoring and logging. Tools like Sumo Logic support advanced Kubernetes monitoring, helping you manage your cluster resources, analyze logs from Kubernetes pods, and ensure observability of your cloud native applications. By default, volumes may be deleted when their claim is removed. Setting the reclaim policy to Retain ensures your data remains intact if a PVC is deleted.
Learn how Sumo Logic can help with your Kubernetes monitoring.