Business and Tech

Kubernetes – Configure PostgreSQL Streaming Replication

Written by:
Published on June 8, 2020

Kubernetes – Configure PostgreSQL Streaming Replication

PostgreSQL relies on replication for high availability, failover, and balancing read requests across multiple nodes. Streaming Replication ensures that data written to the primary PostgreSQL database is mirrored on to one or more standby (replica) nodes.

Standby nodes accept read-only connections, so all traffic related to any Reporting or BI Dashboard applications can be routed to standby nodes thereby reducing the load on primary database instance.

In this post lets look at how to configure Postgres streaming replication in a Kubernetes cluster.

Prerequisites

NOTE: This post assumes an existing Kubernetes cluster is already setup with a NFS mount. Setting up of kubernetes cluster is not in the scope of this post.

PostgreSQL Streaming Replication

Streaming replication works by streaming any modifications from Primary Database to Standby Database instances (number of standby databases can be configured based on the requirement). Standby nodes can be used to offload read queries there by reducing the load on Primary Database.

A Standby Database instance can be promoted to Primary in case the Primary Database crashes. It is always advised not to configure Primary and Standby Database instances on the same disk or NFS drive.

Following diagram illustrates a Primary Database configured with 2 Standby Database instances on different NFS drives. Applications can use Primary Database for write operations and redirect reads to Standby instances.

MuleSoft Code Review
MulSoft Code Analysis

Kubernetes – Configure Streaming Replication

In this example we will be configuring a Primary Database instance and 1 Standby instance. Two NFS volume mounts will be used namely

  • nfs – for Primary Database
  • nfs-standby – for Standby Database

Setup Primary Database

Create a config map with environmental variables required for Postgres. Create a new file called db-config.yaml and pase the following contents.

apiVersion: v1
kind: ConfigMap
metadata:
  name: izdb-config
  namespace: default
  labels:
    type: config
    app: izdb
data:
  POSTGRES_PASSWORD: "changeme"
  TIMESCALEDB_TELEMETRY: "off"

Execute the below command to create the config map –

kubectl create -f db-config.yaml 

Deployment Pod definition for initialising Postgres primary database. Create a new file called db-primary.yaml and paste the below contents –

apiVersion: apps/v1
kind: Deployment
metadata:
  name: izdb
spec:
  replicas: 1
  selector:
    matchLabels:
      app: izdb
  template:
    metadata:
      labels:
        type: deployment
        app: izdb
    spec:
      containers:
      - image: postgres:10
        imagePullPolicy: IfNotPresent
        name: izdb
        ports:
          - containerPort: 5432
        envFrom:
          - configMapRef:
              name: izdb-config
        volumeMounts:
          - name: izdbmnt
            mountPath: /var/lib/postgresql/data
            subPath: izdb/data
      volumes:
      - name: izdbmnt
        persistentVolumeClaim:
          claimName: nfs

Execute the below command to create the pod –

kubectl create -f db-primary.yaml

Once izdb pod comes up the data directory in nfs i.e. izdb/data will be initialised with required files which is sufficient to run a standalone instance of Postgres server. But, to configure replication few parameters have to be changes in postgresql.conf and pg_hba.conf

Execute the following commands in the izdb pod (use “kubectl exec -it <pod_id> bash” to get into the pod/container). We are going to create a replication user and physical replication slot.

> su - postgres
> psql
# SET password_encryption = 'scram-sha-256';
# CREATE ROLE repuser WITH REPLICATION PASSWORD 'changeme' LOGIN;
# SELECT * FROM pg_create_physical_replication_slot('replica_1_slot');

Copy postgresql.conf and pg_hba.conf from the izdb pod to a local directory called config.

Append following contents to postgresql.conf

#Replication Settings

wal_level = replica
max_wal_senders = 2
max_replication_slots = 2
synchronous_commit = off

Append following contents to pg_hba.conf

host    replication     repuser         0.0.0.0/0       scram-sha-256

NOTE: 0.0.0.0/0 can be replaced with the Standby Database ip.

Execute the following command to create conf files map from the config directory –

kubectl create configmap izdb-conf-files-config --from-file config

Now lets mount the updated postgresql.conf and pg_hba.conf to postgres data directory to enable streaming replication. Replace the contents of db-primary.yaml with following –

apiVersion: apps/v1
kind: Deployment
metadata:
  name: izdb
spec:
  replicas: 1
  selector:
    matchLabels:
      app: izdb
  template:
    metadata:
      labels:
        type: deployment
        app: izdb
    spec:
      containers:
      - image: postgres:10
        imagePullPolicy: IfNotPresent
        name: izdb
        ports:
          - containerPort: 5432
        envFrom:
          - configMapRef:
              name: izdb-config
        volumeMounts:
          - name: izdbmnt
            mountPath: /var/lib/postgresql/data
            subPath: izdb/data
      volumes:
      - name: izdbmnt
        persistentVolumeClaim:
          claimName: nfs
      - name: izdb-conf-files-config-mnt
        configMap:
          name: izdb-conf-files-config
      initContainers:
      - name: init-izdb
        image: busybox:1.28
        command: ['sh', '-c', ' cp /var/lib/postgresql/data/postgresql.main.conf /var/lib/postgresql/data/postgresql.conf && cp /var/lib/postgresql/data/pg_hba.main.conf /var/lib/postgresql/data/pg_hba.conf && chmod 600 /var/lib/postgresql/data/postgresql.conf && chmod 600 /var/lib/postgresql/data/pg_hba.conf']
        volumeMounts:
        - name: izdbmnt
          mountPath: /var/lib/postgresql/data
          subPath: izdb/data
        - name: izdb-conf-files-config-mnt
          mountPath: /var/lib/postgresql/data/postgresql.main.conf
          subPath: postgresql.conf
        - name: izdb-conf-files-config-mnt
          mountPath: /var/lib/postgresql/data/pg_hba.main.conf
          subPath: pg_hba.conf            

Expose the izdb pod as a service to be accessed by other applications. Create a new file called izdb-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: izdb
spec:
  type: NodePort
  selector:
    app: izdb
  ports:
    - protocol: TCP
      port: 5432
      targetPort: 5432

Execute the following command to create service –

kubectl create -f izdb-service.yaml

Setup Standby Database

Before configuring the Standby instance we need to sync Standby instance with Primary Database state. We will be configuring Standby instance in a different nfs drive called nfs-standby

Create a new file called izdb-standby.yaml and paste the below contents –

apiVersion: apps/v1
kind: Deployment
metadata:
  name: izdb-standby
spec:
  replicas: 1
  selector:
    matchLabels:
      app: izdb-standby
  template:
    metadata:
      labels:
        type: izdb-standby
        app: izdb-standby
    spec:
      containers:
      - image: busybox:1.28
        imagePullPolicy: Always
        name: izdb-standby
      volumes:
      - name: izdb-standby-mnt
        persistentVolumeClaim:
          claimName: nfs-standby
      - name: izdb-standby-config-mnt
        configMap:
          name: izdb-standby-conf-files-config
      initContainers:
      - name: init-izdb-standby
        image: postgres:10
        command: ['sh', '-c', 'PGPASSWORD="changeme" pg_basebackup -h izdb -D /var/lib/postgresql/data -U repuser -vP ']
        volumeMounts:
        - name: izdb-standby-mnt
          mountPath: /var/lib/postgresql/data
          subPath: izdb_standby/data    

Execute the following command to create a base backup of Primary Database instance –

kubectl create -f izdb-standby.yaml

NOTE: We are using a busy box instance as main container as we should not run Postgres in Primary mode after base backup. After base backup we need to start Postgres in Standby mode.

Copy postgresql.conf from the izdb-standby pod to a local directory called standby-config and append following contents –

hot_standby = on
wal_level = replica
max_wal_senders = 2
max_replication_slots = 2
synchronous_commit = off

Create a new file called recovery.conf in standby-config directory with following contents –

standby_mode = on
primary_conninfo = 'host=izdb port=5432 user=repuser password=changeme application_name=r1'
primary_slot_name = 'replica_1_slot' # Name of the replication slot we created on the master
trigger_file = '/var/lib/postgresql/data/change_to_master'

Execute the following command to create conf files map from the standby-config directory –

kubectl create configmap izdb-standby-conf-files-config --from-file standby-config

Delete the pod izdb-standby and replace the contents of izdb-standby.yaml with following contents

apiVersion: apps/v1
kind: Deployment
metadata:
  name: izdb-standby
spec:
  replicas: 1
  selector:
    matchLabels:
      app: izdb-standby
  template:
    metadata:
      labels:
        type: izdb-standby
        app: izdb-standby
    spec:
      containers:
      - image: postgres:10
        imagePullPolicy: IfNotPresent
        name: izdb-standby
        ports:
          - containerPort: 5432
        envFrom:
          - configMapRef:
              name: izdb-config
        volumeMounts:
          - name: izdb-standby-mnt
            mountPath: /var/lib/postgresql/data
            subPath: izdb_standby/data
      volumes:
      - name: izdb-standby-mnt
        persistentVolumeClaim:
          # use nfs for dev and nfs-standby for prod  
          #claimName: nfs
          claimName: nfs-standby
      - name: izdb-standby-config-mnt
        configMap:
          name: izdb-standby-conf-files-config
      initContainers:
      - name: init-izdb-standby
        image: timescale/timescaledb:latest-pg10
        command: ['sh', '-c', 'cp /var/lib/postgresql/data/postgresql.main.conf /var/lib/postgresql/data/postgresql.conf && cp /var/lib/postgresql/data/recovery.main.conf /var/lib/postgresql/data/recovery.conf && chmod 600 /var/lib/postgresql/data/postgresql.conf && chmod 600 /var/lib/postgresql/data/recovery.conf']
        volumeMounts:
        - name: izdb-standby-mnt
          mountPath: /var/lib/postgresql/data
          subPath: izdb_standby/data    
        - name: izdb-standby-config-mnt
          mountPath: /var/lib/postgresql/data/postgresql.main.conf
          subPath: postgresql.conf
        - name: izdb-standby-config-mnt
          mountPath: /var/lib/postgresql/data/recovery.main.conf
          subPath: recovery.conf              

Execute the following command to create Standby Database instance

kubectl create -f izdb-standby.yaml

With this Postgres Streaming Replication should be configured with one Primary and one Standby Database instances.

End Note

Hope you found this article interesting, do drop us a comment below with your inputs, views, and opinions regarding Kubernetes – Configure PostgreSQL Streaming Replication

Mulesoft Code Analysis

Interested in trying out our exciting new product on automated Mulesoft Code Analysis and learn how it reduces project costs by over 80%?

Please follow the link below to know more:

Quantifying benefits of IZ Analyzer for Mule Projects

6 Replies to “Kubernetes – Configure PostgreSQL Streaming Replication”

  1. Can you share the final yaml file of stand-by instance which is mentioned in this step:

    Delete the pod izdb-standby and replace the contents of izdb-standby.yaml with following contents

  2. does not work for me when launching primary with configuration files, here is the error:
    initdb: error: directory “/var/lib/postgresql/data” exists but is not empty

    Any idea ?

    Best regards

    1. The location of the data directory depends on the distribution. Can you check if the data directory exists in any of /usr/local/pgsql/data or /var/lib/pgsql/data or /var/lib/postgresql/[version]/data/ and update the scripts accordingly.

Leave a Reply

Your email address will not be published. Required fields are marked *

Other Blog Posts

Other Blog Posts

MuleSoft Runtime Code Scanning – Why Do You Need It?

One of the most frequently asked questions is if we have static code analysis and a well defined DevOps process, why would we need run time code analysis? In this article, let’s explore the differences between the two and why you might want to have runtime code analysis (and IZ Runtime Analyzer) even if you have …

Read more

Ensuring Software Quality in Healthcare: Leveraging IZ Analyzer for MuleSoft Code Scanning 🏥💻

Ensuring software quality in the healthcare industry is a top priority, with direct implications for patient safety, data security, and regulatory compliance. Healthcare software development requires adherence to specific rules and best practices to meet the unique challenges of the industry. In this blog post, we will explore essential software quality rules specific to healthcare …

Read more

Mule OWASAP API Security Top 10 – Broken Object Level Authorization

In Mule, Object-Level Authorization refers to the process of controlling access to specific objects or resources within an application based on the permissions of the authenticated user. It ensures that users can only perform operations on objects for which they have appropriate authorization. To demonstrate a broken Object-Level Authorization example in Mule, let’s consider a …

Read more

How KongZap Revolutionises Kong Gateway Deployment

In a rapidly evolving digital landscape, businesses face numerous challenges. Faster time to market is the only option business can choose. When it comes end to end Kong Gateway life cycle from deploying to managing Kong Gateway, every one of these challenges is applicable. However, KongZap, a groundbreaking solution is a game-changer by addressing some …

Read more