From 21380e7909f064d1e14a274cc2604c92f5dd5293 Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 28 Oct 2025 19:37:58 +0100 Subject: [PATCH 01/21] feat(documentdb): Add db type documentdb Signed-off-by: P-Louw --- charts/cluster/examples/documentdb.yaml | 10 ++++++++++ charts/cluster/templates/_bootstrap.tpl | 5 ++++- charts/cluster/templates/_helpers.tpl | 2 ++ charts/cluster/templates/cluster.yaml | 6 +++++- charts/cluster/values.yaml | 5 +++++ 5 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 charts/cluster/examples/documentdb.yaml diff --git a/charts/cluster/examples/documentdb.yaml b/charts/cluster/examples/documentdb.yaml new file mode 100644 index 0000000000..835fe7970e --- /dev/null +++ b/charts/cluster/examples/documentdb.yaml @@ -0,0 +1,10 @@ +type: documentdb +mode: standalone +version: + postgresql: "17" + documentdb: "0.106.0" + ferretdb: "2.5.0" +cluster: + instances: 1 +backups: + enabled: false diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index 95bedd214f..8cd296f9b6 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -10,7 +10,7 @@ bootstrap: {{- if .Values.cluster.initdb.owner }} owner: {{ tpl .Values.cluster.initdb.owner . }} {{- end }} - {{- if or (eq .Values.type "postgis") (eq .Values.type "timescaledb") (not (empty .Values.cluster.initdb.postInitApplicationSQL)) }} + {{- if or (eq .Values.type "postgis") (eq .Values.type "timescaledb") (eq .Values.type "documentdb") (not (empty .Values.cluster.initdb.postInitApplicationSQL)) }} postInitApplicationSQL: {{- if eq .Values.type "postgis" }} - CREATE EXTENSION IF NOT EXISTS postgis; @@ -19,6 +19,9 @@ bootstrap: - CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; {{- else if eq .Values.type "timescaledb" }} - CREATE EXTENSION IF NOT EXISTS timescaledb; + {{- else if eq .Values.type "documentdb" }} + - CREATE EXTENSION IF NOT EXISTS pg_cron CASCADE; + - CREATE EXTENSION IF NOT EXISTS documentdb CASCADE; {{- end }} {{- with .Values.cluster.initdb }} {{- range .postInitApplicationSQL }} diff --git a/charts/cluster/templates/_helpers.tpl b/charts/cluster/templates/_helpers.tpl index 2bae419493..14f13a0d0a 100644 --- a/charts/cluster/templates/_helpers.tpl +++ b/charts/cluster/templates/_helpers.tpl @@ -87,6 +87,8 @@ If a custom imageName is available, use it, otherwise use the defaults based on {{- printf "ghcr.io/cloudnative-pg/postgresql:%s" .Values.version.postgresql -}} {{- else if eq .Values.type "postgis" -}} {{- printf "ghcr.io/cloudnative-pg/postgis:%s-%s" .Values.version.postgresql .Values.version.postgis -}} + {{- else if eq .Values.type "documentdb" -}} + {{- printf "ghcr.io/ferretdb/postgres-documentdb:%s-%s-ferretdb-%s" (include "cluster.postgresqlMajor" .) .Values.version.documentdb .Values.version.ferretdb -}} {{- else -}} {{ fail "Invalid cluster type!" }} {{- end }} diff --git a/charts/cluster/templates/cluster.yaml b/charts/cluster/templates/cluster.yaml index bb9ea770cc..d46bf3032d 100644 --- a/charts/cluster/templates/cluster.yaml +++ b/charts/cluster/templates/cluster.yaml @@ -68,10 +68,14 @@ spec: {{ end }} enablePDB: {{ .Values.cluster.enablePDB }} postgresql: - {{- if or (eq .Values.type "timescaledb") (not (empty .Values.cluster.postgresql.shared_preload_libraries)) }} + {{- if or (eq .Values.type "timescaledb") (eq .Values.type "documentdb") (not (empty .Values.cluster.postgresql.shared_preload_libraries)) }} shared_preload_libraries: {{- if eq .Values.type "timescaledb" }} - timescaledb + {{- else if eq .Values.type "documentdb" }} + - pg_cron + - pg_documentdb_core + - pg_documentdb {{- end }} {{- with .Values.cluster.postgresql.shared_preload_libraries }} {{- toYaml . | nindent 6 }} diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index 3d5f11dc1a..9b5943b226 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -10,6 +10,7 @@ namespaceOverride: "" # * `postgresql` # * `postgis` # * `timescaledb` +# * `documentdb` type: postgresql version: @@ -19,6 +20,10 @@ version: timescaledb: "2.15" # -- If using PostGIS, specify the version postgis: "3.4" + # -- If using DocumentDB, specify the version + documentdb: "0.106.0" + # -- If using DocumentDB, specify the FerretDB version + ferretdb: "2.5.0" ### # -- Cluster mode of operation. Available modes: From 95a658f87e5586db93e17b7a8c23faf2dbfa42a1 Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 28 Oct 2025 20:19:48 +0100 Subject: [PATCH 02/21] feat(documentdb): Add tests backups & client Signed-off-by: P-Louw --- .../00-minio_cleanup-assert.yaml | 6 + .../00-minio_cleanup.yaml | 16 ++ .../01-documentdb_cluster-assert.yaml | 6 + .../01-documentdb_cluster.yaml | 32 ++++ .../03-documentdb_test-assert.yaml | 6 + .../03-documentdb_test.yaml | 23 +++ .../03b-mongodb_client_test-assert.yaml | 6 + .../03b-mongodb_client_test.yaml | 99 ++++++++++++ .../04-data_write-assert.yaml | 6 + .../04-data_write.yaml | 54 +++++++ .../05-backup.yaml | 8 + .../05-backup_completed-assert.yaml | 10 ++ .../05-backup_running-assert.yaml | 10 ++ .../05-checkpoint.yaml | 27 ++++ .../06-post_backup_data_write-assert.yaml | 6 + .../06-post_backup_data_write.yaml | 27 ++++ ...7-recovery_backup_pitr_cluster-assert.yaml | 6 + .../07-recovery_backup_pitr_cluster.yaml | 48 ++++++ .../08-data_test-assert.yaml | 6 + .../08-data_test.yaml | 27 ++++ .../chainsaw-test.yaml | 144 ++++++++++++++++++ 21 files changed, 573 insertions(+) create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/00-minio_cleanup-assert.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/00-minio_cleanup.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster-assert.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/03-documentdb_test-assert.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/03-documentdb_test.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test-assert.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/04-data_write-assert.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/04-data_write.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/05-backup.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/05-backup_completed-assert.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/05-backup_running-assert.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/05-checkpoint.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/06-post_backup_data_write-assert.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/06-post_backup_data_write.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/08-data_test-assert.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/08-data_test.yaml create mode 100644 charts/cluster/test/documentdb-minio-backup-restore/chainsaw-test.yaml diff --git a/charts/cluster/test/documentdb-minio-backup-restore/00-minio_cleanup-assert.yaml b/charts/cluster/test/documentdb-minio-backup-restore/00-minio_cleanup-assert.yaml new file mode 100644 index 0000000000..9c0f3eb480 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/00-minio_cleanup-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: minio-cleanup +status: + succeeded: 1 diff --git a/charts/cluster/test/documentdb-minio-backup-restore/00-minio_cleanup.yaml b/charts/cluster/test/documentdb-minio-backup-restore/00-minio_cleanup.yaml new file mode 100644 index 0000000000..3dabf47c8a --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/00-minio_cleanup.yaml @@ -0,0 +1,16 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: minio-cleanup +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: minio-cleanup + image: minio/mc + command: ['sh', '-c'] + args: + - | + mc alias set myminio https://minio.minio.svc.cluster.local minio minio123 + mc rm --recursive --force myminio/mybucket/documentdb diff --git a/charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster-assert.yaml b/charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster-assert.yaml new file mode 100644 index 0000000000..4203a94d27 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: documentdb-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster.yaml b/charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster.yaml new file mode 100644 index 0000000000..7518783090 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster.yaml @@ -0,0 +1,32 @@ +type: documentdb +mode: standalone +version: + postgresql: "17" + documentdb: "0.106.0" + ferretdb: "2.5.0" + +cluster: + instances: 2 + storage: + size: 256Mi + +backups: + enabled: true + + provider: s3 + endpointURL: "https://minio.minio.svc.cluster.local" + endpointCA: + name: kube-root-ca.crt + key: ca.crt + wal: + encryption: "" + data: + encryption: "" + s3: + bucket: "mybucket" + path: "/documentdb/v1" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/documentdb-minio-backup-restore/03-documentdb_test-assert.yaml b/charts/cluster/test/documentdb-minio-backup-restore/03-documentdb_test-assert.yaml new file mode 100644 index 0000000000..bcfd4be76a --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/03-documentdb_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: documentdb-test +status: + succeeded: 1 diff --git a/charts/cluster/test/documentdb-minio-backup-restore/03-documentdb_test.yaml b/charts/cluster/test/documentdb-minio-backup-restore/03-documentdb_test.yaml new file mode 100644 index 0000000000..0d03621af3 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/03-documentdb_test.yaml @@ -0,0 +1,23 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: documentdb-test +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: documentdb-cluster-app + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM pg_extension WHERE extname = '\''documentdb'\'')' --csv -q 2>/dev/null)" = "t" + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM pg_extension WHERE extname = '\''pg_cron'\'')' --csv -q 2>/dev/null)" = "t" \ No newline at end of file diff --git a/charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test-assert.yaml b/charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test-assert.yaml new file mode 100644 index 0000000000..d55adaee71 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: documentdb-mongodb-client-test +status: + succeeded: 1 diff --git a/charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test.yaml b/charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test.yaml new file mode 100644 index 0000000000..79425e1e4b --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test.yaml @@ -0,0 +1,99 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: documentdb-mongodb-client-test +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: mongo-client-test + image: mongo:7.0 + env: + - name: POSTGRES_HOST + value: "documentdb-cluster-rw" + - name: DB_USER + valueFrom: + secretKeyRef: + name: documentdb-cluster-app + key: username + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: documentdb-cluster-app + key: password + - name: DB_NAME + value: "app" + command: ['sh', '-c'] + args: + - | + # Wait a moment for the service to be ready + sleep 5 + + # Test MongoDB client connectivity and operations + mongosh "mongodb://$DB_USER:$DB_PASSWORD@$POSTGRES_HOST:27017/$DB_NAME?directConnection=true" --eval ' + print("Testing MongoDB client connectivity to DocumentDB..."); + + // Test 1: Insert a document + print("Test 1: Inserting document..."); + const insertResult = db.test_collection.insertOne({ + hello: "world", + timestamp: new Date(), + nested: { field: "value" }, + array: [1, 2, 3] + }); + print("Insert successful, _id: " + insertResult.insertedId); + + // Test 2: Find the document + print("Test 2: Finding document..."); + const doc = db.test_collection.findOne({ hello: "world" }); + if (!doc || doc.hello !== "world") { + throw new Error("Document not found or incorrect"); + } + print("Find successful: " + JSON.stringify(doc)); + + // Test 3: Update the document + print("Test 3: Updating document..."); + const updateResult = db.test_collection.updateOne( + { hello: "world" }, + { $set: { updated: true, updateTime: new Date() } } + ); + if (updateResult.modifiedCount !== 1) { + throw new Error("Update failed"); + } + print("Update successful"); + + // Test 4: Verify update + print("Test 4: Verifying update..."); + const updatedDoc = db.test_collection.findOne({ hello: "world" }); + if (!updatedDoc.updated) { + throw new Error("Update verification failed"); + } + print("Update verified"); + + // Test 5: Count documents + print("Test 5: Counting documents..."); + const count = db.test_collection.countDocuments({ hello: "world" }); + if (count !== 1) { + throw new Error("Count failed, expected 1 got " + count); + } + print("Count successful: " + count); + + // Test 6: Delete the document + print("Test 6: Deleting document..."); + const deleteResult = db.test_collection.deleteOne({ hello: "world" }); + if (deleteResult.deletedCount !== 1) { + throw new Error("Delete failed"); + } + print("Delete successful"); + + // Test 7: Verify deletion + print("Test 7: Verifying deletion..."); + const deletedDoc = db.test_collection.findOne({ hello: "world" }); + if (deletedDoc !== null) { + throw new Error("Document still exists after deletion"); + } + print("Deletion verified"); + + print("✓ All MongoDB client operations successful!"); + ' diff --git a/charts/cluster/test/documentdb-minio-backup-restore/04-data_write-assert.yaml b/charts/cluster/test/documentdb-minio-backup-restore/04-data_write-assert.yaml new file mode 100644 index 0000000000..831f963d9d --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/04-data_write-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write +status: + succeeded: 1 diff --git a/charts/cluster/test/documentdb-minio-backup-restore/04-data_write.yaml b/charts/cluster/test/documentdb-minio-backup-restore/04-data_write.yaml new file mode 100644 index 0000000000..222a8b82b2 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/04-data_write.yaml @@ -0,0 +1,54 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: configmap-creator-sa +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: configmap-creator +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["create"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: configmap-creator-binding +subjects: +- kind: ServiceAccount + name: configmap-creator-sa +roleRef: + kind: Role + name: configmap-creator + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write +spec: + template: + spec: + serviceAccountName: configmap-creator-sa + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: documentdb-cluster-superuser + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client kubectl coreutils + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + psql "$DB_URI" -c "CREATE TABLE mygoodtable (id serial PRIMARY KEY);" + sleep 5 + DATE_NO_BAD_TABLE=$(date --rfc-3339=ns) + kubectl create configmap date-no-bad-table --from-literal=date="$DATE_NO_BAD_TABLE" + sleep 5 diff --git a/charts/cluster/test/documentdb-minio-backup-restore/05-backup.yaml b/charts/cluster/test/documentdb-minio-backup-restore/05-backup.yaml new file mode 100644 index 0000000000..69b675781a --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/05-backup.yaml @@ -0,0 +1,8 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + method: barmanObjectStore + cluster: + name: documentdb-cluster diff --git a/charts/cluster/test/documentdb-minio-backup-restore/05-backup_completed-assert.yaml b/charts/cluster/test/documentdb-minio-backup-restore/05-backup_completed-assert.yaml new file mode 100644 index 0000000000..8ee91b03c8 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/05-backup_completed-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + cluster: + name: documentdb-cluster + method: barmanObjectStore +status: + phase: completed diff --git a/charts/cluster/test/documentdb-minio-backup-restore/05-backup_running-assert.yaml b/charts/cluster/test/documentdb-minio-backup-restore/05-backup_running-assert.yaml new file mode 100644 index 0000000000..7c5eeada59 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/05-backup_running-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + cluster: + name: documentdb-cluster + method: barmanObjectStore +status: + phase: running diff --git a/charts/cluster/test/documentdb-minio-backup-restore/05-checkpoint.yaml b/charts/cluster/test/documentdb-minio-backup-restore/05-checkpoint.yaml new file mode 100644 index 0000000000..2caac61a6b --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/05-checkpoint.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: backup-checkpoint +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: create-checkpoint + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: documentdb-cluster-superuser + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + END_TIME=$(( $(date +%s) + 30 )) + while [ $(date +%s) -lt $END_TIME ]; do + psql "$DB_URI" -c "SELECT pg_switch_wal();CHECKPOINT;" + sleep 5 + done diff --git a/charts/cluster/test/documentdb-minio-backup-restore/06-post_backup_data_write-assert.yaml b/charts/cluster/test/documentdb-minio-backup-restore/06-post_backup_data_write-assert.yaml new file mode 100644 index 0000000000..ad9be77a7b --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/06-post_backup_data_write-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write-post-backup +status: + succeeded: 1 diff --git a/charts/cluster/test/documentdb-minio-backup-restore/06-post_backup_data_write.yaml b/charts/cluster/test/documentdb-minio-backup-restore/06-post_backup_data_write.yaml new file mode 100644 index 0000000000..ec004c10cf --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/06-post_backup_data_write.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write-post-backup +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: documentdb-cluster-superuser + key: uri + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + psql "$DB_URI" -c "CREATE TABLE mybadtable (id serial PRIMARY KEY);" diff --git a/charts/cluster/test/documentdb-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml b/charts/cluster/test/documentdb-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml new file mode 100644 index 0000000000..2b6b9651f1 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: recovery-backup-pitr-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/documentdb-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml b/charts/cluster/test/documentdb-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml new file mode 100644 index 0000000000..e18910a0db --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml @@ -0,0 +1,48 @@ +type: documentdbdb +mode: recovery + +cluster: + instances: 2 + storage: + size: 256Mi + +recovery: + method: backup + backupName: "post-init-backup" + provider: s3 + endpointURL: "https://minio.minio.svc.cluster.local" + endpointCA: + name: kube-root-ca.crt + key: ca.crt + wal: + encryption: "" + data: + encryption: "" + s3: + bucket: "mybucket" + path: "/documentdb/v1" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" + +backups: + enabled: true + provider: s3 + endpointURL: "https://minio.minio.svc.cluster.local" + endpointCA: + name: kube-root-ca.crt + key: ca.crt + wal: + encryption: "" + data: + encryption: "" + s3: + bucket: "mybucket" + path: "/documentdb/v2" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/documentdb-minio-backup-restore/08-data_test-assert.yaml b/charts/cluster/test/documentdb-minio-backup-restore/08-data_test-assert.yaml new file mode 100644 index 0000000000..6f14d5f231 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/08-data_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test-backup-pitr +status: + succeeded: 1 diff --git a/charts/cluster/test/documentdb-minio-backup-restore/08-data_test.yaml b/charts/cluster/test/documentdb-minio-backup-restore/08-data_test.yaml new file mode 100644 index 0000000000..5fb4faf395 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/08-data_test.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test-backup-pitr +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: recovery-backup-pitr-cluster-superuser + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + set -e + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $$mygoodtable$$)' --csv -q 2>/dev/null)" = "t" + echo "Good table exists" + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $$mybadtable$$)' --csv -q 2>/dev/null)" = "f" + echo "Bad table does not exist" diff --git a/charts/cluster/test/documentdb-minio-backup-restore/chainsaw-test.yaml b/charts/cluster/test/documentdb-minio-backup-restore/chainsaw-test.yaml new file mode 100644 index 0000000000..e88679d9c8 --- /dev/null +++ b/charts/cluster/test/documentdb-minio-backup-restore/chainsaw-test.yaml @@ -0,0 +1,144 @@ +## +# This test sets up a documentdb cluster with MinIO backups and ensured that documentdb extensions are installed and +# PITR recovery is enabled and working. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: documentdb +spec: + timeouts: + apply: 1s + assert: 5m + cleanup: 1m + steps: + - name: Clear the MinIO bucket + try: + - apply: + file: ./00-minio_cleanup.yaml + - assert: + file: ./00-minio_cleanup-assert.yaml + - name: Install a standalone documentdb cluster + try: + - script: + content: | + kubectl -n $NAMESPACE create secret generic kube-root-ca.crt --from-literal=ca.crt="$(kubectl -n kube-system get configmaps kube-root-ca.crt -o jsonpath='{.data.ca\.crt}')" --dry-run=client -o yaml | kubectl apply -f - + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./01-documentdb_cluster.yaml \ + --wait \ + documentdb ../../ + - assert: + file: ./01-documentdb_cluster-assert.yaml + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + - podLogs: + selector: cnpg.io/cluster=documentdb-cluster + - name: Verify documentdb extensions are installed + timeouts: + apply: 1s + assert: 30s + try: + - apply: + file: 03-documentdb_test.yaml + - assert: + file: 03-documentdb_test-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=documentdb-test + - name: Verify MongoDB client connectivity and operations + timeouts: + apply: 1s + assert: 60s + try: + - apply: + file: 03b-mongodb_client_test.yaml + - assert: + file: 03b-mongodb_client_test-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=documentdb-mongodb-client-test + - name: Write some data to the cluster + timeouts: + apply: 1s + assert: 30s + try: + - apply: + file: 04-data_write.yaml + - assert: + file: 04-data_write-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=data-test + - name: Create a backup + try: + - apply: + file: ./05-backup.yaml + - assert: + file: ./05-backup_running-assert.yaml + - apply: + file: ./05-checkpoint.yaml + - assert: + file: ./05-backup_completed-assert.yaml + - name: Write more data to the database after the backup + try: + - apply: + file: ./06-post_backup_data_write.yaml + - assert: + file: ./06-post_backup_data_write-assert.yaml + timeouts: + apply: 1s + assert: 10m + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Backup + - name: Create a recovery cluster from backup with a PITR target + try: + - script: + content: | + DATE_NO_BAD_TABLE=$(kubectl -n $NAMESPACE get configmap date-no-bad-table -o 'jsonpath={.data.date}') + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./07-recovery_backup_pitr_cluster.yaml \ + --set recovery.pitrTarget.time="$DATE_NO_BAD_TABLE" \ + --wait \ + recovery-backup-pitr ../../ + - assert: + file: ./07-recovery_backup_pitr_cluster-assert.yaml + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + - podLogs: + selector: cnpg.io/cluster=recovery-backup-pitr-cluster + - name: Verify the pre-backup data on the recovery cluster exists but not the post-backup data + try: + - apply: + file: 08-data_test.yaml + - assert: + file: 08-data_test-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + selector: batch.kubernetes.io/job-name=data-test-backup-pitr + - podLogs: + selector: batch.kubernetes.io/job-name=data-test-backup-pitr + - name: Cleanup + try: + - script: + content: | + helm uninstall --namespace $NAMESPACE documentdb From 0f6d22cae013c05026af8efc1384d9a4b79fcc32 Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 4 Nov 2025 22:02:06 +0100 Subject: [PATCH 03/21] feat(documentdb): Grant permissions on DocumentDB schemas to app owner This adds comprehensive permission grants to the DocumentDB extension schemas (documentdb_api, documentdb_core, documentdb_api_catalog, documentdb_api_internal, documentdb_data) for the app owner during bootstrap initialization. Signed-off-by: P-Louw --- charts/cluster/templates/_bootstrap.tpl | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index 8cd296f9b6..9a0df4386e 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -20,8 +20,36 @@ bootstrap: {{- else if eq .Values.type "timescaledb" }} - CREATE EXTENSION IF NOT EXISTS timescaledb; {{- else if eq .Values.type "documentdb" }} + {{- $owner := .Values.cluster.initdb.owner | default .Values.cluster.initdb.database | default "app" }} - CREATE EXTENSION IF NOT EXISTS pg_cron CASCADE; - CREATE EXTENSION IF NOT EXISTS documentdb CASCADE; + - GRANT documentdb_admin_role TO {{ $owner }}; + - GRANT USAGE ON SCHEMA documentdb_api TO {{ $owner }}; + - GRANT USAGE ON SCHEMA documentdb_core TO {{ $owner }}; + - GRANT USAGE ON SCHEMA documentdb_api_catalog TO {{ $owner }}; + - GRANT USAGE ON SCHEMA documentdb_api_internal TO {{ $owner }}; + - GRANT USAGE ON SCHEMA documentdb_data TO {{ $owner }}; + - GRANT ALL ON ALL TABLES IN SCHEMA documentdb_api TO {{ $owner }}; + - GRANT ALL ON ALL SEQUENCES IN SCHEMA documentdb_api TO {{ $owner }}; + - GRANT ALL ON ALL TABLES IN SCHEMA documentdb_core TO {{ $owner }}; + - GRANT ALL ON ALL SEQUENCES IN SCHEMA documentdb_core TO {{ $owner }}; + - GRANT ALL ON ALL TABLES IN SCHEMA documentdb_api_catalog TO {{ $owner }}; + - GRANT ALL ON ALL SEQUENCES IN SCHEMA documentdb_api_catalog TO {{ $owner }}; + - GRANT ALL ON ALL TABLES IN SCHEMA documentdb_api_internal TO {{ $owner }}; + - GRANT ALL ON ALL SEQUENCES IN SCHEMA documentdb_api_internal TO {{ $owner }}; + - GRANT ALL ON ALL TABLES IN SCHEMA documentdb_data TO {{ $owner }}; + - GRANT ALL ON ALL SEQUENCES IN SCHEMA documentdb_data TO {{ $owner }}; + - GRANT CREATE ON SCHEMA documentdb_data TO {{ $owner }}; + - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_api GRANT ALL ON TABLES TO {{ $owner }}; + - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_api GRANT ALL ON SEQUENCES TO {{ $owner }}; + - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_core GRANT ALL ON TABLES TO {{ $owner }}; + - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_core GRANT ALL ON SEQUENCES TO {{ $owner }}; + - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_api_catalog GRANT ALL ON TABLES TO {{ $owner }}; + - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_api_catalog GRANT ALL ON SEQUENCES TO {{ $owner }}; + - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_api_internal GRANT ALL ON TABLES TO {{ $owner }}; + - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_api_internal GRANT ALL ON SEQUENCES TO {{ $owner }}; + - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_data GRANT ALL ON TABLES TO {{ $owner }}; + - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_data GRANT ALL ON SEQUENCES TO {{ $owner }}; {{- end }} {{- with .Values.cluster.initdb }} {{- range .postInitApplicationSQL }} From 975b325a3d745821064279db6bc30ff9a148f08b Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 4 Nov 2025 22:04:51 +0100 Subject: [PATCH 04/21] feat(documentdb): Configure pg_cron database parameter Signed-off-by: P-Louw --- charts/cluster/templates/cluster.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/charts/cluster/templates/cluster.yaml b/charts/cluster/templates/cluster.yaml index d46bf3032d..884002d156 100644 --- a/charts/cluster/templates/cluster.yaml +++ b/charts/cluster/templates/cluster.yaml @@ -97,9 +97,14 @@ spec: synchronous: {{- toYaml . | nindent 6 }} {{ end }} - {{- with .Values.cluster.postgresql.parameters }} + {{- if or (eq .Values.type "documentdb") .Values.cluster.postgresql.parameters }} parameters: + {{- if eq .Values.type "documentdb" }} + cron.database_name: {{ .Values.cluster.initdb.database | default "app" | quote }} + {{- end }} + {{- with .Values.cluster.postgresql.parameters }} {{- toYaml . | nindent 6 }} + {{- end }} {{- end }} {{- if not (and (empty .Values.cluster.roles) (empty .Values.cluster.services)) }} From e7ae872f04f7c8113cdb5ae07f8cdf5a169d4efa Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 4 Nov 2025 22:06:06 +0100 Subject: [PATCH 05/21] feat(documentdb): Set postgres UID/GID to 999 for DocumentDB Signed-off-by: P-Louw --- charts/cluster/templates/_helpers.tpl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/charts/cluster/templates/_helpers.tpl b/charts/cluster/templates/_helpers.tpl index 14f13a0d0a..34f60b75ba 100644 --- a/charts/cluster/templates/_helpers.tpl +++ b/charts/cluster/templates/_helpers.tpl @@ -129,6 +129,8 @@ Postgres UID {{- .Values.cluster.postgresUID }} {{- else if and (eq (include "cluster.useTimescaleDBDefaults" .) "true") (eq .Values.type "timescaledb") -}} {{- 1000 -}} + {{- else if eq .Values.type "documentdb" -}} + {{- 999 -}} {{- else -}} {{- 26 -}} {{- end -}} @@ -142,6 +144,8 @@ Postgres GID {{- .Values.cluster.postgresGID }} {{- else if and (eq (include "cluster.useTimescaleDBDefaults" .) "true") (eq .Values.type "timescaledb") -}} {{- 1000 -}} + {{- else if eq .Values.type "documentdb" -}} + {{- 999 -}} {{- else -}} {{- 26 -}} {{- end -}} From 02788715e0559a74ee959ef98ba3930cb1bd44b6 Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 4 Nov 2025 22:08:12 +0100 Subject: [PATCH 06/21] feat(documentdb): Add FerretDB gateway deployment support Signed-off-by: P-Louw --- charts/cluster/templates/ferretdb.yaml | 89 ++++++++++++++++++++++++++ charts/cluster/values.yaml | 22 +++++++ 2 files changed, 111 insertions(+) create mode 100644 charts/cluster/templates/ferretdb.yaml diff --git a/charts/cluster/templates/ferretdb.yaml b/charts/cluster/templates/ferretdb.yaml new file mode 100644 index 0000000000..5ef0cfb9e0 --- /dev/null +++ b/charts/cluster/templates/ferretdb.yaml @@ -0,0 +1,89 @@ +{{ if and (eq .Values.type "documentdb") .Values.ferretdb.enabled }} +{{- $dbOwner := .Values.cluster.initdb.owner | default .Values.cluster.initdb.database | default "app" }} +{{- $dbName := .Values.cluster.initdb.database | default "app" }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "cluster.fullname" . }}-ferretdb + namespace: {{ include "cluster.namespace" . }} + labels: + {{- include "cluster.labels" . | nindent 4 }} + app.kubernetes.io/component: ferretdb +spec: + replicas: {{ .Values.ferretdb.instances }} + selector: + matchLabels: + {{- include "cluster.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: ferretdb + template: + metadata: + labels: + {{- include "cluster.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: ferretdb + spec: + containers: + - name: ferretdb + image: {{ .Values.ferretdb.image | default "ghcr.io/ferretdb/ferretdb" }}:{{ .Values.ferretdb.tag | default .Values.version.ferretdb }} + imagePullPolicy: {{ .Values.cluster.imagePullPolicy }} + ports: + - name: mongodb + containerPort: 27017 + protocol: TCP + command: ["/ferretdb"] + args: + - "--listen-addr=:27017" + - "--postgresql-url=postgres://{{ $dbOwner }}@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}" + - "--telemetry=disable" + - "--log-level=info" + env: + {{- if .Values.cluster.initdb.secret }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.cluster.initdb.secret.name }} + key: password + {{- else }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ include "cluster.fullname" . }}-app + key: password + {{- end }} + {{- with .Values.ferretdb.env }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ferretdb.resources }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} + livenessProbe: + tcpSocket: + port: 27017 + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 27017 + initialDelaySeconds: 5 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "cluster.fullname" . }}-ferretdb + namespace: {{ include "cluster.namespace" . }} + labels: + {{- include "cluster.labels" . | nindent 4 }} + app.kubernetes.io/component: ferretdb +spec: + type: ClusterIP + ports: + - name: mongodb + port: 27017 + targetPort: 27017 + protocol: TCP + selector: + {{- include "cluster.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: ferretdb +{{- end }} diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index 9b5943b226..aa8b03ef8e 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -487,6 +487,28 @@ imageCatalog: # - image: ghcr.io/your_repo/your_image:your_tag # major: 16 +# -- FerretDB gateway (DocumentDB only) +# Provides MongoDB wire protocol (port 27017) for existing MongoDB applications +ferretdb: + # -- Enable FerretDB gateway (only for type: documentdb) + enabled: false + # -- Number of FerretDB instances + instances: 1 + # -- FerretDB image (defaults to ghcr.io/ferretdb/ferretdb) + image: "" + # -- FerretDB image tag (defaults to version.ferretdb) + tag: "" + # -- Container resources + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 250m + # memory: 256Mi + # -- Additional environment variables + env: [] + # -- List of PgBouncer poolers poolers: [] # - From 7ae4273a3dacc66dcc73f467a6cc09e07c5653d1 Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 4 Nov 2025 22:46:28 +0100 Subject: [PATCH 07/21] feat(documentdb): Update example yaml Signed-off-by: P-Louw --- charts/cluster/examples/documentdb.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/charts/cluster/examples/documentdb.yaml b/charts/cluster/examples/documentdb.yaml index 835fe7970e..b5b2a09f76 100644 --- a/charts/cluster/examples/documentdb.yaml +++ b/charts/cluster/examples/documentdb.yaml @@ -6,5 +6,7 @@ version: ferretdb: "2.5.0" cluster: instances: 1 +ferretdb: + enabled: true backups: enabled: false From f9208b635cdc80087228b57454e771444b6ca491 Mon Sep 17 00:00:00 2001 From: P-Louw Date: Wed, 5 Nov 2025 10:14:06 +0100 Subject: [PATCH 08/21] feat(documentdb): Update test and auth vars Signed-off-by: P-Louw --- .../examples/documentdb-auth-examples.yaml | 308 ++++++++++++++++++ charts/cluster/templates/ferretdb.yaml | 48 ++- .../01-documentdb_cluster.yaml | 3 + .../03b-mongodb_client_test.yaml | 6 +- charts/cluster/values.yaml | 26 ++ 5 files changed, 381 insertions(+), 10 deletions(-) create mode 100644 charts/cluster/examples/documentdb-auth-examples.yaml diff --git a/charts/cluster/examples/documentdb-auth-examples.yaml b/charts/cluster/examples/documentdb-auth-examples.yaml new file mode 100644 index 0000000000..fbed7ccd2c --- /dev/null +++ b/charts/cluster/examples/documentdb-auth-examples.yaml @@ -0,0 +1,308 @@ +# DocumentDB Authentication Configuration Examples +# +# This file shows different authentication configurations for DocumentDB with FerretDB. +# Users can choose the approach that best fits their security requirements. + +############################################################################### +# Option 1: Default Configuration (.pgpass) - Recommended for Dev/Test +############################################################################### +# The chart automatically configures secure authentication using .pgpass file. +# No additional configuration needed! +# +# Security Level: Medium +# Use Case: Development, Testing, Staging +--- +type: documentdb +mode: standalone +version: + postgresql: "17" + documentdb: "0.106.0" + ferretdb: "2.5.0" +cluster: + instances: 1 +ferretdb: + enabled: true +backups: + enabled: false + +############################################################################### +# Option 2: pg_hba Host-Based Authentication - Recommended for Production +############################################################################### +# Configure PostgreSQL to authenticate based on source IP/network. +# This is the most secure and flexible production approach. +# +# Security Level: High +# Use Case: Production, Staging with network isolation +--- +type: documentdb +mode: standalone +version: + postgresql: "17" + documentdb: "0.106.0" + ferretdb: "2.5.0" +cluster: + instances: 3 + postgresql: + pg_hba: + # Allow connections from pod network with SCRAM-SHA-256 (most secure) + - "hostssl all all 10.244.0.0/16 scram-sha-256" + # Or use md5 for broader compatibility + # - "hostssl all all 10.244.0.0/16 md5" + parameters: + # Enforce strong password encryption + password_encryption: "scram-sha-256" + # Require SSL connections + ssl: "on" + ssl_min_protocol_version: "TLSv1.3" + # Enable connection logging for security auditing + log_connections: "on" + log_disconnections: "on" +ferretdb: + enabled: true + instances: 2 +backups: + enabled: true + +############################################################################### +# Option 3: Trust Authentication - Local Development ONLY +############################################################################### +# WARNING: This is INSECURE! Use only for local testing. +# No password required - anyone who can reach the database can connect. +# +# Security Level: None +# Use Case: Local development on trusted networks only +--- +type: documentdb +mode: standalone +version: + postgresql: "17" + documentdb: "0.106.0" + ferretdb: "2.5.0" +cluster: + instances: 1 + postgresql: + pg_hba: + # DANGEROUS: No authentication required! + - "host all all 10.244.0.0/16 trust" +ferretdb: + enabled: true +backups: + enabled: false + +############################################################################### +# Option 4: Mixed Authentication Rules +############################################################################### +# Combine multiple pg_hba rules for different access patterns. +# +# Security Level: High (with proper configuration) +# Use Case: Complex environments with different access requirements +--- +type: documentdb +mode: standalone +version: + postgresql: "17" + documentdb: "0.106.0" + ferretdb: "2.5.0" +cluster: + instances: 3 + postgresql: + pg_hba: + # FerretDB pods from specific subnet with strong auth + - "hostssl all all 10.244.0.0/24 scram-sha-256" + # Allow admin subnet with certificate authentication + - "hostssl all all 10.245.0.0/24 cert" + # Legacy app subnet with md5 (less secure, but compatible) + - "hostssl all all 10.246.0.0/24 md5" + # Reject all other connections explicitly + - "reject all all 0.0.0.0/0" + parameters: + password_encryption: "scram-sha-256" + ssl: "on" + ssl_min_protocol_version: "TLSv1.2" +ferretdb: + enabled: true + instances: 2 + +############################################################################### +# Option 5: Custom FerretDB Configuration +############################################################################### +# Override FerretDB behavior with custom environment variables +# +# Security Level: Configurable +# Use Case: Custom FerretDB settings, debugging +--- +type: documentdb +mode: standalone +version: + postgresql: "17" + documentdb: "0.106.0" + ferretdb: "2.5.0" +cluster: + instances: 2 +ferretdb: + enabled: true + instances: 2 + # Custom FerretDB image if needed + image: "ghcr.io/ferretdb/ferretdb" + tag: "2.5.0" + # Override resources + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1000m" + # Add custom environment variables + # These can be used to modify FerretDB behavior + env: + - name: FERRETDB_LOG_LEVEL + value: "debug" + - name: FERRETDB_TELEMETRY + value: "disable" + # You can even override the PostgreSQL connection parameters + # But be careful - the chart manages the connection string by default + # - name: FERRETDB_POSTGRESQL_URL + # value: "postgres://custom-connection-string" + +############################################################################### +# Option 6: Production Setup with All Security Features +############################################################################### +# Complete production-ready configuration with all security best practices. +# +# Security Level: Maximum +# Use Case: Production deployments +--- +type: documentdb +mode: standalone +version: + postgresql: "17" + documentdb: "0.106.0" + ferretdb: "2.5.0" +cluster: + instances: 3 + storage: + size: 100Gi + storageClass: fast-ssd + # Enable pod anti-affinity for high availability + affinity: + topologyKey: topology.kubernetes.io/zone + postgresql: + pg_hba: + # Only allow SSL connections from pod network + - "hostssl all all 10.244.0.0/16 scram-sha-256" + # Explicitly reject non-SSL connections + - "reject all all 0.0.0.0/0" + parameters: + # Security parameters + password_encryption: "scram-sha-256" + ssl: "on" + ssl_min_protocol_version: "TLSv1.3" + ssl_prefer_server_ciphers: "on" + # Connection limits + max_connections: 200 + superuser_reserved_connections: 3 + # Logging for security auditing + log_connections: "on" + log_disconnections: "on" + log_failed_authentication: "on" + log_statement: "ddl" # Log all DDL statements + # Performance tuning + shared_buffers: "4GB" + effective_cache_size: "12GB" + work_mem: "16MB" + # Enable monitoring + monitoring: + enabled: true + podMonitor: + enabled: true +ferretdb: + enabled: true + instances: 3 + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "2Gi" + cpu: "2000m" +backups: + enabled: true + provider: s3 + s3: + region: us-east-1 + bucket: production-backups + path: /documentdb + inheritFromIAMRole: true # Use IAM roles instead of access keys + scheduledBackups: + - name: daily-backup + schedule: "0 0 2 * * *" # Daily at 2 AM + backupOwnerReference: self + retentionPolicy: "30d" + +############################################################################### +# How to Find Your Pod Network CIDR +############################################################################### +# To configure pg_hba rules, you need to know your Kubernetes pod network CIDR: +# +# Method 1: Check node pod CIDR allocation +# kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' +# +# Method 2: Check existing pod IPs +# kubectl get pods -A -o wide | grep -v "IP" | awk '{print $6}' | sort -u +# +# Method 3: Check CNI configuration +# kubectl get cm kube-proxy -n kube-system -o yaml | grep clusterCIDR +# +# Common pod CIDRs by Kubernetes distribution: +# - kind: 10.244.0.0/16 +# - minikube: 172.17.0.0/16 +# - GKE: 10.0.0.0/8 (varies) +# - EKS: 10.0.0.0/8 (varies) +# - AKS: 10.244.0.0/16 + +############################################################################### +# Testing Authentication Configuration +############################################################################### +# After deploying, test your authentication setup: +# +# 1. Test PostgreSQL direct connection: +# kubectl run psql-test --rm -it --image postgres:17 -- \ +# psql "$(kubectl get secret documentdb-cluster-app -o jsonpath='{.data.uri}' | base64 -d)" +# +# 2. Test FerretDB MongoDB connection: +# DB_USER=$(kubectl get secret documentdb-cluster-app -o jsonpath='{.data.username}' | base64 -d) +# DB_PASSWORD=$(kubectl get secret documentdb-cluster-app -o jsonpath='{.data.password}' | base64 -d) +# kubectl run mongo-test --rm -it --image mongo:7.0 -- \ +# mongosh "mongodb://$DB_USER:$DB_PASSWORD@documentdb-cluster-ferretdb:27017/app" +# +# 3. Check authentication logs: +# kubectl logs -l cnpg.io/cluster=documentdb-cluster | grep -i "authentication\|connection" +# +# 4. View pg_hba configuration: +# kubectl exec documentdb-cluster-1 -- cat /var/lib/postgresql/data/pgdata/pg_hba.conf + +############################################################################### +# Troubleshooting +############################################################################### +# Common Issues: +# +# 1. "fe_sendauth: no password supplied" +# - Check that FerretDB can reach PostgreSQL service +# - Verify pg_hba rules allow connections from FerretDB pods +# - Check FerretDB logs: kubectl logs -l app.kubernetes.io/component=ferretdb +# +# 2. "no pg_hba.conf entry for host" +# - Your pg_hba rules don't match the source IP +# - Check actual pod IPs: kubectl get pods -o wide +# - Verify your CIDR includes the FerretDB pod IPs +# +# 3. "SCRAM authentication failed" +# - Password may be incorrect +# - Or password_encryption setting doesn't match pg_hba method +# - Check: kubectl get secret documentdb-cluster-app -o yaml +# +# 4. Connection timeout +# - Check if NetworkPolicy is blocking access +# - Verify FerretDB service: kubectl get svc documentdb-cluster-ferretdb +# - Test connectivity: kubectl exec -it -- nc -zv documentdb-cluster-rw 5432 diff --git a/charts/cluster/templates/ferretdb.yaml b/charts/cluster/templates/ferretdb.yaml index 5ef0cfb9e0..2e9fb8b598 100644 --- a/charts/cluster/templates/ferretdb.yaml +++ b/charts/cluster/templates/ferretdb.yaml @@ -30,21 +30,55 @@ spec: - name: mongodb containerPort: 27017 protocol: TCP - command: ["/ferretdb"] + command: ["/bin/sh"] args: - - "--listen-addr=:27017" - - "--postgresql-url=postgres://{{ $dbOwner }}@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}" - - "--telemetry=disable" - - "--log-level=info" + - "-c" + - | + {{- $credMethod := .Values.ferretdb.credentialMethod | default "pgpass" }} + {{- if eq $credMethod "pgpass" }} + # Create .pgpass file for secure password handling + echo "{{ include "cluster.fullname" . }}-rw:5432:{{ $dbName }}:{{ $dbOwner }}:${DB_PASSWORD}" > /tmp/.pgpass + chmod 600 /tmp/.pgpass + export PGPASSFILE=/tmp/.pgpass + PG_URL="postgres://{{ $dbOwner }}@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}{{- if .Values.ferretdb.sslMode }}?sslmode={{ .Values.ferretdb.sslMode }}{{- end }}" + {{- else if eq $credMethod "password-in-url" }} + # Using password in URL (less secure, for dev/test only) + PG_URL="postgres://{{ $dbOwner }}:${DB_PASSWORD}@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}{{- if .Values.ferretdb.sslMode }}?sslmode={{ .Values.ferretdb.sslMode }}{{- end }}" + {{- else if eq $credMethod "env" }} + # Using PGPASSWORD environment variable + export PGPASSWORD="${DB_PASSWORD}" + PG_URL="postgres://{{ $dbOwner }}@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}{{- if .Values.ferretdb.sslMode }}?sslmode={{ .Values.ferretdb.sslMode }}{{- end }}" + {{- end }} + + exec /ferretdb \ + --listen-addr=:27017 \ + --postgresql-url="${PG_URL}" \ + --telemetry={{ .Values.ferretdb.telemetry | default "disable" }} \ + --log-level={{ .Values.ferretdb.logLevel | default "info" }} \ + {{- if .Values.ferretdb.auth }} + --auth={{ .Values.ferretdb.auth }} \ + {{- end }} + {{- if .Values.ferretdb.mode }} + --mode={{ .Values.ferretdb.mode }} \ + {{- end }} + {{- if .Values.ferretdb.debugAddr }} + --debug-addr={{ .Values.ferretdb.debugAddr }} \ + {{- end }} + {{- if .Values.ferretdb.otelTracesUrl }} + --otel-traces-url={{ .Values.ferretdb.otelTracesUrl }} \ + {{- end }} + {{- range .Values.ferretdb.extraArgs }} + {{ . }} \ + {{- end }} env: {{- if .Values.cluster.initdb.secret }} - - name: PGPASSWORD + - name: DB_PASSWORD valueFrom: secretKeyRef: name: {{ .Values.cluster.initdb.secret.name }} key: password {{- else }} - - name: PGPASSWORD + - name: DB_PASSWORD valueFrom: secretKeyRef: name: {{ include "cluster.fullname" . }}-app diff --git a/charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster.yaml b/charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster.yaml index 7518783090..ade7a789a8 100644 --- a/charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster.yaml +++ b/charts/cluster/test/documentdb-minio-backup-restore/01-documentdb_cluster.yaml @@ -10,6 +10,9 @@ cluster: storage: size: 256Mi +ferretdb: + enabled: true + backups: enabled: true diff --git a/charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test.yaml b/charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test.yaml index 79425e1e4b..cb7ab76fe0 100644 --- a/charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test.yaml +++ b/charts/cluster/test/documentdb-minio-backup-restore/03b-mongodb_client_test.yaml @@ -10,8 +10,8 @@ spec: - name: mongo-client-test image: mongo:7.0 env: - - name: POSTGRES_HOST - value: "documentdb-cluster-rw" + - name: FERRETDB_HOST + value: "documentdb-cluster-ferretdb" - name: DB_USER valueFrom: secretKeyRef: @@ -31,7 +31,7 @@ spec: sleep 5 # Test MongoDB client connectivity and operations - mongosh "mongodb://$DB_USER:$DB_PASSWORD@$POSTGRES_HOST:27017/$DB_NAME?directConnection=true" --eval ' + mongosh "mongodb://$DB_USER:$DB_PASSWORD@$FERRETDB_HOST:27017/$DB_NAME?directConnection=true" --eval ' print("Testing MongoDB client connectivity to DocumentDB..."); // Test 1: Insert a document diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index aa8b03ef8e..dcec4856c4 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -506,6 +506,32 @@ ferretdb: # requests: # cpu: 250m # memory: 256Mi + # -- FerretDB log level (debug, info, warn, error) + logLevel: "info" + # -- Enable/disable FerretDB telemetry + telemetry: "disable" + # -- How FerretDB provides credentials to PostgreSQL (pgpass, password-in-url, or env) + # - pgpass: Uses .pgpass file (recommended, works with all pg_hba methods) + # - password-in-url: Embeds password in connection string (simpler, dev/test only) + # - env: Uses PGPASSWORD environment variable + # Note: PostgreSQL authentication method is controlled by cluster.postgresql.pg_hba + credentialMethod: "pgpass" + # -- PostgreSQL connection SSL mode (disable, require, verify-ca, verify-full) + # Uncomment to override default SSL behavior + # sslMode: "require" + # -- Enable authentication (default: true). Set to false to disable + # auth: true + # -- FerretDB operation mode (default: normal, options: diff-normal, proxy) + # mode: "normal" + # -- Expose debug/metrics endpoint (e.g. ":8088") + # debugAddr: ":8088" + # -- OpenTelemetry traces URL for distributed tracing + # otelTracesUrl: "" + # -- Additional FerretDB command-line arguments (as list) + # extraArgs: [] + # - "--listen-tls=:27018" + # - "--listen-tls-cert-file=/path/to/cert" + # - "--listen-tls-key-file=/path/to/key" # -- Additional environment variables env: [] From ef9c5938444ad50789e8bbd84c3e71559d083dfa Mon Sep 17 00:00:00 2001 From: P-Louw Date: Wed, 5 Nov 2025 11:32:30 +0100 Subject: [PATCH 09/21] feature(documentdb): Update example yaml usage Signed-off-by: P-Louw --- charts/cluster/examples/documentdb-auth-examples.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/charts/cluster/examples/documentdb-auth-examples.yaml b/charts/cluster/examples/documentdb-auth-examples.yaml index fbed7ccd2c..47a2428cff 100644 --- a/charts/cluster/examples/documentdb-auth-examples.yaml +++ b/charts/cluster/examples/documentdb-auth-examples.yaml @@ -4,13 +4,14 @@ # Users can choose the approach that best fits their security requirements. ############################################################################### -# Option 1: Default Configuration (.pgpass) - Recommended for Dev/Test +# Option 1: Default Configuration (.pgpass) ############################################################################### -# The chart automatically configures secure authentication using .pgpass file. -# No additional configuration needed! +# The chart automatically configures .pgpass for credential handling. +# This is the recommended way to provide credentials (vs password-in-url or env). # -# Security Level: Medium -# Use Case: Development, Testing, Staging +# Credential Handling: Best practice (prevents password exposure in logs/ps) +# Use Case: All environments (recommended default for password-based auth) +# Production Note: Combine with strong pg_hba rules (see Option 2 below) --- type: documentdb mode: standalone From 26dd8ce4771dd3a55f5dca3158041b446464b8fe Mon Sep 17 00:00:00 2001 From: P-Louw Date: Sat, 8 Nov 2025 18:49:52 +0100 Subject: [PATCH 10/21] chore: trim down auth-example comments Signed-off-by: P-Louw --- .../examples/documentdb-auth-examples.yaml | 33 ++----------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/charts/cluster/examples/documentdb-auth-examples.yaml b/charts/cluster/examples/documentdb-auth-examples.yaml index 47a2428cff..18751f7079 100644 --- a/charts/cluster/examples/documentdb-auth-examples.yaml +++ b/charts/cluster/examples/documentdb-auth-examples.yaml @@ -1,11 +1,6 @@ # DocumentDB Authentication Configuration Examples -# -# This file shows different authentication configurations for DocumentDB with FerretDB. -# Users can choose the approach that best fits their security requirements. -############################################################################### # Option 1: Default Configuration (.pgpass) -############################################################################### # The chart automatically configures .pgpass for credential handling. # This is the recommended way to provide credentials (vs password-in-url or env). # @@ -26,13 +21,10 @@ ferretdb: backups: enabled: false -############################################################################### # Option 2: pg_hba Host-Based Authentication - Recommended for Production -############################################################################### # Configure PostgreSQL to authenticate based on source IP/network. # This is the most secure and flexible production approach. # -# Security Level: High # Use Case: Production, Staging with network isolation --- type: documentdb @@ -64,13 +56,10 @@ ferretdb: backups: enabled: true -############################################################################### # Option 3: Trust Authentication - Local Development ONLY -############################################################################### # WARNING: This is INSECURE! Use only for local testing. # No password required - anyone who can reach the database can connect. # -# Security Level: None # Use Case: Local development on trusted networks only --- type: documentdb @@ -90,12 +79,9 @@ ferretdb: backups: enabled: false -############################################################################### # Option 4: Mixed Authentication Rules -############################################################################### # Combine multiple pg_hba rules for different access patterns. # -# Security Level: High (with proper configuration) # Use Case: Complex environments with different access requirements --- type: documentdb @@ -124,12 +110,9 @@ ferretdb: enabled: true instances: 2 -############################################################################### # Option 5: Custom FerretDB Configuration -############################################################################### # Override FerretDB behavior with custom environment variables # -# Security Level: Configurable # Use Case: Custom FerretDB settings, debugging --- type: documentdb @@ -166,12 +149,9 @@ ferretdb: # - name: FERRETDB_POSTGRESQL_URL # value: "postgres://custom-connection-string" -############################################################################### # Option 6: Production Setup with All Security Features -############################################################################### # Complete production-ready configuration with all security best practices. # -# Security Level: Maximum # Use Case: Production deployments --- type: documentdb @@ -207,7 +187,7 @@ cluster: log_connections: "on" log_disconnections: "on" log_failed_authentication: "on" - log_statement: "ddl" # Log all DDL statements + log_statement: "ddl" # Log all DDL statements # Performance tuning shared_buffers: "4GB" effective_cache_size: "12GB" @@ -234,16 +214,13 @@ backups: region: us-east-1 bucket: production-backups path: /documentdb - inheritFromIAMRole: true # Use IAM roles instead of access keys + inheritFromIAMRole: true # Use IAM roles instead of access keys scheduledBackups: - name: daily-backup - schedule: "0 0 2 * * *" # Daily at 2 AM + schedule: "0 0 2 * * *" # Daily at 2 AM backupOwnerReference: self retentionPolicy: "30d" - -############################################################################### # How to Find Your Pod Network CIDR -############################################################################### # To configure pg_hba rules, you need to know your Kubernetes pod network CIDR: # # Method 1: Check node pod CIDR allocation @@ -262,9 +239,7 @@ backups: # - EKS: 10.0.0.0/8 (varies) # - AKS: 10.244.0.0/16 -############################################################################### # Testing Authentication Configuration -############################################################################### # After deploying, test your authentication setup: # # 1. Test PostgreSQL direct connection: @@ -283,9 +258,7 @@ backups: # 4. View pg_hba configuration: # kubectl exec documentdb-cluster-1 -- cat /var/lib/postgresql/data/pgdata/pg_hba.conf -############################################################################### # Troubleshooting -############################################################################### # Common Issues: # # 1. "fe_sendauth: no password supplied" From 5d56a98e63876b888aff4841d48ba789eeda1e4d Mon Sep 17 00:00:00 2001 From: P-Louw Date: Mon, 10 Nov 2025 16:46:54 +0100 Subject: [PATCH 11/21] fix(auth): No shell connection secret. --- .../examples/documentdb-auth-examples.yaml | 12 +- charts/cluster/templates/ferretdb.yaml | 109 +++++++++--------- charts/cluster/values.yaml | 6 - 3 files changed, 62 insertions(+), 65 deletions(-) diff --git a/charts/cluster/examples/documentdb-auth-examples.yaml b/charts/cluster/examples/documentdb-auth-examples.yaml index 18751f7079..ba1f9822d6 100644 --- a/charts/cluster/examples/documentdb-auth-examples.yaml +++ b/charts/cluster/examples/documentdb-auth-examples.yaml @@ -1,8 +1,8 @@ # DocumentDB Authentication Configuration Examples -# Option 1: Default Configuration (.pgpass) -# The chart automatically configures .pgpass for credential handling. -# This is the recommended way to provide credentials (vs password-in-url or env). +# Option 1: Default Configuration (Secure File-Based Authentication) +# The chart automatically configures secure file-based PostgreSQL URL handling. +# Credentials are stored in a memory-backed volume and passed via --postgresql-url-file. # # Credential Handling: Best practice (prevents password exposure in logs/ps) # Use Case: All environments (recommended default for password-based auth) @@ -144,10 +144,8 @@ ferretdb: value: "debug" - name: FERRETDB_TELEMETRY value: "disable" - # You can even override the PostgreSQL connection parameters - # But be careful - the chart manages the connection string by default - # - name: FERRETDB_POSTGRESQL_URL - # value: "postgres://custom-connection-string" + # Note: PostgreSQL connection is managed via --postgresql-url-file flag + # The chart automatically creates a secure connection string from secrets # Option 6: Production Setup with All Security Features # Complete production-ready configuration with all security best practices. diff --git a/charts/cluster/templates/ferretdb.yaml b/charts/cluster/templates/ferretdb.yaml index 2e9fb8b598..1aca995c44 100644 --- a/charts/cluster/templates/ferretdb.yaml +++ b/charts/cluster/templates/ferretdb.yaml @@ -22,6 +22,25 @@ spec: {{- include "cluster.selectorLabels" . | nindent 8 }} app.kubernetes.io/component: ferretdb spec: + # Init container to build PostgreSQL connection URL with password + initContainers: + - name: setup-pgurl + image: busybox:1.36 + command: ["/bin/sh", "-c"] + args: + - | + # Read password from secret and build connection URL + PASSWORD=$(cat /secrets/password) + cat > /pgurl/pgurl < /tmp/.pgpass - chmod 600 /tmp/.pgpass - export PGPASSFILE=/tmp/.pgpass - PG_URL="postgres://{{ $dbOwner }}@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}{{- if .Values.ferretdb.sslMode }}?sslmode={{ .Values.ferretdb.sslMode }}{{- end }}" - {{- else if eq $credMethod "password-in-url" }} - # Using password in URL (less secure, for dev/test only) - PG_URL="postgres://{{ $dbOwner }}:${DB_PASSWORD}@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}{{- if .Values.ferretdb.sslMode }}?sslmode={{ .Values.ferretdb.sslMode }}{{- end }}" - {{- else if eq $credMethod "env" }} - # Using PGPASSWORD environment variable - export PGPASSWORD="${DB_PASSWORD}" - PG_URL="postgres://{{ $dbOwner }}@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}{{- if .Values.ferretdb.sslMode }}?sslmode={{ .Values.ferretdb.sslMode }}{{- end }}" - {{- end }} - - exec /ferretdb \ - --listen-addr=:27017 \ - --postgresql-url="${PG_URL}" \ - --telemetry={{ .Values.ferretdb.telemetry | default "disable" }} \ - --log-level={{ .Values.ferretdb.logLevel | default "info" }} \ - {{- if .Values.ferretdb.auth }} - --auth={{ .Values.ferretdb.auth }} \ - {{- end }} - {{- if .Values.ferretdb.mode }} - --mode={{ .Values.ferretdb.mode }} \ - {{- end }} - {{- if .Values.ferretdb.debugAddr }} - --debug-addr={{ .Values.ferretdb.debugAddr }} \ - {{- end }} - {{- if .Values.ferretdb.otelTracesUrl }} - --otel-traces-url={{ .Values.ferretdb.otelTracesUrl }} \ - {{- end }} - {{- range .Values.ferretdb.extraArgs }} - {{ . }} \ - {{- end }} - env: - {{- if .Values.cluster.initdb.secret }} - - name: DB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Values.cluster.initdb.secret.name }} - key: password - {{- else }} - - name: DB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ include "cluster.fullname" . }}-app - key: password + - --listen-addr=:27017 + - --postgresql-url-file=/pgurl/pgurl + - --telemetry={{ .Values.ferretdb.telemetry | default "disable" }} + - --log-level={{ .Values.ferretdb.logLevel | default "info" }} + {{- if .Values.ferretdb.auth }} + - --auth={{ .Values.ferretdb.auth }} + {{- end }} + {{- if .Values.ferretdb.mode }} + - --mode={{ .Values.ferretdb.mode }} {{- end }} + {{- if .Values.ferretdb.debugAddr }} + - --debug-addr={{ .Values.ferretdb.debugAddr }} + {{- end }} + {{- if .Values.ferretdb.otelTracesUrl }} + - --otel-traces-url={{ .Values.ferretdb.otelTracesUrl }} + {{- end }} + {{- range .Values.ferretdb.extraArgs }} + - {{ . }} + {{- end }} + volumeMounts: + - name: pgurl + mountPath: /pgurl + readOnly: true {{- with .Values.ferretdb.env }} + env: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.ferretdb.resources }} @@ -101,6 +92,20 @@ spec: port: 27017 initialDelaySeconds: 5 periodSeconds: 5 + volumes: + - name: pgurl + emptyDir: + medium: Memory # Use in-memory storage for credentials + - name: db-password + secret: + {{- if .Values.cluster.initdb.secret }} + secretName: {{ .Values.cluster.initdb.secret.name }} + {{- else }} + secretName: {{ include "cluster.fullname" . }}-app + {{- end }} + items: + - key: password + path: password --- apiVersion: v1 kind: Service diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index dcec4856c4..272cd4e040 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -510,12 +510,6 @@ ferretdb: logLevel: "info" # -- Enable/disable FerretDB telemetry telemetry: "disable" - # -- How FerretDB provides credentials to PostgreSQL (pgpass, password-in-url, or env) - # - pgpass: Uses .pgpass file (recommended, works with all pg_hba methods) - # - password-in-url: Embeds password in connection string (simpler, dev/test only) - # - env: Uses PGPASSWORD environment variable - # Note: PostgreSQL authentication method is controlled by cluster.postgresql.pg_hba - credentialMethod: "pgpass" # -- PostgreSQL connection SSL mode (disable, require, verify-ca, verify-full) # Uncomment to override default SSL behavior # sslMode: "require" From 6838e98a6e1a68fbb26f08255c0ad71ccadb14cc Mon Sep 17 00:00:00 2001 From: P-Louw Date: Mon, 10 Nov 2025 21:21:21 +0100 Subject: [PATCH 12/21] fix: init container uses ferretd uids --- charts/cluster/templates/ferretdb.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/charts/cluster/templates/ferretdb.yaml b/charts/cluster/templates/ferretdb.yaml index 1aca995c44..27bb60f832 100644 --- a/charts/cluster/templates/ferretdb.yaml +++ b/charts/cluster/templates/ferretdb.yaml @@ -26,6 +26,10 @@ spec: initContainers: - name: setup-pgurl image: busybox:1.36 + # Run as same user as FerretDB container to ensure file permissions work + securityContext: + runAsUser: 1000 + runAsGroup: 1000 command: ["/bin/sh", "-c"] args: - | From 7662f7eda8b59e6032e51797da2bcd234d630904 Mon Sep 17 00:00:00 2001 From: P-Louw Date: Mon, 10 Nov 2025 23:37:25 +0100 Subject: [PATCH 13/21] fix: ferretdb pg rol --- charts/cluster/Chart.yaml | 2 +- charts/cluster/templates/ferretdb.yaml | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/charts/cluster/Chart.yaml b/charts/cluster/Chart.yaml index 6e0bfd49b3..404fcc0941 100644 --- a/charts/cluster/Chart.yaml +++ b/charts/cluster/Chart.yaml @@ -21,7 +21,7 @@ name: cluster description: Deploys and manages a CloudNativePG cluster and its associated resources. icon: https://raw.githubusercontent.com/cloudnative-pg/artwork/main/cloudnativepg-logo.svg type: application -version: 0.3.1 +version: 0.3.1-documentdb.2 sources: - https://github.com/cloudnative-pg/charts keywords: diff --git a/charts/cluster/templates/ferretdb.yaml b/charts/cluster/templates/ferretdb.yaml index 27bb60f832..11baadbadb 100644 --- a/charts/cluster/templates/ferretdb.yaml +++ b/charts/cluster/templates/ferretdb.yaml @@ -1,5 +1,5 @@ {{ if and (eq .Values.type "documentdb") .Values.ferretdb.enabled }} -{{- $dbOwner := .Values.cluster.initdb.owner | default .Values.cluster.initdb.database | default "app" }} +{{- $dbOwner := "postgres" }} {{- $dbName := .Values.cluster.initdb.database | default "app" }} --- apiVersion: apps/v1 @@ -102,11 +102,8 @@ spec: medium: Memory # Use in-memory storage for credentials - name: db-password secret: - {{- if .Values.cluster.initdb.secret }} - secretName: {{ .Values.cluster.initdb.secret.name }} - {{- else }} - secretName: {{ include "cluster.fullname" . }}-app - {{- end }} + # Use superuser secret for FerretDB (required for DocumentDB extension) + secretName: {{ include "cluster.fullname" . }}-superuser items: - key: password path: password From 5a07a465a80d859a383b783efa371daa09a3f7e9 Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 11 Nov 2025 09:15:55 +0100 Subject: [PATCH 14/21] fix: cron db name and user --- charts/cluster/templates/cluster.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/charts/cluster/templates/cluster.yaml b/charts/cluster/templates/cluster.yaml index 884002d156..a956d75b8e 100644 --- a/charts/cluster/templates/cluster.yaml +++ b/charts/cluster/templates/cluster.yaml @@ -100,7 +100,9 @@ spec: {{- if or (eq .Values.type "documentdb") .Values.cluster.postgresql.parameters }} parameters: {{- if eq .Values.type "documentdb" }} - cron.database_name: {{ .Values.cluster.initdb.database | default "app" | quote }} + # pg_cron should connect to postgres database (not app) + # See: https://blog.ferretdb.io/run-ferretdb-postgres-documentdb-extension-cnpg-kubernetes/ + cron.database_name: "postgres" {{- end }} {{- with .Values.cluster.postgresql.parameters }} {{- toYaml . | nindent 6 }} From 2297be789969536ef65a0d11fa0719fcac7af94a Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 11 Nov 2025 14:45:29 +0100 Subject: [PATCH 15/21] fix: extension enable initdb --- charts/cluster/Chart.yaml | 2 +- charts/cluster/templates/_bootstrap.tpl | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/charts/cluster/Chart.yaml b/charts/cluster/Chart.yaml index 404fcc0941..11c2f0c3a9 100644 --- a/charts/cluster/Chart.yaml +++ b/charts/cluster/Chart.yaml @@ -21,7 +21,7 @@ name: cluster description: Deploys and manages a CloudNativePG cluster and its associated resources. icon: https://raw.githubusercontent.com/cloudnative-pg/artwork/main/cloudnativepg-logo.svg type: application -version: 0.3.1-documentdb.2 +version: 0.3.1-documentdb.3 sources: - https://github.com/cloudnative-pg/charts keywords: diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index 9a0df4386e..c6022bcc7d 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -10,6 +10,12 @@ bootstrap: {{- if .Values.cluster.initdb.owner }} owner: {{ tpl .Values.cluster.initdb.owner . }} {{- end }} + {{- if eq .Values.type "documentdb" }} + # pg_cron extension must be created in postgres database + # See: https://github.com/citusdata/pg_cron#installing-pg_cron + postInitSQL: + - CREATE EXTENSION IF NOT EXISTS pg_cron CASCADE; + {{- end }} {{- if or (eq .Values.type "postgis") (eq .Values.type "timescaledb") (eq .Values.type "documentdb") (not (empty .Values.cluster.initdb.postInitApplicationSQL)) }} postInitApplicationSQL: {{- if eq .Values.type "postgis" }} @@ -21,7 +27,6 @@ bootstrap: - CREATE EXTENSION IF NOT EXISTS timescaledb; {{- else if eq .Values.type "documentdb" }} {{- $owner := .Values.cluster.initdb.owner | default .Values.cluster.initdb.database | default "app" }} - - CREATE EXTENSION IF NOT EXISTS pg_cron CASCADE; - CREATE EXTENSION IF NOT EXISTS documentdb CASCADE; - GRANT documentdb_admin_role TO {{ $owner }}; - GRANT USAGE ON SCHEMA documentdb_api TO {{ $owner }}; From 36e6cab4756d929258c0893eaa2a4b50e945108f Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 11 Nov 2025 15:50:34 +0100 Subject: [PATCH 16/21] fix: documentdb extension in app db --- charts/cluster/templates/_bootstrap.tpl | 27 ++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index c6022bcc7d..d200ffb84e 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -11,22 +11,11 @@ bootstrap: owner: {{ tpl .Values.cluster.initdb.owner . }} {{- end }} {{- if eq .Values.type "documentdb" }} - # pg_cron extension must be created in postgres database - # See: https://github.com/citusdata/pg_cron#installing-pg_cron + # Both pg_cron and documentdb extensions must be created in postgres database + # See: https://blog.ferretdb.io/run-ferretdb-postgres-documentdb-extension-cnpg-kubernetes/ + {{- $owner := .Values.cluster.initdb.owner | default .Values.cluster.initdb.database | default "app" }} postInitSQL: - CREATE EXTENSION IF NOT EXISTS pg_cron CASCADE; - {{- end }} - {{- if or (eq .Values.type "postgis") (eq .Values.type "timescaledb") (eq .Values.type "documentdb") (not (empty .Values.cluster.initdb.postInitApplicationSQL)) }} - postInitApplicationSQL: - {{- if eq .Values.type "postgis" }} - - CREATE EXTENSION IF NOT EXISTS postgis; - - CREATE EXTENSION IF NOT EXISTS postgis_topology; - - CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; - - CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; - {{- else if eq .Values.type "timescaledb" }} - - CREATE EXTENSION IF NOT EXISTS timescaledb; - {{- else if eq .Values.type "documentdb" }} - {{- $owner := .Values.cluster.initdb.owner | default .Values.cluster.initdb.database | default "app" }} - CREATE EXTENSION IF NOT EXISTS documentdb CASCADE; - GRANT documentdb_admin_role TO {{ $owner }}; - GRANT USAGE ON SCHEMA documentdb_api TO {{ $owner }}; @@ -55,6 +44,16 @@ bootstrap: - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_api_internal GRANT ALL ON SEQUENCES TO {{ $owner }}; - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_data GRANT ALL ON TABLES TO {{ $owner }}; - ALTER DEFAULT PRIVILEGES IN SCHEMA documentdb_data GRANT ALL ON SEQUENCES TO {{ $owner }}; + {{- end }} + {{- if or (eq .Values.type "postgis") (eq .Values.type "timescaledb") (not (empty .Values.cluster.initdb.postInitApplicationSQL)) }} + postInitApplicationSQL: + {{- if eq .Values.type "postgis" }} + - CREATE EXTENSION IF NOT EXISTS postgis; + - CREATE EXTENSION IF NOT EXISTS postgis_topology; + - CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; + - CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; + {{- else if eq .Values.type "timescaledb" }} + - CREATE EXTENSION IF NOT EXISTS timescaledb; {{- end }} {{- with .Values.cluster.initdb }} {{- range .postInitApplicationSQL }} From 4dac10c578ac0627d30ce26ab852ef1d34523e5e Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 11 Nov 2025 16:34:58 +0100 Subject: [PATCH 17/21] fix: confiugrable pg db ferretdb --- charts/cluster/templates/ferretdb.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/cluster/templates/ferretdb.yaml b/charts/cluster/templates/ferretdb.yaml index 11baadbadb..33ac924c4a 100644 --- a/charts/cluster/templates/ferretdb.yaml +++ b/charts/cluster/templates/ferretdb.yaml @@ -1,6 +1,6 @@ {{ if and (eq .Values.type "documentdb") .Values.ferretdb.enabled }} {{- $dbOwner := "postgres" }} -{{- $dbName := .Values.cluster.initdb.database | default "app" }} +{{- $dbName := .Values.ferretdb.database | default "postgres" }} --- apiVersion: apps/v1 kind: Deployment From 435bc06d8fe181aec5a06553810252ee5b50e934 Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 11 Nov 2025 20:31:03 +0100 Subject: [PATCH 18/21] fix: env secret ferretdb to pg --- charts/cluster/templates/ferretdb.yaml | 55 ++++---------------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/charts/cluster/templates/ferretdb.yaml b/charts/cluster/templates/ferretdb.yaml index 33ac924c4a..5e2f1ff2a3 100644 --- a/charts/cluster/templates/ferretdb.yaml +++ b/charts/cluster/templates/ferretdb.yaml @@ -22,29 +22,6 @@ spec: {{- include "cluster.selectorLabels" . | nindent 8 }} app.kubernetes.io/component: ferretdb spec: - # Init container to build PostgreSQL connection URL with password - initContainers: - - name: setup-pgurl - image: busybox:1.36 - # Run as same user as FerretDB container to ensure file permissions work - securityContext: - runAsUser: 1000 - runAsGroup: 1000 - command: ["/bin/sh", "-c"] - args: - - | - # Read password from secret and build connection URL - PASSWORD=$(cat /secrets/password) - cat > /pgurl/pgurl < Date: Tue, 11 Nov 2025 21:06:57 +0100 Subject: [PATCH 19/21] fix: connection var expansion --- charts/cluster/templates/ferretdb.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/charts/cluster/templates/ferretdb.yaml b/charts/cluster/templates/ferretdb.yaml index 5e2f1ff2a3..70b9a07ffd 100644 --- a/charts/cluster/templates/ferretdb.yaml +++ b/charts/cluster/templates/ferretdb.yaml @@ -32,6 +32,7 @@ spec: protocol: TCP args: - --listen-addr=:27017 + - --postgresql-url=postgresql://{{ $dbOwner }}:$(POSTGRES_PASSWORD)@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}{{- if .Values.ferretdb.sslMode }}?sslmode={{ .Values.ferretdb.sslMode }}{{- end }} - --telemetry={{ .Values.ferretdb.telemetry | default "disable" }} - --log-level={{ .Values.ferretdb.logLevel | default "info" }} {{- if .Values.ferretdb.debugAddr }} @@ -44,8 +45,6 @@ spec: - {{ . }} {{- end }} env: - - name: FERRETDB_POSTGRESQL_URL - value: "postgresql://{{ $dbOwner }}:$(POSTGRES_PASSWORD)@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}{{- if .Values.ferretdb.sslMode }}?sslmode={{ .Values.ferretdb.sslMode }}{{- end }}" - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: From 3afb601c45c51760218be01a2d1018acf1e32ba5 Mon Sep 17 00:00:00 2001 From: P-Louw Date: Tue, 11 Nov 2025 21:21:27 +0100 Subject: [PATCH 20/21] fix: env onl connectionstring --- charts/cluster/templates/ferretdb.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/charts/cluster/templates/ferretdb.yaml b/charts/cluster/templates/ferretdb.yaml index 70b9a07ffd..2f6c7435f6 100644 --- a/charts/cluster/templates/ferretdb.yaml +++ b/charts/cluster/templates/ferretdb.yaml @@ -31,8 +31,6 @@ spec: containerPort: 27017 protocol: TCP args: - - --listen-addr=:27017 - - --postgresql-url=postgresql://{{ $dbOwner }}:$(POSTGRES_PASSWORD)@{{ include "cluster.fullname" . }}-rw:5432/{{ $dbName }}{{- if .Values.ferretdb.sslMode }}?sslmode={{ .Values.ferretdb.sslMode }}{{- end }} - --telemetry={{ .Values.ferretdb.telemetry | default "disable" }} - --log-level={{ .Values.ferretdb.logLevel | default "info" }} {{- if .Values.ferretdb.debugAddr }} From 61b1ab1d856283a4a3c88a597176e9f16daa3c1f Mon Sep 17 00:00:00 2001 From: P-Louw Date: Wed, 12 Nov 2025 00:02:40 +0100 Subject: [PATCH 21/21] build: dev no auth flag --- charts/cluster/templates/ferretdb.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/charts/cluster/templates/ferretdb.yaml b/charts/cluster/templates/ferretdb.yaml index 2f6c7435f6..20e1a32cb4 100644 --- a/charts/cluster/templates/ferretdb.yaml +++ b/charts/cluster/templates/ferretdb.yaml @@ -33,6 +33,10 @@ spec: args: - --telemetry={{ .Values.ferretdb.telemetry | default "disable" }} - --log-level={{ .Values.ferretdb.logLevel | default "info" }} + - --postgresql-url=postgresql://postgres:$(POSTGRES_PASSWORD)@{{ include "cluster.fullname" . }}-rw.{{ include "cluster.namespace" . }}:5432/postgres + {{- if and (hasKey .Values.ferretdb "auth") (not .Values.ferretdb.auth) }} + - --no-auth + {{- end }} {{- if .Values.ferretdb.debugAddr }} - --debug-addr={{ .Values.ferretdb.debugAddr }} {{- end }}