How to Create a PostgreSQL Solution#
This guide walks through creating and publishing a PostgreSQL-as-a-service offering using CloudNativePG and Crossplane, making it available to consumers as a self-service catalogue entry.
Prerequisites#
infrastructure.stakater.comAPI available- CloudNativePG operator installed on the management cluster
kubernetes-provider,kcp-provider, andhelm-providerconfiguredkcp-api-syncagentHelm chart installed (see Publishing APIs)
Install CloudNativePG:
kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-1.24.0.yaml
Step 1: Define the XRD#
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresqldatabases.databases.cloud.stakater.com
spec:
scope: LegacyCluster
group: databases.cloud.stakater.com
names:
kind: XPostgreSQLDatabase
plural: xpostgresqldatabases
claimNames:
kind: PostgreSQLDatabase
plural: postgresqldatabases
versions:
- name: v1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
providerConfigsRef:
type: object
properties:
kubernetes:
type: string
default: kubernetes-provider
parameters:
type: object
required: [dbName]
properties:
dbName:
type: string
description: Name of the database instance
version:
type: string
default: "16"
enum: ["14", "15", "16"]
description: PostgreSQL major version
storageGb:
type: integer
default: 20
description: Storage in GiB
instances:
type: integer
default: 1
description: "1 = standalone, 3 = high availability"
status:
type: object
properties:
endpoint:
type: string
port:
type: integer
secretName:
type: string
kubectl apply -f xrd.yaml
Step 2: Write the Composition#
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
labels:
crossplane.io/xrd: xpostgresqldatabases.databases.cloud.stakater.com
name: postgresql-database
spec:
compositeTypeRef:
apiVersion: databases.cloud.stakater.com/v1
kind: XPostgreSQLDatabase
mode: Pipeline
pipeline:
- step: kcl
functionRef:
name: function-kcl
input:
apiVersion: krm.kcl.dev/v1alpha1
kind: KCLInput
spec:
source: |
oxr = option("params").oxr
ocds = option("params").ocds
spec = oxr.spec
parameters = spec.parameters
kubernetesProvider = spec?.providerConfigsRef?.kubernetes or "kubernetes-provider"
dbName = parameters.dbName
instances = parameters?.instances or 1
storageGb = parameters?.storageGb or 20
pgVersion = parameters?.version or "16"
targetNamespace = spec.claimRef.namespace
items = [
{
apiVersion: "kubernetes.crossplane.io/v1alpha2"
kind: "Object"
metadata: {
name: dbName
annotations: {
"krm.kcl.dev/composition-resource-name": "postgresql-cluster"
}
}
spec: {
providerConfigRef: {name: kubernetesProvider}
forProvider: {
manifest: {
apiVersion: "postgresql.cnpg.io/v1"
kind: "Cluster"
metadata: {
name: dbName
namespace: targetNamespace
}
spec: {
instances: instances
postgresql: {
parameters: {
max_connections: "200"
shared_buffers: "256MB"
}
}
bootstrap: {
initdb: {
database: dbName
owner: dbName
}
}
storage: {
size: "{}Gi".format(storageGb)
}
}
}
}
}
}
]
- step: automatically-detect-ready-composed-resources
functionRef:
name: function-auto-ready
kubectl apply -f composition.yaml
Step 3: Test the Composition#
apiVersion: databases.cloud.stakater.com/v1
kind: PostgreSQLDatabase
metadata:
name: test-postgres
spec:
parameters:
dbName: testapp
version: "16"
storageGb: 10
instances: 1
kubectl apply -f test-claim.yaml
kubectl get postgresqldatabase test-postgres -w
kubectl get postgresqldatabase test-postgres -o jsonpath='{.status}'
Remove the test claim before publishing:
kubectl delete postgresqldatabase test-postgres
Step 4: Publish the API#
apiVersion: infrastructure.stakater.com/v1alpha1
kind: PublishedOffering
metadata:
name: databases-service
spec:
providerConfigRef:
name: kubernetes-provider
kcpProviderConfigRef:
name: kcp-provider
helmProviderConfigRef:
name: helm-provider
parameters:
apiSyncagent:
namespace: kcp-system
exports:
- apiGroup: databases.cloud.stakater.com
resources:
- resource:
kind: PostgreSQLDatabase
plural: postgresqldatabases
version: v1
namespaceSuffix: postgresql
kubectl apply -f published-offering.yaml
kubectl get publishedoffering databases-service
Step 5: Verify Consumer Access#
export KUBECONFIG=~/.kube/consumer-project.yaml
kubectl api-resources | grep databases.cloud.stakater.com
Consumers can now provision PostgreSQL databases:
apiVersion: databases.cloud.stakater.com/v1
kind: PostgreSQLDatabase
metadata:
name: my-app-db
spec:
parameters:
dbName: myapp
version: "16"
storageGb: 50
instances: 3
What's Next?#
- Crossplane Compositions — Advanced composition patterns
- Publishing APIs — Full publishing guide
- Crossplane Integration — Provider setup and KCL function reference