|
| 1 | +--- |
| 2 | +title: CloudNativePG |
| 3 | +description: | |
| 4 | + Guide on integrating kube-bind with CloudNativePG for automated Postgres database management. |
| 5 | +weight: 30 |
| 6 | +--- |
| 7 | + |
| 8 | +# CloudNativePG Integration |
| 9 | + |
| 10 | +This document shows how the [CloudNativePG](https://cloudnative-pg.io/) Postgres database operator |
| 11 | +can be integrated and provided using kube-bind. |
| 12 | + |
| 13 | +## Setup |
| 14 | + |
| 15 | +The following sections will guide you through the one-time setup that is required for providing |
| 16 | +Postgres databases using CloudNativePG and kube-bind. |
| 17 | + |
| 18 | +### Install CloudNativePG |
| 19 | + |
| 20 | +Install CloudNativePG in your Kubernetes cluster, where kube-bind backend is running, if you |
| 21 | +haven't already. Follow the [official installation guide](https://cloudnative-pg.io/docs/1.28/installation_upgrade) |
| 22 | +in the CloudNativePG documentation. In its simplest form, the installation consists of applying this |
| 23 | +manifest: |
| 24 | + |
| 25 | +```bash |
| 26 | +kubectl apply \ |
| 27 | + --server-side \ |
| 28 | + --filename https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.28/releases/cnpg-1.28.0.yaml |
| 29 | +``` |
| 30 | + |
| 31 | +### Export the Cluster CRD |
| 32 | + |
| 33 | +To export the `Cluster` CRD in the provider cluster, add the kube-bind export label to it: |
| 34 | + |
| 35 | +```bash |
| 36 | +kubectl label crd clusters.postgresql.cnpg.io kube-bind.io/exported=true --overwrite |
| 37 | +``` |
| 38 | + |
| 39 | +### Create a APIServiceExportTemplate |
| 40 | + |
| 41 | +It's now time to configure kube-bind to export the `clusters` resource. To do so, create a |
| 42 | +kube-bind `APIServiceExportTemplate` in the provider cluster like this one: |
| 43 | + |
| 44 | +```yaml |
| 45 | +kubectl apply -f - <<EOF |
| 46 | +apiVersion: kube-bind.io/v1alpha2 |
| 47 | +kind: APIServiceExportTemplate |
| 48 | +metadata: |
| 49 | + labels: |
| 50 | + provider: cloudnativepg |
| 51 | + name: pg-clusters |
| 52 | +spec: |
| 53 | + permissionClaims: |
| 54 | + - group: "" |
| 55 | + resource: secrets |
| 56 | + selector: |
| 57 | + references: |
| 58 | + - resource: clusters |
| 59 | + group: postgresql.cnpg.io |
| 60 | + jsonPath: |
| 61 | + name: 'spec.bootstrap.initdb.secret.name' |
| 62 | + resources: |
| 63 | + - group: postgresql.cnpg.io |
| 64 | + resource: clusters |
| 65 | + versions: |
| 66 | + - v1 |
| 67 | + scope: Namespaced |
| 68 | +EOF |
| 69 | +``` |
| 70 | + |
| 71 | +## Usage |
| 72 | + |
| 73 | +Now that everything is set up, users can begin to bind to your backend and begin consuming the new |
| 74 | +API. |
| 75 | + |
| 76 | +### Login to kube-bind |
| 77 | + |
| 78 | +```bash |
| 79 | +kubectl bind login https://kube-bind.example.com |
| 80 | +``` |
| 81 | + |
| 82 | +### Request a Binding |
| 83 | + |
| 84 | +Request a binding to the `pg-clusters` template created above. This will allow you to create |
| 85 | +`Database` objects in your consumer cluster. |
| 86 | + |
| 87 | +**NB::** Make sure you've set your `KUBECONFIG` to the consumer cluster. |
| 88 | + |
| 89 | +```bash |
| 90 | +# you will get redirected to UI to authenticate and pick the template |
| 91 | +kubectl bind |
| 92 | +``` |
| 93 | + |
| 94 | +!!! note |
| 95 | + If you're using one of the [dev environments](../../developers/dev-environment/index.md), you |
| 96 | + might need to add additional arguments to the `bind` command, like `--konnector-host-alias`. |
| 97 | + |
| 98 | +### Wait for the Binding to be Established |
| 99 | + |
| 100 | +Once the binding is active, you can create `Cluster` object in your consumer cluster, |
| 101 | +and you will get `Cluster` objects synced from the provider cluster. |
| 102 | + |
| 103 | +```bash |
| 104 | +kubectl bind |
| 105 | +🌐 Opening kube-bind UI in your browser... |
| 106 | + https://kube-bind.example.com?redirect_url=.... |
| 107 | + |
| 108 | +Browser opened successfully |
| 109 | +Waiting for binding completion from UI... |
| 110 | + (Press Ctrl+C to cancel) |
| 111 | + |
| 112 | +Binding completed successfully! |
| 113 | +🔒 Updated secret kube-bind/kubeconfig-zxrdn for host https://kube-bind.example.com, namespace kube-bind-bp52k |
| 114 | +🚀 Deploying konnector v0.6.0 to namespace kube-bind. |
| 115 | +✅ Created APIServiceBinding pg-clusters-pk5c8 for 1 resources |
| 116 | +Created 1 APIServiceBinding(s): |
| 117 | + - pg-clusters-pk5c8 |
| 118 | +Resources bound successfully! |
| 119 | +``` |
| 120 | + |
| 121 | +### Create a Managed Database |
| 122 | + |
| 123 | +Verify that a `clusters.postgresql.cnpg.io` CRD is synced to the consumer cluster: |
| 124 | + |
| 125 | +```bash |
| 126 | +k get crd clusters.postgresql.cnpg.io |
| 127 | +NAME CREATED AT |
| 128 | +clusters.postgresql.cnpg.io 2025-11-27T14:22:18Z |
| 129 | +``` |
| 130 | + |
| 131 | +Order a new consumer database instance by creating a Postgres cluster. We need to provide our own |
| 132 | +credentials, otherwise the automatically generated credentials on the provider cluster will be |
| 133 | +inaccessible to consumers. |
| 134 | + |
| 135 | +```yaml |
| 136 | +kubectl apply -f - <<EOF |
| 137 | +apiVerson: v1 |
| 138 | +kind: Secret |
| 139 | +metadata: |
| 140 | + name: cluster-example-app-credentials |
| 141 | +data: |
| 142 | + username: bXktYXBwbGljYXRpb24= |
| 143 | + password: c3VwZXItc2VjcjN0LXBhc3N3MHJk |
| 144 | + |
| 145 | +--- |
| 146 | +apiVersion: postgresql.cnpg.io/v1 |
| 147 | +kind: Cluster |
| 148 | +metadata: |
| 149 | + name: cluster-example |
| 150 | +spec: |
| 151 | + instances: 3 |
| 152 | + |
| 153 | + storage: |
| 154 | + size: 1Gi |
| 155 | + |
| 156 | + bootstrap: |
| 157 | + initdb: |
| 158 | + secret: |
| 159 | + name: cluster-example-app-credentials |
| 160 | +EOF |
| 161 | +``` |
| 162 | + |
| 163 | +### Wait for Provisioning |
| 164 | + |
| 165 | +The kube-bind konnector and the CloudNativePG operator should now be busy provisioning your |
| 166 | +database. You can observe the provisioned database and connection Secret in the provider cluster: |
| 167 | + |
| 168 | +```bash |
| 169 | +kubectl -n kube-bind-xbrxn-default get clusters |
| 170 | + |
| 171 | +NAME AGE INSTANCES READY STATUS PRIMARY |
| 172 | +cluster-example 22m 3 3 Cluster in healthy state cluster-example-1 |
| 173 | +``` |
| 174 | + |
| 175 | +```bash |
| 176 | +kubectl -n kube-bind-xbrxn-default get clusters cluster-example -o yaml |
| 177 | +``` |
| 178 | + |
| 179 | +```yaml |
| 180 | +apiVersion: postgresql.cnpg.io/v1 |
| 181 | +kind: Cluster |
| 182 | +metadata: |
| 183 | + annotations: |
| 184 | + kube-bind.io/cluster-namespace: kube-bind-xbrxn |
| 185 | + kubectl.kubernetes.io/last-applied-configuration: | |
| 186 | + {"apiVersion":"postgresql.cnpg.io/v1","kind":"Cluster","metadata":{"annotations":{},"name":"cluster-example","namespace":"default"},"spec":{"bootstrap":{"initdb":{"secret":{"name":"cluster-example-app-credentials"}}},"instances":3,"storage":{"size":"1Gi"}}} |
| 187 | + creationTimestamp: "2026-01-22T10:13:57Z" |
| 188 | + generation: 1 |
| 189 | + name: cluster-example |
| 190 | + namespace: kube-bind-xbrxn-default |
| 191 | + resourceVersion: "4329" |
| 192 | + uid: a8949c70-c3b7-417f-9191-fb43cbd2d667 |
| 193 | +spec: |
| 194 | + affinity: |
| 195 | + podAntiAffinityType: preferred |
| 196 | + bootstrap: |
| 197 | + initdb: |
| 198 | + database: app |
| 199 | + encoding: UTF8 |
| 200 | + localeCType: C |
| 201 | + localeCollate: C |
| 202 | + owner: app |
| 203 | + secret: |
| 204 | + name: cluster-example-app-credentials |
| 205 | + enablePDB: true |
| 206 | + enableSuperuserAccess: false |
| 207 | + failoverDelay: 0 |
| 208 | + imageName: ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie |
| 209 | + instances: 3 |
| 210 | + logLevel: info |
| 211 | + maxSyncReplicas: 0 |
| 212 | + minSyncReplicas: 0 |
| 213 | + monitoring: |
| 214 | + customQueriesConfigMap: |
| 215 | + - key: queries |
| 216 | + name: cnpg-default-monitoring |
| 217 | + disableDefaultQueries: false |
| 218 | + enablePodMonitor: false |
| 219 | + postgresGID: 26 |
| 220 | + postgresUID: 26 |
| 221 | + postgresql: |
| 222 | + parameters: |
| 223 | + archive_mode: "on" |
| 224 | + archive_timeout: 5min |
| 225 | + dynamic_shared_memory_type: posix |
| 226 | + full_page_writes: "on" |
| 227 | + log_destination: csvlog |
| 228 | + log_directory: /controller/log |
| 229 | + log_filename: postgres |
| 230 | + log_rotation_age: "0" |
| 231 | + log_rotation_size: "0" |
| 232 | + log_truncate_on_rotation: "false" |
| 233 | + logging_collector: "on" |
| 234 | + max_parallel_workers: "32" |
| 235 | + max_replication_slots: "32" |
| 236 | + max_worker_processes: "32" |
| 237 | + shared_memory_type: mmap |
| 238 | + shared_preload_libraries: "" |
| 239 | + ssl_max_protocol_version: TLSv1.3 |
| 240 | + ssl_min_protocol_version: TLSv1.3 |
| 241 | + wal_keep_size: 512MB |
| 242 | + wal_level: logical |
| 243 | + wal_log_hints: "on" |
| 244 | + wal_receiver_timeout: 5s |
| 245 | + wal_sender_timeout: 5s |
| 246 | + syncReplicaElectionConstraint: |
| 247 | + enabled: false |
| 248 | + primaryUpdateMethod: restart |
| 249 | + primaryUpdateStrategy: unsupervised |
| 250 | + probes: |
| 251 | + liveness: |
| 252 | + isolationCheck: |
| 253 | + connectionTimeout: 1000 |
| 254 | + enabled: true |
| 255 | + requestTimeout: 1000 |
| 256 | + replicationSlots: |
| 257 | + highAvailability: |
| 258 | + enabled: true |
| 259 | + slotPrefix: _cnpg_ |
| 260 | + synchronizeReplicas: |
| 261 | + enabled: true |
| 262 | + updateInterval: 30 |
| 263 | + resources: {} |
| 264 | + smartShutdownTimeout: 180 |
| 265 | + startDelay: 3600 |
| 266 | + stopDelay: 1800 |
| 267 | + storage: |
| 268 | + resizeInUseVolumes: true |
| 269 | + size: 1Gi |
| 270 | + switchoverDelay: 3600 |
| 271 | +status: |
| 272 | + availableArchitectures: |
| 273 | + - goArch: amd64 |
| 274 | + hash: 527e2e3b680dfba7f7578b98530f755f36156e7be00acda3318ef36fe4f4418f |
| 275 | + - goArch: arm64 |
| 276 | + hash: aaa74c6061f3fe30f230a664b4f6076bb886c9a89d4eba3293483525c5cff533 |
| 277 | + certificates: |
| 278 | + clientCASecret: cluster-example-ca |
| 279 | + expirations: |
| 280 | + cluster-example-ca: 2026-04-22 10:08:57 +0000 UTC |
| 281 | + cluster-example-replication: 2026-04-22 10:08:57 +0000 UTC |
| 282 | + cluster-example-server: 2026-04-22 10:08:57 +0000 UTC |
| 283 | + replicationTLSSecret: cluster-example-replication |
| 284 | + serverAltDNSNames: |
| 285 | + - cluster-example-rw |
| 286 | + - cluster-example-rw.kube-bind-xbrxn-default |
| 287 | + - cluster-example-rw.kube-bind-xbrxn-default.svc |
| 288 | + - cluster-example-rw.kube-bind-xbrxn-default.svc.cluster.local |
| 289 | + - cluster-example-r |
| 290 | + - cluster-example-r.kube-bind-xbrxn-default |
| 291 | + - cluster-example-r.kube-bind-xbrxn-default.svc |
| 292 | + - cluster-example-r.kube-bind-xbrxn-default.svc.cluster.local |
| 293 | + - cluster-example-ro |
| 294 | + - cluster-example-ro.kube-bind-xbrxn-default |
| 295 | + - cluster-example-ro.kube-bind-xbrxn-default.svc |
| 296 | + - cluster-example-ro.kube-bind-xbrxn-default.svc.cluster.local |
| 297 | + serverCASecret: cluster-example-ca |
| 298 | + serverTLSSecret: cluster-example-server |
| 299 | + cloudNativePGCommitHash: a9696201f |
| 300 | + cloudNativePGOperatorHash: 527e2e3b680dfba7f7578b98530f755f36156e7be00acda3318ef36fe4f4418f |
| 301 | + conditions: |
| 302 | + - lastTransitionTime: "2026-01-22T10:30:08Z" |
| 303 | + message: A single, unique system ID was found across reporting instances. |
| 304 | + reason: Unique |
| 305 | + status: "True" |
| 306 | + type: ConsistentSystemID |
| 307 | + - lastTransitionTime: "2026-01-22T10:31:06Z" |
| 308 | + message: Cluster is Ready |
| 309 | + reason: ClusterIsReady |
| 310 | + status: "True" |
| 311 | + type: Ready |
| 312 | + - lastTransitionTime: "2026-01-22T10:30:08Z" |
| 313 | + message: Continuous archiving is working |
| 314 | + reason: ContinuousArchivingSuccess |
| 315 | + status: "True" |
| 316 | + type: ContinuousArchiving |
| 317 | + configMapResourceVersion: |
| 318 | + metrics: |
| 319 | + cnpg-default-monitoring: "2120" |
| 320 | + currentPrimary: cluster-example-1 |
| 321 | + currentPrimaryTimestamp: "2026-01-22T10:30:07.986099Z" |
| 322 | + healthyPVC: |
| 323 | + - cluster-example-1 |
| 324 | + - cluster-example-2 |
| 325 | + - cluster-example-3 |
| 326 | + image: ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie |
| 327 | + instanceNames: |
| 328 | + - cluster-example-1 |
| 329 | + - cluster-example-2 |
| 330 | + - cluster-example-3 |
| 331 | + instances: 3 |
| 332 | + instancesReportedState: |
| 333 | + cluster-example-1: |
| 334 | + ip: 10.244.0.21 |
| 335 | + isPrimary: true |
| 336 | + timeLineID: 1 |
| 337 | + cluster-example-2: |
| 338 | + ip: 10.244.0.24 |
| 339 | + isPrimary: false |
| 340 | + timeLineID: 1 |
| 341 | + cluster-example-3: |
| 342 | + ip: 10.244.0.27 |
| 343 | + isPrimary: false |
| 344 | + timeLineID: 1 |
| 345 | + instancesStatus: |
| 346 | + healthy: |
| 347 | + - cluster-example-1 |
| 348 | + - cluster-example-2 |
| 349 | + - cluster-example-3 |
| 350 | + latestGeneratedNode: 3 |
| 351 | + managedRolesStatus: {} |
| 352 | + pgDataImageInfo: |
| 353 | + image: ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie |
| 354 | + majorVersion: 18 |
| 355 | + phase: Cluster in healthy state |
| 356 | + poolerIntegrations: |
| 357 | + pgBouncerIntegration: {} |
| 358 | + pvcCount: 3 |
| 359 | + readService: cluster-example-r |
| 360 | + readyInstances: 3 |
| 361 | + secretsResourceVersion: |
| 362 | + applicationSecretVersion: "3969" |
| 363 | + clientCaSecretVersion: "2089" |
| 364 | + replicationSecretVersion: "2092" |
| 365 | + serverCaSecretVersion: "2089" |
| 366 | + serverSecretVersion: "2090" |
| 367 | + switchReplicaClusterStatus: {} |
| 368 | + systemID: "7598131281368047651" |
| 369 | + targetPrimary: cluster-example-1 |
| 370 | + targetPrimaryTimestamp: "2026-01-22T10:13:58.221403Z" |
| 371 | + timelineID: 1 |
| 372 | + topology: |
| 373 | + instances: |
| 374 | + cluster-example-1: {} |
| 375 | + cluster-example-2: {} |
| 376 | + cluster-example-3: {} |
| 377 | + nodesUsed: 1 |
| 378 | + successfullyExtracted: true |
| 379 | + writeService: cluster-example-rw |
| 380 | +``` |
| 381 | +
|
| 382 | +--- |
| 383 | +
|
| 384 | +For troubleshooting and more information, check the [kube-bind documentation](https://kube-bind.io/docs/). |
0 commit comments