In this guide, you will learn how to deploy and run a Postres database server instance in Kubernetes.
- Deploying containerized Postgress instances in Kubernetes
- Using Kubernetes Secrets to store sensitive configurations
- Using Kubernetes ConfigMaps to store non-sensitive configurations
- Using Kuberentes Persistent Volumes to persist databases
- Using Kubernetes CronJobs to backup databases
PostgreSQL Docker Image
PostgreSQL offers an Official PostgreSQL Docker image through Docker Hub. It always recommended to use official images over third-party or self-created images. In this guide we will use the official image to deploy the database server in Kubernetes.
Environment variables allow us to configure environment specific parameters for Docker images that support them. The official Postgres images support the following environment variables.
- POSTGRES_PASSWORD (required) is used to set the superuser password.
- POSTGRES_USER (optional) is used to create a superuser with the name set in the environment variable.
- POSTGRES_DB (optional) is used to create a database to be used as the default.
We will dive into setting these values in the next section: Configuring PostreSQL. For now, it’s good to understand what variables are available for configuring your instance.
ConfigMaps provide a means to store environment parameters in Kubernetes, to be fetched by a Pod when it starts. Values in a ConfigMap and be key-pair strings, entire files, or both. Which you use depends on your implementation.
Data stored in a ConfigMap not encrypted. These Kubernetes resources should be only be used with data that is non-sensitive.
apiVersion: v1 kind: ConfigMap metadata: name: postgres data: POSTGRES_DB: myapp_production
For sensitive data, such as user credentials, Kubernetes Secrets allow you to more safely store data in the cluster. Like ConfigMap, the values in a Secret can be fetched by a Pod during startup.
Kubernetes Secrets can be expressed using a manifest, like any other resource. However, due to their sensitive nature it is not recommended to store secret manifest files in your version control system with actual values.
To create a secret resource named
postgres for storing a superuser username and password, use the
kubectl create secret command.
kubectl create secret generic postgres \ --from-literal=POSTGRES_USER="root" \ --from-literal=POSTGRES_PASSWORD="my-super-secret-password"
Alternatively, if you do wish to use a manifest file, create a new
postgres-secrets.yaml file with the following structure.
apiVersion: v1 kind: Secret metadata: name: postgres data: POSTGRES_PASSWORD: bXktc3VwZXItc2VjcmV0LXBhc3N3b3Jk stringData: POSTGRES_USER: root
Notice the value used for
POSTGRES_PASSWORD. That value is not the actual password. Rather, it is base64 encoded string of the password. Do not confuse base64 encoding with encyption. It merely serves to obfuscate the password to prevent prying eyes from easily reading it.
To base64 encode a string in Linux and OSX, you use the
echo -n 'my-super-secret-password' | base64
To create the secret resource for Postgres, apply the manifest.
kubectl apply -f postgres-secrets.yaml
While containers in Kubernetes run from Pods a Postgres server should not be deployed as a pod. Rather, a Kubernetes Deployment should be used.
Persistent Volume Claim
Database servers are stateful and, therefore, their databases are expected to be persistent. A container, on the other hand, is ephermeral with no expectation for persisting state. This is obviously problemmatic with stateful applications like database servers.
A Kubernetes Persistent Volume is a volume that attaches to a Pod or group of Pods. Data writtent to it persists and is available to any Pod that mounts it. In most use cases it can be thought of as a network filesystem (NFS), as the behaviour is similar.
To mount a Persistent Volume to a Pod a Persistent Volume Claim must exist.
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: postgres-pv-claim labels: app: postgres spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi
A Kubernetes deployment manifest for Postgres would look similar to the example.
- Create a single pod replica.
- Use a base image Postgres 12.4 runing on Alpine Linux.
- Expose port 5432.
- Environment variables set using parameters found in a Secret manifest
- Environment variables set using parameters found in a ConfigMap.
- Mount a Persistent Volume using a Persistent Volume Claim for the Postgres database files.
apiVersion: apps/v1 kind: Deployment metadata: name: postgres spec: replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:12.4-alpine ports: - containerPort: 5432 envFrom: - secretRef: name: postgres-secrets - configMapRef: name: postgres-configmap volumeMounts: - name: postgres-database-storage mountPath: /var/lib/pgsql/data volumes: - name: postgres-database-storage persistentVolumeClaim: claimName: postgres-pv-claim
Services running in Kubernetes are exposed using Kubernetes Services. The following Service manifest will attach to our Postgres deployment and accept traffic over TCP port 5432.
apiVersion: v1 kind: Service metadata: name: postgres spec: selector: app: postgress ports: - protocol: TCP port: 5432 clusterIP: none
In the manifest above we’ve chosen not to provide an internal Cluster IP address to our Postgres service. We do not want the service to be exposed outside of the cluster. Rather, we want to force applications to connect to this service using Kubernetes’ CoreDNS.
The DNS name for this service will be the following.
default part of the name is derived from the resource’s namespace. Since we have not set a namespace for our Postgre reources, they will automaticaly default to the default namespace.
Backing up Databases
As with any database service, backups should be done routinely in order to safe guard your data. While you could
kubectl exec into the running Postgres Pod and run the
pg_dump, that’s a manual process that should be avoided.
Instead, we are going to create a CronJob in order for us to automate the task. A Kuberentes CronJob is a scheduled job that schedules a pod for executing commands. Our CronJob will perform the following tasks.
- Use the Google Cloud SDK Docker image
- Mount the Postgre database persistent volume.
- Use the Postgres ConfigMap and Secret to inject environment variables
pg_dumpagainst the database
- Copy the database dump to a Google Cloud Storage Bucket.
apiVersion: batch/v1beta1 kind: CronJob metadata: name: postgres-backup spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: postgres-backup image: google/cloud-sdk:alpine args: - apk --update add postgresql - pg_dump -u myblog > myblog-$(date +%s).bak - gsutil cp myblog.bak gs://myblog/backups envFrom: - secretRef: name: postres-secrets - configMapRef: name: postgres-configmap volumeMounts: - name: postgres-database-storage mountPath: /var/lib/pgsql/data volumes: - name: postgres-database-storage persistentVolumeClaim: claimName: postgres-pv-claim
Upgrading PostgreSQL on Kubernetes
New versions of Postgres our released on a regular basis, from major releases to bug\security fixes. While our database service is running in a containerized world, swapping your image from an older release to a newer one will likely not work.
Major and Minor Upgrades
When major new release of PostgreSQL is released databases on your current release must be upgraded to the latest schema. This is a hard requirement, and that makes it impossible to do in a containerized workload.
For major releases you will need to do the following:
- Create a separate deployment running a more recent container image of PostgreSQL.
- Dump your data from the currently running pod.
- Import your data dump into the newer PostgreSQL deployment.
- Tear down your old PostgreSQL deployment.
For a more detailed workflow for upgrading PostgreSQL running in Kubernetes, read How to upgrade PostgreSQL in Docker and Kubernetes
Bugfix and security patches do not have the same requirement as major and minor PostgreSQL releases. You can simply update your deployment to the latest bugfix version (X.X.1 -> X.X.2).