This option didn’t seem super widely documented from my initial searches online; it should be able to basically enforce layer 4 ingress/firewall rules at the individual service level. This is a quick test to check if it works.
Steps were generated with ChatGPT, and mostly worked. It missed Azure provider registration, but I figure that out easily from the Azure error message. GPT was creating the VMs after the CNPG cluster … I had to reverse that so I’d know the IP for loadBalancerSourceRanges. I had to switch the VMs to the “westus” region because of quota limits. There were a couple more tweaks but overall I got this done in an hour or two – couldn’t have done that with just google.
I used an Azure free account; never had to set up a credit card with Azure. Note that free accounts are limited to 30 days and only available for new users. This blog post happened to be the first time I’d used azure with my personal account so it qualified. I’ll need to use a pay-as-you-go account in the future.
Step 1: Create an AKS Cluster
Create resource group
az group create --name myResourceGroup --location eastus
{
"id": "/subscriptions/7460cc93-ed07-42e7-a246-3b87e52a3ad7/resourceGroups/myResourceGroup",
"location": "eastus",
"managedBy": null,
"name": "myResourceGroup",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null,
"type": "Microsoft.Resources/resourceGroups"
}
Register the provider
az provider register --namespace Microsoft.ContainerService
Registering is still on-going. You can monitor using 'az provider show -n Microsoft.ContainerService'
Create AKS cluster
az aks create \
--resource-group myResourceGroup \
--name myAKSCluster \
--network-plugin kubenet \
--network-policy calico \
--load-balancer-sku standard \
--node-count 2 \
--enable-managed-identity \
--generate-ssh-keys
{
"aadProfile": null,
"addonProfiles": null,
"agentPoolProfiles": [
{
"availabilityZones": null,
"capacityReservationGroupId": null,
"count": 2,
...
"upgradeSettings": null,
"windowsProfile": null,
"workloadAutoScalerProfile": {
"keda": null,
"verticalPodAutoscaler": null
}
}
get cluster credentials
az aks get-credentials --resource-group myResourceGroup --name myAKSCluster
Merged "myAKSCluster" as current context in /home/jeremy/code/cnpg-playground/k8s/kube-config.yaml
Step 2: Install CloudNativePG (CNPG)
Installing CNPG with helm
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update
helm install cnpg cnpg/cloudnative-pg --namespace cnpg-system --create-namespace
NAME: cnpg
LAST DEPLOYED: Fri Mar 14 00:01:16 2025
NAMESPACE: cnpg-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CloudNativePG operator should be installed in namespace "cnpg-system".
You can now create a PostgreSQL cluster with 3 nodes as follows:
cat <<EOF | kubectl apply -f -
# Example of PostgreSQL cluster
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example
spec:
instances: 3
storage:
size: 1Gi
EOF
kubectl get -A cluster
Verifying install
kubectl get pods -n cnpg-system
NAME READY STATUS RESTARTS AGE
cnpg-cloudnative-pg-847b949f48-d4clp 1/1 Running 0 118s
Step 3: Create Client VMs
Create Ubuntu client VMs
az vm create \
--resource-group myResourceGroup \
--name VM1 \
--location westus \
--image Ubuntu2404 \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-address VM1PublicIP
{
"fqdns": "",
"id": "/subscriptions/7460cc93-ed07-42e7-a246-3b87e52a3ad7/resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/VM1",
"location": "westus",
"macAddress": "60-45-BD-09-BD-FA",
"powerState": "VM running",
"privateIpAddress": "10.0.0.4",
"publicIpAddress": "104.42.23.20",
"resourceGroup": "myResourceGroup",
"zones": ""
}
az vm create \
--resource-group myResourceGroup \
--name VM2 \
--location westus \
--image Ubuntu2404 \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-address VM2PublicIP
{
"fqdns": "",
"id": "/subscriptions/7460cc93-ed07-42e7-a246-3b87e52a3ad7/resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/VM2",
"location": "westus",
"macAddress": "00-0D-3A-33-8C-32",
"powerState": "VM running",
"privateIpAddress": "10.0.0.5",
"publicIpAddress": "104.42.13.179",
"resourceGroup": "myResourceGroup",
"zones": ""
}
Verify the public IPs
az vm list-ip-addresses --resource-group myResourceGroup --output table
VirtualMachine PublicIPAddresses PrivateIPAddresses
---------------- ------------------- --------------------
VM1 104.42.23.20 10.0.0.4
VM2 104.42.13.179 10.0.0.5
ssh azureuser@104.42.23.20
The authenticity of host '104.42.23.20 (104.42.23.20)' can't be established.
ED25519 key fingerprint is SHA256:glEquFeCDVHDsA8x6KK3E3yhkeIBwMwl1p+1Wq51AFw.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '104.42.23.20' (ED25519) to the list of known hosts.
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-1021-azure x86_64)
...
azureuser@VM1:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 24.04.2 LTS
Release: 24.04
Codename: noble
Step 4: Deploy CNPG cluster
generate and store the password
export POSTGRES_PASSWORD=$(openssl rand -base64 16) && echo "Generated Password: $POSTGRES_PASSWORD"
Generated Password: R0L0W056xZdykI/GoAW0lQ==
kubectl create secret generic mydb-secret \
--from-literal=username=myuser \
--from-literal=password=$POSTGRES_PASSWORD
secret/mydb-secret created
kubectl get secrets mydb-secret -o yaml
apiVersion: v1
data:
password: UjBMMFcwNTZ4WmR5a0kvR29BVzBsUT09
username: bXl1c2Vy
kind: Secret
metadata:
creationTimestamp: "2025-03-14T07:35:06Z"
name: mydb-secret
namespace: default
resourceVersion: "13805"
uid: f62e732a-95de-4b54-be9e-734db5dedb9e
type: Opaque
create the cluster
cat >cnpg-cluster.yaml <<EOF
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cnpg-cluster
spec:
instances: 2
imageName: ghcr.io/cloudnative-pg/postgresql:17
storage:
size: 1Gi
bootstrap:
initdb:
database: mydb
owner: myuser
secret:
name: mydb-secret
postgresql:
synchronous:
method: any
number: 1
dataDurability: preferred
managed:
services:
## disable the default services
disabledDefaultServices: ["ro", "r"]
additional:
- selectorType: rw
serviceTemplate:
metadata:
name: "test-rw"
spec:
type: LoadBalancer
loadBalancerSourceRanges:
- 104.42.23.20/32 # VM1's public IP
EOF
kubectl apply -f cnpg-cluster.yaml
cluster.postgresql.cnpg.io/cnpg-cluster created
kubectl cnpg status cnpg-cluster
Cluster Summary
Name default/cnpg-cluster
System ID: 7481566161465909268
PostgreSQL Image: ghcr.io/cloudnative-pg/postgresql:17
Primary instance: cnpg-cluster-1
Primary start time: 2025-03-14 07:38:01 +0000 UTC (uptime 4m17s)
Status: Cluster in healthy state
Instances: 2
Ready instances: 2
Size: 94M
Current Write LSN: 0/4055A58 (Timeline: 1 - WAL File: 000000010000000000000004)
Continuous Backup status
Not configured
Streaming Replication status
Replication Slots Enabled
Name Sent LSN Write LSN Flush LSN Replay LSN Write Lag Flush Lag Replay Lag State Sync State Sync Priority Replication Slot
---- -------- --------- --------- ---------- --------- --------- ---------- ----- ---------- ------------- ----------------
cnpg-cluster-2 0/4055A58 0/4055A58 0/4055A58 0/4055A58 00:00:00 00:00:00 00:00:00 streaming quorum 1 active
Instances status
Name Current LSN Replication role Status QoS Manager Version Node
---- ----------- ---------------- ------ --- --------------- ----
cnpg-cluster-1 0/4055A58 Primary OK BestEffort 1.25.1 aks-nodepool1-30726688-vmss000001
cnpg-cluster-2 0/4055A58 Standby (sync) OK BestEffort 1.25.1 aks-nodepool1-30726688-vmss000000
kubectl get svc test-rw
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
test-rw LoadBalancer 10.0.126.67 57.152.72.113 5432:31291/TCP 16m
Test Connectivity from VMs
Test from VM1
ssh azureuser@104.42.23.20
azureuser@VM1:~$ sudo apt update; sudo apt install postgresql-client-common postgresql-client-16
...
The following additional packages will be installed:
libpq5
Suggested packages:
postgresql-16 postgresql-doc-16
The following NEW packages will be installed:
libpq5 postgresql-client-16 postgresql-client-common
0 upgraded, 3 newly installed, 0 to remove and 43 not upgraded.
Need to get 1471 kB of archives.
After this operation, 4922 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
...
azureuser@VM1:~$ psql -h 57.152.72.113 -U myuser -d mydb
Password for user myuser:
psql (16.8 (Ubuntu 16.8-0ubuntu0.24.04.1), server 17.4 (Debian 17.4-1.pgdg110+2))
WARNING: psql major version 16, server major version 17.
Some psql features might not work.
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.
mydb=> select version();
version
-----------------------------------------------------------------------------------------------------------------------------
PostgreSQL 17.4 (Debian 17.4-1.pgdg110+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
(1 row)
mydb=> \q
azureuser@VM1:~$ exit
logout
Connection to 104.42.23.20 closed.
Test from VM2
ssh azureuser@104.42.13.179
azureuser@VM2:~$ sudo apt update; sudo apt install postgresql-client-common postgresql-client-16
...
The following additional packages will be installed:
libpq5
Suggested packages:
postgresql-16 postgresql-doc-16
The following NEW packages will be installed:
libpq5 postgresql-client-16 postgresql-client-common
0 upgraded, 3 newly installed, 0 to remove and 43 not upgraded.
Need to get 1471 kB of archives.
After this operation, 4922 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
...
azureuser@VM2:~$ time psql -h 57.152.72.113 -U myuser -d mydb
^Z
[1]+ Stopped psql -h 57.152.72.113 -U myuser -d mydb
real 0m40.947s
user 0m0.001s
sys 0m0.000s
azureuser@VM2:~$ exit
logout
Connection to 104.42.13.179 closed.
Clean up resources
Delete VMs
az vm delete --name VM1 --resource-group myResourceGroup --yes
az vm delete --name VM2 --resource-group myResourceGroup --yes
az disk delete --name VM1_OSDisk --resource-group myResourceGroup --yes
az disk delete --name VM2_OSDisk --resource-group myResourceGroup --yes
az network nic list --output table
AuxiliaryMode AuxiliarySku DisableTcpStateTracking EnableIPForwarding Location MacAddress Name NicType ProvisioningState ResourceGroup ResourceGuid VnetEncryptionSupported
--------------- -------------- ------------------------- -------------------- ---------- ----------------- -------- --------- ------------------- --------------- ------------------------------------ -------------------------
None None False False westus 60-45-BD-09-BD-FA VM1VMNic Standard Succeeded myResourceGroup 185c9f84-bef6-4f45-b53e-bd40a72dd3f8 False
None None False False westus 00-0D-3A-33-8C-32 VM2VMNic Standard Succeeded myResourceGroup 293a72b4-70ab-44ce-bc04-2dc42c2e0591 False
az network nic delete --name VM1VMNic --resource-group myResourceGroup
az network nic delete --name VM2VMNic --resource-group myResourceGroup
az network public-ip delete --name VM1PublicIP --resource-group myResourceGroup
az network public-ip delete --name VM2PublicIP --resource-group myResourceGroup
Delete AKS cluster
az aks delete --name myAKSCluster --resource-group myResourceGroup --yes
Delete resource group
az group delete --name myResourceGroup --yes --no-wait
Check they are gone
az aks list --output table
az vm list --output table
az group list --output table
Name Location Status
---------------- ---------- ---------
myResourceGroup eastus Deleting
NetworkWatcherRG eastus Succeeded
QED. Works as advertised.



Discussion
No comments yet.