pgEdge Distributed Postgres (VM Edition - Containers)
Tutorial: Deploying pgEdge Distributed Postgres with kind and a Helm Chart

Tutorial: Deploying pgEdge Distributed Postgres with kind and a Helm Chart

You can deploy a fully distributed, multi-master pgEdge Distributed Postgres cluster easier than ever with kind (opens in a new tab) and a Helm Chart from pgEdge. The pgedge-helm repository contains Helm Charts, makefiles, and scripts that deploy Postgres in a sample cluster as a StatefulSet. A StatefulSet is used to describe an installation that requires properties such as:

  • persistent storage
  • stable network identities
  • ordered deployment
  • scaling

In this tutorial, we'll walk you through using the information in the pgedge-helm repo to deploy a simple but flexible pre-defined cluster topology with Helm. The topology we create is useful for evaluations and to manage simple workflows, and can act as a starting point for creating a more flexible topology.

The commands in the deployment example that follow call commands defined in this Makefile (opens in a new tab). The Makefile provides shortcuts to simplify Postgres deployment; examine the file to see the specific helm commands invoked when you use sample commands.

Prerequisites:

Before starting, you should install:

  • Docker Desktop
  • kind
  • kubectl
  • make

If you decide to deploy a multi-zone cluster with the repo content, you'll also need:

  • Cilium CLI
  • subctl

After installing the prerequisites, clone the pgEdge/pgedge-helm repository (opens in a new tab). The command is:

git clone https://github.com/pgEdge/pgedge-helm.git

Then, navigate into the pgedge-helm/examples repository:

cd pgedge-helm/examples

The commands in the deployment example call shortcuts defined in this Makefile (opens in a new tab). The Makefile simplifies Postgres deployment; examine the Makefile to see the specific helm commands invoked when you use sample commands.

Then, use make commands to deploy the defined cluster with kind. To create a local Kubernetes cluster with two pods, use the commands:

make single-up make single-install

You can monitor the deployment progress with the command:

kubectl get pods

Once deployed, the kubectl get pods command returns a description of your cluster along with pod names (in the NAME column) and the pod STATUS:

NAME       READY   STATUS    RESTARTS   AGE
pgedge-0   1/1     Running   0          6m19s
pgedge-1   1/1     Running   0          6m19s```

When the deployment is up and running, you will have two pods; you can connect to each pod with psql with the following commands. To connect to pgedge-0 (node 1):

kubectl --context kind-single exec -it pgedge-0 -- psql -U app defaultdb

psql (16.10)
Type "help" for help.
 
defaultdb=>

To connect to pgedge-1 (node 2) with psql use the command:

kubectl --context kind-single exec -it pgedge-1 -- psql -U app defaultdb

To simplify experimentation, connect to each pod in a different terminal window. Then, in pgedge-0, create a table:

CREATE TABLE public.users (id uuid default gen_random_uuid(), name text, primary key(id));

defaultdb=> CREATE TABLE public.users (id uuid default gen_random_uuid(), name text, primary key(id));
INFO:  DDL statement replicated.
CREATE TABLE

Verify that the public.users table was created on both pods with the psql meta command:

\d

defaultdb=> \d
                 List of relations
 Schema |          Name           | Type  | Owner  
--------+-------------------------+-------+--------
 public | pg_stat_statements      | view  | pgedge
 public | pg_stat_statements_info | view  | pgedge
 public | users                   | table | app
(3 rows)

You can test replication by adding rows to pgedge-0; this command adds 3 rows:

INSERT INTO users (name) VALUES ('Alice'),('Bob'),('Carol');

Confirm that the data was inserted correctly in pgedge-1, then do the same in pgedge-0:

SELECT * FROM users;

defaultdb=> SELECT * FROM users;
                  id                  |   name    
--------------------------------------+-----------
 073f865b-6acb-4c0a-be06-fe1eec0686be | Alice
 cfdb60e1-f50b-4ebb-b4d3-0a1e9ac10a83 | Bob
 438132cf-b3de-4f39-8089-51857d1e5b9e | Carol
(3 rows)

To exit psql, use the command:

\q

Clean Up

To remove the cluster, use the following command:

kind delete cluster --name $(kubectl config current-context | sed 's/^kind-//')

Deleting cluster "single" ...
Deleted nodes: ["single-control-plane" "single-worker" "single-worker2"]

Adding the Active Consistency Engine to your Pods

A powerful feature supported by pgEdge Distributed Postgres is the Active Consistency Engine (ACE). ACE is designed to detect and repair data drift across replicated nodes. It quickly and efficiently detects and repairs any data divergence that results from exceptions or infrastructure failures.

The following steps add ACE to your cluster. To install the ACE pod on the Kubernetes cluster, first spin up a cluster, and then add a pod runnning ACE:

kubectl apply -f ace/ace.yaml

You can check the cluster status with the command:

kubectl get pods

pod/ace configured
NAME       READY   STATUS    RESTARTS   AGE
ace        1/1     Running   0          118s
pgedge-0   1/1     Running   0          22m
pgedge-1   1/1     Running   0          22m

Next, we're going to start a bash shell inside of the ACE pod:

kubectl exec -it ace -- /bin/bash

Defaulted container "ace" out of: ace, merge-files (init)
bash-5.1$ 

Then, use ACE to find differences in tables:

./ace table-diff defaultdb public.users

 Cluster defaultdb exists
 Connections successful to nodes in cluster
 Table public.users is comparable across nodes
Estimated row count: 3
Getting primary key offsets for table...
Starting jobs to compare tables...
 
 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3/3  [ 0:00:00 < 0:00:00 , ? it/s ]
 TABLES MATCH OK
 
TOTAL ROWS CHECKED = 6
RUN TIME = 0.20 seconds

ACE confirms that our replication is working and our tables match.

ACE can examine and repair tables with millions of rows efficiently and flexibly, and can even be run automatically to monitor and repair conflicts. For more information about ACE, review the online docs.

Customizing a Container Deployment

You can use your choice of editor to review and modify the values.yaml file, specifying values that describe your cluster properties in key/value pairs. Please note that the values provided in the sample file are designed to work with the Makefile and scripts shared in the pgedge-helm repository; modifying some properties may require changes elsewhere within scripts.

Updating the values.yaml File

The first section of the values.yaml file describes properties that are applied globally to each pod:

KeyTypeDefaultDescription
annotationsobject{}Additional annotations to apply to all created objects.
global.clusterDomainstring"cluster.local"Set to the cluster's domain if the cluster uses a custom domain.
labelsobject{}Additional labels to apply to all created objects.

The pgEdge section of the values.yaml file contains properties that define your Postgres installation and cluster deployment:

KeyTypeDefaultDescription
pgEdge.appNamestring"pgedge"Determines the name of the pgEdge StatefulSet and the app.kubernetes.io/name label. Must be 26 characters or less.
pgEdge.dbSpec.dbNamestring"defaultdb"The name of your database.
pgEdge.dbSpec.nodeslist[]Used to override the nodes in the generated db spec. This can be useful in multi-cluster setups, like the included multi-cluster example.
pgEdge.dbSpec.optionslist["autoddl:enabled"]Options for the database.
pgEdge.dbSpec.userslist[{"service":"postgres", "superuser":false, "type":"application", "username":"app"}, {"service":"postgres", "superuser":true, "type":"admin", "username":"admin"}]Database users created during deployment.
pgEdge.existingUsersSecretstring""The name of a users secret in the release namespace. If not specified, a new secret will generate random passwords for each user and store them in a new secret. See the pgedge-docker README for the format of this secret: https://github.com/pgEdge/pgedge-docker?tab=readme-ov-file#database-configuration (opens in a new tab)
pgEdge.extraMatchLabelsobject{}Specify additional labels to be used in the StatefulSet, Service, and other selectors.
pgEdge.imageTagstring"pg16-latest"Set a custom image tag from the docker.io/pgedge/pgedge repository.
pgEdge.livenessProbe.enabledbooltrue
pgEdge.livenessProbe.failureThresholdint6
pgEdge.livenessProbe.initialDelaySecondsint30
pgEdge.livenessProbe.periodSecondsint10
pgEdge.livenessProbe.successThresholdint1
pgEdge.livenessProbe.timeoutSecondsint5
pgEdge.nodeAffinityobject{}
pgEdge.nodeCountint3Specifies the number of pods in the pgEdge StatefulSet.
pgEdge.pdb.createboolfalseEnables the creation of a PodDisruptionBudget for pgEdge.
pgEdge.pdb.maxUnavailablestring""
pgEdge.pdb.minAvailableint1
pgEdge.podAffinityobject{}
pgEdge.podAntiAffinityEnabledbooltrueDisable the default pod anti-affinity. By default, this chart uses a preferredDuringSchedulingIgnoredDuringExecution anti-affinity to spread the replicas across different nodes if possible.
pgEdge.podAntiAffinityOverrideobject{}Override the default pod anti-affinity.
pgEdge.podManagementPolicystring"Parallel"Sets how pods are created during the initial scale up. Parallel results in a faster cluster initialization.
pgEdge.portint5432
pgEdge.readinessProbe.enabledbooltrue
pgEdge.readinessProbe.failureThresholdint6
pgEdge.readinessProbe.initialDelaySecondsint5
pgEdge.readinessProbe.periodSecondsint5
pgEdge.readinessProbe.successThresholdint1
pgEdge.readinessProbe.timeoutSecondsint5
pgEdge.resourcesobject{}Set resource requests and limits. There are none by default.
pgEdge.terminationGracePeriodSecondsint10

The service section of the values.yaml file describes the properties that describe your preferences for the service deployment:

KeyTypeDefaultDescription
service.annotationsobject{}Additional annotations to apply to the Service.
service.clusterIPstring""
service.externalTrafficPolicystring"Cluster"
service.loadBalancerIPstring""
service.loadBalancerSourceRangeslist[]
service.namestring"pgedge"The name of the Service created by this chart.
service.sessionAffinitystring"None"
service.sessionAffinityConfigobject{}
service.typestring"ClusterIP"
storage.accessModes[0]string"ReadWriteOnce"
storage.annotationsobject{}
storage.classNamestring"standard"
storage.labelsobject{}
storage.retentionPolicy.enabledboolfalse
storage.retentionPolicy.whenDeletedstring"Retain"
storage.retentionPolicy.whenScaledstring"Retain"
storage.selectorobject{}
storage.sizestring"8Gi"