diff --git a/.github/actions/verify-pooler-ready/action.yml b/.github/actions/verify-pooler-ready/action.yml deleted file mode 100644 index 38a7f493b..000000000 --- a/.github/actions/verify-pooler-ready/action.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Verifies that a CNPG cluster has a certain amount of ready instances -description: Verifies that a CNPG cluster has a certain amount of ready instances -inputs: - pooler-name: - description: The name of the pooler to verify - required: true - default: database-cluster - ready-instances: - description: The amount of ready instances to wait for - required: true - default: "3" - -runs: - using: composite - steps: - - name: Wait for the pooler to become ready - shell: bash - run: | - ITER=0 - while true; do - if [[ $ITER -ge 300 ]]; then - echo "Pooler not ready" - exit 1 - fi - READY_INSTANCES=$(kubectl get deployments.apps ${INPUT_POOLER_NAME} -o jsonpath='{.status.readyReplicas}') - if [[ "$READY_INSTANCES" == ${INPUT_READY_INSTANCES} ]]; then - echo "Pooler up and running" - break - fi - sleep 1 - (( ++ITER )) - done diff --git a/.github/minio.yaml b/.github/minio.yaml new file mode 100644 index 000000000..6bcb77acc --- /dev/null +++ b/.github/minio.yaml @@ -0,0 +1,9 @@ +tenant: + pools: + - servers: 1 + name: pool0 + volumesPerServer: 1 + size: 1Gi + buckets: + - name: mybucket + region: local diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6f40dcd39..07a5bd1ba 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,6 +5,9 @@ on: branches: - '**' - '!gh-pages' + pull_request: + branches-ignore: + - 'gh-pages' jobs: linter: @@ -20,7 +23,7 @@ jobs: with: version: v3.4.0 - - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: 3.7 diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 5854184dc..aec6959af 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -65,7 +65,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Install sigstore/cosign - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 + uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 - name: Push charts to GHCR env: diff --git a/.github/workflows/tests-cluster-chainsaw.yaml b/.github/workflows/tests-cluster-chainsaw.yaml new file mode 100644 index 000000000..555b3fe07 --- /dev/null +++ b/.github/workflows/tests-cluster-chainsaw.yaml @@ -0,0 +1,49 @@ +name: tests-cluster-chainsaw + +on: + pull_request: + branches-ignore: + - 'gh-pages' + +jobs: + test-cluster-standalone: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + fetch-depth: 0 + + - name: Setup kind + uses: ./.github/actions/setup-kind + + - name: Deploy the operator + uses: ./.github/actions/deploy-operator + + - name: Install Prometheus CRDs + run: | + helm repo add prometheus-community https://prometheus-community.github.io/helm-charts + helm install prometheus-crds prometheus-community/prometheus-operator-crds + + - name: Install Chainsaw + uses: kyverno/action-install-chainsaw@b2f61a8d0459a65c476ac802514d88e1612b3396 # v0.2.9 + + - name: Setup MinIO + run: | + helm repo add minio-operator https://operator.min.io + helm upgrade \ + --install \ + --namespace minio-system \ + --create-namespace \ + --wait \ + operator minio-operator/operator + helm upgrade \ + --install \ + --namespace minio \ + --create-namespace \ + --wait \ + --values ./.github/minio.yaml \ + tenant minio-operator/tenant + + - name: Run Kyverno/Chainsaw + run: chainsaw test diff --git a/.github/workflows/tests-cluster-standalone.yml b/.github/workflows/tests-cluster-standalone.yml deleted file mode 100644 index 99488d3f2..000000000 --- a/.github/workflows/tests-cluster-standalone.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: tests-cluster-standalone - -on: - pull_request: - branches-ignore: - - 'gh-pages' - -jobs: - test-cluster-standalone: - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - fetch-depth: 0 - - - name: Setup kind - uses: ./.github/actions/setup-kind - - - name: Deploy the operator - uses: ./.github/actions/deploy-operator - - - name: Deploy a standalone cluster - run: | - helm upgrade --install \ - --values charts/cluster/examples/basic.yaml \ - --namespace database \ - --create-namespace \ - --wait \ - database ./charts/cluster - - - name: Verify that the cluster is ready - uses: ./.github/actions/verify-cluster-ready - with: - cluster-name: database-cluster - ready-instances: 1 - - test-cluster-pgbouncer: - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - fetch-depth: 0 - - - name: Setup kind - uses: ./.github/actions/setup-kind - - - name: Deploy the operator - uses: ./.github/actions/deploy-operator - - - name: Deploy a standalone cluster - run: | - helm upgrade --install \ - --values charts/cluster/examples/pgbouncer.yaml \ - --namespace database \ - --create-namespace \ - --wait \ - database ./charts/cluster - - - name: Verify that the cluster is ready - uses: ./.github/actions/verify-cluster-ready - with: - cluster-name: database-cluster - ready-instances: 1 - - - name: Verify that the pooler is ready - uses: ./.github/actions/verify-pooler-ready - with: - pooler-name: database-cluster-pooler-rw - ready-instances: 1 diff --git a/README.md b/README.md index 67eccbd93..dda335965 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # CloudNativePG Helm Charts +[![Stack Overflow](https://img.shields.io/badge/stackoverflow-cloudnative--pg-blue?logo=stackoverflow&logoColor=%23F48024&link=https%3A%2F%2Fstackoverflow.com%2Fquestions%2Ftagged%2Fcloudnative-pg)][stackoverflow] +[![GitHub License](https://img.shields.io/github/license/cloudnative-pg/charts)][license] + + +[![GitHub Release](https://img.shields.io/github/v/release/cloudnative-pg/charts?filter=cloudnative-pg-*)](https://github.com/cloudnative-pg/charts/tree/main/charts/cloudnative-pg) +[![GitHub Release](https://img.shields.io/github/v/release/cloudnative-pg/charts?filter=cluster-*)](https://github.com/cloudnative-pg/charts/tree/main/charts/cluster) + + ## Operator chart Helm chart to install the @@ -40,3 +48,6 @@ Please read the [code of conduct](CODE-OF-CONDUCT.md) and the ## Copyright Helm charts for CloudNativePG are distributed under [Apache License 2.0](LICENSE). + +[stackoverflow]: https://stackoverflow.com/questions/tagged/cloudnative-pg +[license]: https://github.com/cloudnative-pg/charts?tab=Apache-2.0-1-ov-file diff --git a/charts/cloudnative-pg/Chart.yaml b/charts/cloudnative-pg/Chart.yaml index 4cd01ddd6..7187367cf 100644 --- a/charts/cloudnative-pg/Chart.yaml +++ b/charts/cloudnative-pg/Chart.yaml @@ -18,12 +18,12 @@ name: cloudnative-pg description: CloudNativePG Operator Helm Chart icon: https://raw.githubusercontent.com/cloudnative-pg/artwork/main/cloudnativepg-logo.svg type: application -version: "0.21.6" +version: "0.22.0" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning, they should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.23.3" +appVersion: "1.24.0" sources: - https://github.com/cloudnative-pg/charts keywords: diff --git a/charts/cloudnative-pg/README.md b/charts/cloudnative-pg/README.md index 70b14e5e2..3083109e8 100644 --- a/charts/cloudnative-pg/README.md +++ b/charts/cloudnative-pg/README.md @@ -1,6 +1,6 @@ # cloudnative-pg -![Version: 0.21.6](https://img.shields.io/badge/Version-0.21.6-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.23.3](https://img.shields.io/badge/AppVersion-1.23.3-informational?style=flat-square) +![Version: 0.22.0](https://img.shields.io/badge/Version-0.22.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.24.0](https://img.shields.io/badge/AppVersion-1.24.0-informational?style=flat-square) CloudNativePG Operator Helm Chart @@ -56,7 +56,7 @@ CloudNativePG Operator Helm Chart | monitoring.podMonitorMetricRelabelings | list | `[]` | Metrics relabel configurations to apply to samples before ingestion. | | monitoring.podMonitorRelabelings | list | `[]` | Relabel configurations to apply to samples before scraping. | | monitoringQueriesConfigMap.name | string | `"cnpg-default-monitoring"` | The name of the default monitoring configmap. | -| monitoringQueriesConfigMap.queries | string | `"backends:\n query: |\n SELECT sa.datname\n , sa.usename\n , sa.application_name\n , states.state\n , COALESCE(sa.count, 0) AS total\n , COALESCE(sa.max_tx_secs, 0) AS max_tx_duration_seconds\n FROM ( VALUES ('active')\n , ('idle')\n , ('idle in transaction')\n , ('idle in transaction (aborted)')\n , ('fastpath function call')\n , ('disabled')\n ) AS states(state)\n LEFT JOIN (\n SELECT datname\n , state\n , usename\n , COALESCE(application_name, '') AS application_name\n , COUNT(*)\n , COALESCE(EXTRACT (EPOCH FROM (max(now() - xact_start))), 0) AS max_tx_secs\n FROM pg_catalog.pg_stat_activity\n GROUP BY datname, state, usename, application_name\n ) sa ON states.state = sa.state\n WHERE sa.usename IS NOT NULL\n metrics:\n - datname:\n usage: \"LABEL\"\n description: \"Name of the database\"\n - usename:\n usage: \"LABEL\"\n description: \"Name of the user\"\n - application_name:\n usage: \"LABEL\"\n description: \"Name of the application\"\n - state:\n usage: \"LABEL\"\n description: \"State of the backend\"\n - total:\n usage: \"GAUGE\"\n description: \"Number of backends\"\n - max_tx_duration_seconds:\n usage: \"GAUGE\"\n description: \"Maximum duration of a transaction in seconds\"\n\nbackends_waiting:\n query: |\n SELECT count(*) AS total\n FROM pg_catalog.pg_locks blocked_locks\n JOIN pg_catalog.pg_locks blocking_locks\n ON blocking_locks.locktype = blocked_locks.locktype\n AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database\n AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation\n AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page\n AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple\n AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid\n AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid\n AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid\n AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid\n AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid\n AND blocking_locks.pid != blocked_locks.pid\n JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid\n WHERE NOT blocked_locks.granted\n metrics:\n - total:\n usage: \"GAUGE\"\n description: \"Total number of backends that are currently waiting on other queries\"\n\npg_database:\n query: |\n SELECT datname\n , pg_catalog.pg_database_size(datname) AS size_bytes\n , pg_catalog.age(datfrozenxid) AS xid_age\n , pg_catalog.mxid_age(datminmxid) AS mxid_age\n FROM pg_catalog.pg_database\n metrics:\n - datname:\n usage: \"LABEL\"\n description: \"Name of the database\"\n - size_bytes:\n usage: \"GAUGE\"\n description: \"Disk space used by the database\"\n - xid_age:\n usage: \"GAUGE\"\n description: \"Number of transactions from the frozen XID to the current one\"\n - mxid_age:\n usage: \"GAUGE\"\n description: \"Number of multiple transactions (Multixact) from the frozen XID to the current one\"\n\npg_postmaster:\n query: |\n SELECT EXTRACT(EPOCH FROM pg_postmaster_start_time) AS start_time\n FROM pg_catalog.pg_postmaster_start_time()\n metrics:\n - start_time:\n usage: \"GAUGE\"\n description: \"Time at which postgres started (based on epoch)\"\n\npg_replication:\n query: \"SELECT CASE WHEN (\n NOT pg_catalog.pg_is_in_recovery()\n OR pg_catalog.pg_last_wal_receive_lsn() = pg_catalog.pg_last_wal_replay_lsn())\n THEN 0\n ELSE GREATEST (0,\n EXTRACT(EPOCH FROM (now() - pg_catalog.pg_last_xact_replay_timestamp())))\n END AS lag,\n pg_catalog.pg_is_in_recovery() AS in_recovery,\n EXISTS (TABLE pg_stat_wal_receiver) AS is_wal_receiver_up,\n (SELECT count(*) FROM pg_catalog.pg_stat_replication) AS streaming_replicas\"\n metrics:\n - lag:\n usage: \"GAUGE\"\n description: \"Replication lag behind primary in seconds\"\n - in_recovery:\n usage: \"GAUGE\"\n description: \"Whether the instance is in recovery\"\n - is_wal_receiver_up:\n usage: \"GAUGE\"\n description: \"Whether the instance wal_receiver is up\"\n - streaming_replicas:\n usage: \"GAUGE\"\n description: \"Number of streaming replicas connected to the instance\"\n\npg_replication_slots:\n query: |\n SELECT slot_name,\n slot_type,\n database,\n active,\n (CASE pg_catalog.pg_is_in_recovery()\n WHEN TRUE THEN pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_last_wal_receive_lsn(), restart_lsn)\n ELSE pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), restart_lsn)\n END) as pg_wal_lsn_diff\n FROM pg_catalog.pg_replication_slots\n WHERE NOT temporary\n metrics:\n - slot_name:\n usage: \"LABEL\"\n description: \"Name of the replication slot\"\n - slot_type:\n usage: \"LABEL\"\n description: \"Type of the replication slot\"\n - database:\n usage: \"LABEL\"\n description: \"Name of the database\"\n - active:\n usage: \"GAUGE\"\n description: \"Flag indicating whether the slot is active\"\n - pg_wal_lsn_diff:\n usage: \"GAUGE\"\n description: \"Replication lag in bytes\"\n\npg_stat_archiver:\n query: |\n SELECT archived_count\n , failed_count\n , COALESCE(EXTRACT(EPOCH FROM (now() - last_archived_time)), -1) AS seconds_since_last_archival\n , COALESCE(EXTRACT(EPOCH FROM (now() - last_failed_time)), -1) AS seconds_since_last_failure\n , COALESCE(EXTRACT(EPOCH FROM last_archived_time), -1) AS last_archived_time\n , COALESCE(EXTRACT(EPOCH FROM last_failed_time), -1) AS last_failed_time\n , COALESCE(CAST(CAST('x'||pg_catalog.right(pg_catalog.split_part(last_archived_wal, '.', 1), 16) AS pg_catalog.bit(64)) AS pg_catalog.int8), -1) AS last_archived_wal_start_lsn\n , COALESCE(CAST(CAST('x'||pg_catalog.right(pg_catalog.split_part(last_failed_wal, '.', 1), 16) AS pg_catalog.bit(64)) AS pg_catalog.int8), -1) AS last_failed_wal_start_lsn\n , EXTRACT(EPOCH FROM stats_reset) AS stats_reset_time\n FROM pg_catalog.pg_stat_archiver\n metrics:\n - archived_count:\n usage: \"COUNTER\"\n description: \"Number of WAL files that have been successfully archived\"\n - failed_count:\n usage: \"COUNTER\"\n description: \"Number of failed attempts for archiving WAL files\"\n - seconds_since_last_archival:\n usage: \"GAUGE\"\n description: \"Seconds since the last successful archival operation\"\n - seconds_since_last_failure:\n usage: \"GAUGE\"\n description: \"Seconds since the last failed archival operation\"\n - last_archived_time:\n usage: \"GAUGE\"\n description: \"Epoch of the last time WAL archiving succeeded\"\n - last_failed_time:\n usage: \"GAUGE\"\n description: \"Epoch of the last time WAL archiving failed\"\n - last_archived_wal_start_lsn:\n usage: \"GAUGE\"\n description: \"Archived WAL start LSN\"\n - last_failed_wal_start_lsn:\n usage: \"GAUGE\"\n description: \"Last failed WAL LSN\"\n - stats_reset_time:\n usage: \"GAUGE\"\n description: \"Time at which these statistics were last reset\"\n\npg_stat_bgwriter:\n runonserver: \"<17.0.0\"\n query: |\n SELECT checkpoints_timed\n , checkpoints_req\n , checkpoint_write_time\n , checkpoint_sync_time\n , buffers_checkpoint\n , buffers_clean\n , maxwritten_clean\n , buffers_backend\n , buffers_backend_fsync\n , buffers_alloc\n FROM pg_catalog.pg_stat_bgwriter\n metrics:\n - checkpoints_timed:\n usage: \"COUNTER\"\n description: \"Number of scheduled checkpoints that have been performed\"\n - checkpoints_req:\n usage: \"COUNTER\"\n description: \"Number of requested checkpoints that have been performed\"\n - checkpoint_write_time:\n usage: \"COUNTER\"\n description: \"Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in milliseconds\"\n - checkpoint_sync_time:\n usage: \"COUNTER\"\n description: \"Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in milliseconds\"\n - buffers_checkpoint:\n usage: \"COUNTER\"\n description: \"Number of buffers written during checkpoints\"\n - buffers_clean:\n usage: \"COUNTER\"\n description: \"Number of buffers written by the background writer\"\n - maxwritten_clean:\n usage: \"COUNTER\"\n description: \"Number of times the background writer stopped a cleaning scan because it had written too many buffers\"\n - buffers_backend:\n usage: \"COUNTER\"\n description: \"Number of buffers written directly by a backend\"\n - buffers_backend_fsync:\n usage: \"COUNTER\"\n description: \"Number of times a backend had to execute its own fsync call (normally the background writer handles those even when the backend does its own write)\"\n - buffers_alloc:\n usage: \"COUNTER\"\n description: \"Number of buffers allocated\"\n\npg_stat_database:\n query: |\n SELECT datname\n , xact_commit\n , xact_rollback\n , blks_read\n , blks_hit\n , tup_returned\n , tup_fetched\n , tup_inserted\n , tup_updated\n , tup_deleted\n , conflicts\n , temp_files\n , temp_bytes\n , deadlocks\n , blk_read_time\n , blk_write_time\n FROM pg_catalog.pg_stat_database\n metrics:\n - datname:\n usage: \"LABEL\"\n description: \"Name of this database\"\n - xact_commit:\n usage: \"COUNTER\"\n description: \"Number of transactions in this database that have been committed\"\n - xact_rollback:\n usage: \"COUNTER\"\n description: \"Number of transactions in this database that have been rolled back\"\n - blks_read:\n usage: \"COUNTER\"\n description: \"Number of disk blocks read in this database\"\n - blks_hit:\n usage: \"COUNTER\"\n description: \"Number of times disk blocks were found already in the buffer cache, so that a read was not necessary (this only includes hits in the PostgreSQL buffer cache, not the operating system's file system cache)\"\n - tup_returned:\n usage: \"COUNTER\"\n description: \"Number of rows returned by queries in this database\"\n - tup_fetched:\n usage: \"COUNTER\"\n description: \"Number of rows fetched by queries in this database\"\n - tup_inserted:\n usage: \"COUNTER\"\n description: \"Number of rows inserted by queries in this database\"\n - tup_updated:\n usage: \"COUNTER\"\n description: \"Number of rows updated by queries in this database\"\n - tup_deleted:\n usage: \"COUNTER\"\n description: \"Number of rows deleted by queries in this database\"\n - conflicts:\n usage: \"COUNTER\"\n description: \"Number of queries canceled due to conflicts with recovery in this database\"\n - temp_files:\n usage: \"COUNTER\"\n description: \"Number of temporary files created by queries in this database\"\n - temp_bytes:\n usage: \"COUNTER\"\n description: \"Total amount of data written to temporary files by queries in this database\"\n - deadlocks:\n usage: \"COUNTER\"\n description: \"Number of deadlocks detected in this database\"\n - blk_read_time:\n usage: \"COUNTER\"\n description: \"Time spent reading data file blocks by backends in this database, in milliseconds\"\n - blk_write_time:\n usage: \"COUNTER\"\n description: \"Time spent writing data file blocks by backends in this database, in milliseconds\"\n\npg_stat_replication:\n primary: true\n query: |\n SELECT usename\n , COALESCE(application_name, '') AS application_name\n , COALESCE(client_addr::text, '') AS client_addr\n , COALESCE(client_port::text, '') AS client_port\n , EXTRACT(EPOCH FROM backend_start) AS backend_start\n , COALESCE(pg_catalog.age(backend_xmin), 0) AS backend_xmin_age\n , pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), sent_lsn) AS sent_diff_bytes\n , pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), write_lsn) AS write_diff_bytes\n , pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), flush_lsn) AS flush_diff_bytes\n , COALESCE(pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), replay_lsn),0) AS replay_diff_bytes\n , COALESCE((EXTRACT(EPOCH FROM write_lag)),0)::float AS write_lag_seconds\n , COALESCE((EXTRACT(EPOCH FROM flush_lag)),0)::float AS flush_lag_seconds\n , COALESCE((EXTRACT(EPOCH FROM replay_lag)),0)::float AS replay_lag_seconds\n FROM pg_catalog.pg_stat_replication\n metrics:\n - usename:\n usage: \"LABEL\"\n description: \"Name of the replication user\"\n - application_name:\n usage: \"LABEL\"\n description: \"Name of the application\"\n - client_addr:\n usage: \"LABEL\"\n description: \"Client IP address\"\n - client_port:\n usage: \"LABEL\"\n description: \"Client TCP port\"\n - backend_start:\n usage: \"COUNTER\"\n description: \"Time when this process was started\"\n - backend_xmin_age:\n usage: \"COUNTER\"\n description: \"The age of this standby's xmin horizon\"\n - sent_diff_bytes:\n usage: \"GAUGE\"\n description: \"Difference in bytes from the last write-ahead log location sent on this connection\"\n - write_diff_bytes:\n usage: \"GAUGE\"\n description: \"Difference in bytes from the last write-ahead log location written to disk by this standby server\"\n - flush_diff_bytes:\n usage: \"GAUGE\"\n description: \"Difference in bytes from the last write-ahead log location flushed to disk by this standby server\"\n - replay_diff_bytes:\n usage: \"GAUGE\"\n description: \"Difference in bytes from the last write-ahead log location replayed into the database on this standby server\"\n - write_lag_seconds:\n usage: \"GAUGE\"\n description: \"Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it\"\n - flush_lag_seconds:\n usage: \"GAUGE\"\n description: \"Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it\"\n - replay_lag_seconds:\n usage: \"GAUGE\"\n description: \"Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it\"\n\npg_settings:\n query: |\n SELECT name,\n CASE setting WHEN 'on' THEN '1' WHEN 'off' THEN '0' ELSE setting END AS setting\n FROM pg_catalog.pg_settings\n WHERE vartype IN ('integer', 'real', 'bool')\n ORDER BY 1\n metrics:\n - name:\n usage: \"LABEL\"\n description: \"Name of the setting\"\n - setting:\n usage: \"GAUGE\"\n description: \"Setting value\"\n"` | A string representation of a YAML defining monitoring queries. | +| monitoringQueriesConfigMap.queries | string | `"backends:\n query: |\n SELECT sa.datname\n , sa.usename\n , sa.application_name\n , states.state\n , COALESCE(sa.count, 0) AS total\n , COALESCE(sa.max_tx_secs, 0) AS max_tx_duration_seconds\n FROM ( VALUES ('active')\n , ('idle')\n , ('idle in transaction')\n , ('idle in transaction (aborted)')\n , ('fastpath function call')\n , ('disabled')\n ) AS states(state)\n LEFT JOIN (\n SELECT datname\n , state\n , usename\n , COALESCE(application_name, '') AS application_name\n , COUNT(*)\n , COALESCE(EXTRACT (EPOCH FROM (max(now() - xact_start))), 0) AS max_tx_secs\n FROM pg_catalog.pg_stat_activity\n GROUP BY datname, state, usename, application_name\n ) sa ON states.state = sa.state\n WHERE sa.usename IS NOT NULL\n metrics:\n - datname:\n usage: \"LABEL\"\n description: \"Name of the database\"\n - usename:\n usage: \"LABEL\"\n description: \"Name of the user\"\n - application_name:\n usage: \"LABEL\"\n description: \"Name of the application\"\n - state:\n usage: \"LABEL\"\n description: \"State of the backend\"\n - total:\n usage: \"GAUGE\"\n description: \"Number of backends\"\n - max_tx_duration_seconds:\n usage: \"GAUGE\"\n description: \"Maximum duration of a transaction in seconds\"\n\nbackends_waiting:\n query: |\n SELECT count(*) AS total\n FROM pg_catalog.pg_locks blocked_locks\n JOIN pg_catalog.pg_locks blocking_locks\n ON blocking_locks.locktype = blocked_locks.locktype\n AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database\n AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation\n AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page\n AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple\n AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid\n AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid\n AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid\n AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid\n AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid\n AND blocking_locks.pid != blocked_locks.pid\n JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid\n WHERE NOT blocked_locks.granted\n metrics:\n - total:\n usage: \"GAUGE\"\n description: \"Total number of backends that are currently waiting on other queries\"\n\npg_database:\n query: |\n SELECT datname\n , pg_catalog.pg_database_size(datname) AS size_bytes\n , pg_catalog.age(datfrozenxid) AS xid_age\n , pg_catalog.mxid_age(datminmxid) AS mxid_age\n FROM pg_catalog.pg_database\n WHERE datallowconn\n metrics:\n - datname:\n usage: \"LABEL\"\n description: \"Name of the database\"\n - size_bytes:\n usage: \"GAUGE\"\n description: \"Disk space used by the database\"\n - xid_age:\n usage: \"GAUGE\"\n description: \"Number of transactions from the frozen XID to the current one\"\n - mxid_age:\n usage: \"GAUGE\"\n description: \"Number of multiple transactions (Multixact) from the frozen XID to the current one\"\n\npg_postmaster:\n query: |\n SELECT EXTRACT(EPOCH FROM pg_postmaster_start_time) AS start_time\n FROM pg_catalog.pg_postmaster_start_time()\n metrics:\n - start_time:\n usage: \"GAUGE\"\n description: \"Time at which postgres started (based on epoch)\"\n\npg_replication:\n query: \"SELECT CASE WHEN (\n NOT pg_catalog.pg_is_in_recovery()\n OR pg_catalog.pg_last_wal_receive_lsn() = pg_catalog.pg_last_wal_replay_lsn())\n THEN 0\n ELSE GREATEST (0,\n EXTRACT(EPOCH FROM (now() - pg_catalog.pg_last_xact_replay_timestamp())))\n END AS lag,\n pg_catalog.pg_is_in_recovery() AS in_recovery,\n EXISTS (TABLE pg_stat_wal_receiver) AS is_wal_receiver_up,\n (SELECT count(*) FROM pg_catalog.pg_stat_replication) AS streaming_replicas\"\n metrics:\n - lag:\n usage: \"GAUGE\"\n description: \"Replication lag behind primary in seconds\"\n - in_recovery:\n usage: \"GAUGE\"\n description: \"Whether the instance is in recovery\"\n - is_wal_receiver_up:\n usage: \"GAUGE\"\n description: \"Whether the instance wal_receiver is up\"\n - streaming_replicas:\n usage: \"GAUGE\"\n description: \"Number of streaming replicas connected to the instance\"\n\npg_replication_slots:\n query: |\n SELECT slot_name,\n slot_type,\n database,\n active,\n (CASE pg_catalog.pg_is_in_recovery()\n WHEN TRUE THEN pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_last_wal_receive_lsn(), restart_lsn)\n ELSE pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), restart_lsn)\n END) as pg_wal_lsn_diff\n FROM pg_catalog.pg_replication_slots\n WHERE NOT temporary\n metrics:\n - slot_name:\n usage: \"LABEL\"\n description: \"Name of the replication slot\"\n - slot_type:\n usage: \"LABEL\"\n description: \"Type of the replication slot\"\n - database:\n usage: \"LABEL\"\n description: \"Name of the database\"\n - active:\n usage: \"GAUGE\"\n description: \"Flag indicating whether the slot is active\"\n - pg_wal_lsn_diff:\n usage: \"GAUGE\"\n description: \"Replication lag in bytes\"\n\npg_stat_archiver:\n query: |\n SELECT archived_count\n , failed_count\n , COALESCE(EXTRACT(EPOCH FROM (now() - last_archived_time)), -1) AS seconds_since_last_archival\n , COALESCE(EXTRACT(EPOCH FROM (now() - last_failed_time)), -1) AS seconds_since_last_failure\n , COALESCE(EXTRACT(EPOCH FROM last_archived_time), -1) AS last_archived_time\n , COALESCE(EXTRACT(EPOCH FROM last_failed_time), -1) AS last_failed_time\n , COALESCE(CAST(CAST('x'||pg_catalog.right(pg_catalog.split_part(last_archived_wal, '.', 1), 16) AS pg_catalog.bit(64)) AS pg_catalog.int8), -1) AS last_archived_wal_start_lsn\n , COALESCE(CAST(CAST('x'||pg_catalog.right(pg_catalog.split_part(last_failed_wal, '.', 1), 16) AS pg_catalog.bit(64)) AS pg_catalog.int8), -1) AS last_failed_wal_start_lsn\n , EXTRACT(EPOCH FROM stats_reset) AS stats_reset_time\n FROM pg_catalog.pg_stat_archiver\n metrics:\n - archived_count:\n usage: \"COUNTER\"\n description: \"Number of WAL files that have been successfully archived\"\n - failed_count:\n usage: \"COUNTER\"\n description: \"Number of failed attempts for archiving WAL files\"\n - seconds_since_last_archival:\n usage: \"GAUGE\"\n description: \"Seconds since the last successful archival operation\"\n - seconds_since_last_failure:\n usage: \"GAUGE\"\n description: \"Seconds since the last failed archival operation\"\n - last_archived_time:\n usage: \"GAUGE\"\n description: \"Epoch of the last time WAL archiving succeeded\"\n - last_failed_time:\n usage: \"GAUGE\"\n description: \"Epoch of the last time WAL archiving failed\"\n - last_archived_wal_start_lsn:\n usage: \"GAUGE\"\n description: \"Archived WAL start LSN\"\n - last_failed_wal_start_lsn:\n usage: \"GAUGE\"\n description: \"Last failed WAL LSN\"\n - stats_reset_time:\n usage: \"GAUGE\"\n description: \"Time at which these statistics were last reset\"\n\npg_stat_bgwriter:\n runonserver: \"<17.0.0\"\n query: |\n SELECT checkpoints_timed\n , checkpoints_req\n , checkpoint_write_time\n , checkpoint_sync_time\n , buffers_checkpoint\n , buffers_clean\n , maxwritten_clean\n , buffers_backend\n , buffers_backend_fsync\n , buffers_alloc\n FROM pg_catalog.pg_stat_bgwriter\n metrics:\n - checkpoints_timed:\n usage: \"COUNTER\"\n description: \"Number of scheduled checkpoints that have been performed\"\n - checkpoints_req:\n usage: \"COUNTER\"\n description: \"Number of requested checkpoints that have been performed\"\n - checkpoint_write_time:\n usage: \"COUNTER\"\n description: \"Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in milliseconds\"\n - checkpoint_sync_time:\n usage: \"COUNTER\"\n description: \"Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in milliseconds\"\n - buffers_checkpoint:\n usage: \"COUNTER\"\n description: \"Number of buffers written during checkpoints\"\n - buffers_clean:\n usage: \"COUNTER\"\n description: \"Number of buffers written by the background writer\"\n - maxwritten_clean:\n usage: \"COUNTER\"\n description: \"Number of times the background writer stopped a cleaning scan because it had written too many buffers\"\n - buffers_backend:\n usage: \"COUNTER\"\n description: \"Number of buffers written directly by a backend\"\n - buffers_backend_fsync:\n usage: \"COUNTER\"\n description: \"Number of times a backend had to execute its own fsync call (normally the background writer handles those even when the backend does its own write)\"\n - buffers_alloc:\n usage: \"COUNTER\"\n description: \"Number of buffers allocated\"\n\npg_stat_bgwriter_17:\n runonserver: \">=17.0.0\"\n name: pg_stat_bgwriter\n query: |\n SELECT buffers_clean\n , maxwritten_clean\n , buffers_alloc\n , EXTRACT(EPOCH FROM stats_reset) AS stats_reset_time\n FROM pg_catalog.pg_stat_bgwriter\n metrics:\n - buffers_clean:\n usage: \"COUNTER\"\n description: \"Number of buffers written by the background writer\"\n - maxwritten_clean:\n usage: \"COUNTER\"\n description: \"Number of times the background writer stopped a cleaning scan because it had written too many buffers\"\n - buffers_alloc:\n usage: \"COUNTER\"\n description: \"Number of buffers allocated\"\n - stats_reset_time:\n usage: \"GAUGE\"\n description: \"Time at which these statistics were last reset\"\n\npg_stat_checkpointer:\n runonserver: \">=17.0.0\"\n query: |\n SELECT num_timed AS checkpoints_timed\n , num_requested AS checkpoints_req\n , restartpoints_timed\n , restartpoints_req\n , restartpoints_done\n , write_time\n , sync_time\n , buffers_written\n , EXTRACT(EPOCH FROM stats_reset) AS stats_reset_time\n FROM pg_catalog.pg_stat_checkpointer\n metrics:\n - checkpoints_timed:\n usage: \"COUNTER\"\n description: \"Number of scheduled checkpoints that have been performed\"\n - checkpoints_req:\n usage: \"COUNTER\"\n description: \"Number of requested checkpoints that have been performed\"\n - restartpoints_timed:\n usage: \"COUNTER\"\n description: \"Number of scheduled restartpoints due to timeout or after a failed attempt to perform it\"\n - restartpoints_req:\n usage: \"COUNTER\"\n description: \"Number of requested restartpoints that have been performed\"\n - restartpoints_done:\n usage: \"COUNTER\"\n description: \"Number of restartpoints that have been performed\"\n - write_time:\n usage: \"COUNTER\"\n description: \"Total amount of time that has been spent in the portion of processing checkpoints and restartpoints where files are written to disk, in milliseconds\"\n - sync_time:\n usage: \"COUNTER\"\n description: \"Total amount of time that has been spent in the portion of processing checkpoints and restartpoints where files are synchronized to disk, in milliseconds\"\n - buffers_written:\n usage: \"COUNTER\"\n description: \"Number of buffers written during checkpoints and restartpoints\"\n - stats_reset_time:\n usage: \"GAUGE\"\n description: \"Time at which these statistics were last reset\"\n\npg_stat_database:\n query: |\n SELECT datname\n , xact_commit\n , xact_rollback\n , blks_read\n , blks_hit\n , tup_returned\n , tup_fetched\n , tup_inserted\n , tup_updated\n , tup_deleted\n , conflicts\n , temp_files\n , temp_bytes\n , deadlocks\n , blk_read_time\n , blk_write_time\n FROM pg_catalog.pg_stat_database\n metrics:\n - datname:\n usage: \"LABEL\"\n description: \"Name of this database\"\n - xact_commit:\n usage: \"COUNTER\"\n description: \"Number of transactions in this database that have been committed\"\n - xact_rollback:\n usage: \"COUNTER\"\n description: \"Number of transactions in this database that have been rolled back\"\n - blks_read:\n usage: \"COUNTER\"\n description: \"Number of disk blocks read in this database\"\n - blks_hit:\n usage: \"COUNTER\"\n description: \"Number of times disk blocks were found already in the buffer cache, so that a read was not necessary (this only includes hits in the PostgreSQL buffer cache, not the operating system's file system cache)\"\n - tup_returned:\n usage: \"COUNTER\"\n description: \"Number of rows returned by queries in this database\"\n - tup_fetched:\n usage: \"COUNTER\"\n description: \"Number of rows fetched by queries in this database\"\n - tup_inserted:\n usage: \"COUNTER\"\n description: \"Number of rows inserted by queries in this database\"\n - tup_updated:\n usage: \"COUNTER\"\n description: \"Number of rows updated by queries in this database\"\n - tup_deleted:\n usage: \"COUNTER\"\n description: \"Number of rows deleted by queries in this database\"\n - conflicts:\n usage: \"COUNTER\"\n description: \"Number of queries canceled due to conflicts with recovery in this database\"\n - temp_files:\n usage: \"COUNTER\"\n description: \"Number of temporary files created by queries in this database\"\n - temp_bytes:\n usage: \"COUNTER\"\n description: \"Total amount of data written to temporary files by queries in this database\"\n - deadlocks:\n usage: \"COUNTER\"\n description: \"Number of deadlocks detected in this database\"\n - blk_read_time:\n usage: \"COUNTER\"\n description: \"Time spent reading data file blocks by backends in this database, in milliseconds\"\n - blk_write_time:\n usage: \"COUNTER\"\n description: \"Time spent writing data file blocks by backends in this database, in milliseconds\"\n\npg_stat_replication:\n primary: true\n query: |\n SELECT usename\n , COALESCE(application_name, '') AS application_name\n , COALESCE(client_addr::text, '') AS client_addr\n , COALESCE(client_port::text, '') AS client_port\n , EXTRACT(EPOCH FROM backend_start) AS backend_start\n , COALESCE(pg_catalog.age(backend_xmin), 0) AS backend_xmin_age\n , pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), sent_lsn) AS sent_diff_bytes\n , pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), write_lsn) AS write_diff_bytes\n , pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), flush_lsn) AS flush_diff_bytes\n , COALESCE(pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), replay_lsn),0) AS replay_diff_bytes\n , COALESCE((EXTRACT(EPOCH FROM write_lag)),0)::float AS write_lag_seconds\n , COALESCE((EXTRACT(EPOCH FROM flush_lag)),0)::float AS flush_lag_seconds\n , COALESCE((EXTRACT(EPOCH FROM replay_lag)),0)::float AS replay_lag_seconds\n FROM pg_catalog.pg_stat_replication\n metrics:\n - usename:\n usage: \"LABEL\"\n description: \"Name of the replication user\"\n - application_name:\n usage: \"LABEL\"\n description: \"Name of the application\"\n - client_addr:\n usage: \"LABEL\"\n description: \"Client IP address\"\n - client_port:\n usage: \"LABEL\"\n description: \"Client TCP port\"\n - backend_start:\n usage: \"COUNTER\"\n description: \"Time when this process was started\"\n - backend_xmin_age:\n usage: \"COUNTER\"\n description: \"The age of this standby's xmin horizon\"\n - sent_diff_bytes:\n usage: \"GAUGE\"\n description: \"Difference in bytes from the last write-ahead log location sent on this connection\"\n - write_diff_bytes:\n usage: \"GAUGE\"\n description: \"Difference in bytes from the last write-ahead log location written to disk by this standby server\"\n - flush_diff_bytes:\n usage: \"GAUGE\"\n description: \"Difference in bytes from the last write-ahead log location flushed to disk by this standby server\"\n - replay_diff_bytes:\n usage: \"GAUGE\"\n description: \"Difference in bytes from the last write-ahead log location replayed into the database on this standby server\"\n - write_lag_seconds:\n usage: \"GAUGE\"\n description: \"Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it\"\n - flush_lag_seconds:\n usage: \"GAUGE\"\n description: \"Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it\"\n - replay_lag_seconds:\n usage: \"GAUGE\"\n description: \"Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it\"\n\npg_settings:\n query: |\n SELECT name,\n CASE setting WHEN 'on' THEN '1' WHEN 'off' THEN '0' ELSE setting END AS setting\n FROM pg_catalog.pg_settings\n WHERE vartype IN ('integer', 'real', 'bool')\n ORDER BY 1\n metrics:\n - name:\n usage: \"LABEL\"\n description: \"Name of the setting\"\n - setting:\n usage: \"GAUGE\"\n description: \"Setting value\"\n"` | A string representation of a YAML defining monitoring queries. | | nameOverride | string | `""` | | | nodeSelector | object | `{}` | Nodeselector for the operator to be installed. | | podAnnotations | object | `{}` | Annotations to be added to the pod. | diff --git a/charts/cloudnative-pg/templates/crds/crds.yaml b/charts/cloudnative-pg/templates/crds/crds.yaml index 0730228e7..c16c02c09 100644 --- a/charts/cloudnative-pg/templates/crds/crds.yaml +++ b/charts/cloudnative-pg/templates/crds/crds.yaml @@ -1844,6 +1844,26 @@ spec: When not defined, WAL files will be stored uncompressed and may be unencrypted in the object store, according to the bucket default policy. properties: + archiveAdditionalCommandArgs: + description: |- + Additional arguments that can be appended to the 'barman-cloud-wal-archive' + command-line invocation. These arguments provide flexibility to customize + the WAL archive process further, according to specific requirements or configurations. + + + Example: + In a scenario where specialized backup options are required, such as setting + a specific timeout or defining custom behavior, users can use this field + to specify additional command arguments. + + + Note: + It's essential to ensure that the provided arguments are valid and supported + by the 'barman-cloud-wal-archive' command, to avoid potential errors or unintended + behavior during execution. + items: + type: string + type: array compression: description: |- Compress a WAL file before sending it to the object store. Available @@ -1873,6 +1893,26 @@ spec: value - with 1 being the minimum accepted value. minimum: 1 type: integer + restoreAdditionalCommandArgs: + description: |- + Additional arguments that can be appended to the 'barman-cloud-wal-restore' + command-line invocation. These arguments provide flexibility to customize + the WAL restore process further, according to specific requirements or configurations. + + + Example: + In a scenario where specialized backup options are required, such as setting + a specific timeout or defining custom behavior, users can use this field + to specify additional command arguments. + + + Note: + It's essential to ensure that the provided arguments are valid and supported + by the 'barman-cloud-wal-restore' command, to avoid potential errors or unintended + behavior during execution. + items: + type: string + type: array type: object required: - destinationPath @@ -2069,17 +2109,19 @@ spec: postInitApplicationSQL: description: |- List of SQL queries to be executed as a superuser in the application - database right after is created - to be used with extreme care + database right after the cluster has been created - to be used with extreme care (by default empty) items: type: string type: array postInitApplicationSQLRefs: description: |- - PostInitApplicationSQLRefs points references to ConfigMaps or Secrets which - contain SQL files, the general implementation order to these references is - from all Secrets to all ConfigMaps, and inside Secrets or ConfigMaps, - the implementation order is same as the order of each array + List of references to ConfigMaps or Secrets containing SQL files + to be executed as a superuser in the application database right after + the cluster has been created. The references are processed in a specific order: + first, all Secrets are processed, followed by all ConfigMaps. + Within each group, the processing order follows the sequence specified + in their respective arrays. (by default empty) properties: configMapRefs: @@ -2123,20 +2165,118 @@ spec: type: object postInitSQL: description: |- - List of SQL queries to be executed as a superuser immediately - after the cluster has been created - to be used with extreme care + List of SQL queries to be executed as a superuser in the `postgres` + database right after the cluster has been created - to be used with extreme care (by default empty) items: type: string type: array + postInitSQLRefs: + description: |- + List of references to ConfigMaps or Secrets containing SQL files + to be executed as a superuser in the `postgres` database right after + the cluster has been created. The references are processed in a specific order: + first, all Secrets are processed, followed by all ConfigMaps. + Within each group, the processing order follows the sequence specified + in their respective arrays. + (by default empty) + properties: + configMapRefs: + description: ConfigMapRefs holds a list of references + to ConfigMaps + items: + description: |- + ConfigMapKeySelector contains enough information to let you locate + the key of a ConfigMap + properties: + key: + description: The key to select + type: string + name: + description: Name of the referent. + type: string + required: + - key + - name + type: object + type: array + secretRefs: + description: SecretRefs holds a list of references to + Secrets + items: + description: |- + SecretKeySelector contains enough information to let you locate + the key of a Secret + properties: + key: + description: The key to select + type: string + name: + description: Name of the referent. + type: string + required: + - key + - name + type: object + type: array + type: object postInitTemplateSQL: description: |- List of SQL queries to be executed as a superuser in the `template1` - after the cluster has been created - to be used with extreme care + database right after the cluster has been created - to be used with extreme care (by default empty) items: type: string type: array + postInitTemplateSQLRefs: + description: |- + List of references to ConfigMaps or Secrets containing SQL files + to be executed as a superuser in the `template1` database right after + the cluster has been created. The references are processed in a specific order: + first, all Secrets are processed, followed by all ConfigMaps. + Within each group, the processing order follows the sequence specified + in their respective arrays. + (by default empty) + properties: + configMapRefs: + description: ConfigMapRefs holds a list of references + to ConfigMaps + items: + description: |- + ConfigMapKeySelector contains enough information to let you locate + the key of a ConfigMap + properties: + key: + description: The key to select + type: string + name: + description: Name of the referent. + type: string + required: + - key + - name + type: object + type: array + secretRefs: + description: SecretRefs holds a list of references to + Secrets + items: + description: |- + SecretKeySelector contains enough information to let you locate + the key of a Secret + properties: + key: + description: The key to select + type: string + name: + description: Name of the referent. + type: string + required: + - key + - name + type: object + type: array + type: object secret: description: |- Name of the secret containing the initial credentials for the @@ -3168,6 +3308,26 @@ spec: When not defined, WAL files will be stored uncompressed and may be unencrypted in the object store, according to the bucket default policy. properties: + archiveAdditionalCommandArgs: + description: |- + Additional arguments that can be appended to the 'barman-cloud-wal-archive' + command-line invocation. These arguments provide flexibility to customize + the WAL archive process further, according to specific requirements or configurations. + + + Example: + In a scenario where specialized backup options are required, such as setting + a specific timeout or defining custom behavior, users can use this field + to specify additional command arguments. + + + Note: + It's essential to ensure that the provided arguments are valid and supported + by the 'barman-cloud-wal-archive' command, to avoid potential errors or unintended + behavior during execution. + items: + type: string + type: array compression: description: |- Compress a WAL file before sending it to the object store. Available @@ -3197,6 +3357,26 @@ spec: value - with 1 being the minimum accepted value. minimum: 1 type: integer + restoreAdditionalCommandArgs: + description: |- + Additional arguments that can be appended to the 'barman-cloud-wal-restore' + command-line invocation. These arguments provide flexibility to customize + the WAL restore process further, according to specific requirements or configurations. + + + Example: + In a scenario where specialized backup options are required, such as setting + a specific timeout or defining custom behavior, users can use this field + to specify additional command arguments. + + + Note: + It's essential to ensure that the provided arguments are valid and supported + by the 'barman-cloud-wal-restore' command, to avoid potential errors or unintended + behavior during execution. + items: + type: string + type: array type: object required: - destinationPath @@ -3554,6 +3734,443 @@ spec: - name type: object type: array + services: + description: Services roles managed by the `Cluster` + properties: + additional: + description: Additional is a list of additional managed services + specified by the user. + items: + description: |- + ManagedService represents a specific service managed by the cluster. + It includes the type of service and its associated template specification. + properties: + selectorType: + allOf: + - enum: + - rw + - r + - ro + - enum: + - rw + - r + - ro + description: |- + SelectorType specifies the type of selectors that the service will have. + Valid values are "rw", "r", and "ro", representing read-write, read, and read-only services. + type: string + serviceTemplate: + description: ServiceTemplate is the template specification + for the service. + properties: + metadata: + description: |- + Standard object's metadata. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is an unstructured key value map stored with a resource that may be + set by external tools to store and retrieve arbitrary metadata. They are not + queryable and should be preserved when modifying objects. + More info: http://kubernetes.io/docs/user-guide/annotations + type: object + labels: + additionalProperties: + type: string + description: |- + Map of string keys and values that can be used to organize and categorize + (scope and select) objects. May match selectors of replication controllers + and services. + More info: http://kubernetes.io/docs/user-guide/labels + type: object + name: + description: The name of the resource. Only + supported for certain types + type: string + type: object + spec: + description: |- + Specification of the desired behavior of the service. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + allocateLoadBalancerNodePorts: + description: |- + allocateLoadBalancerNodePorts defines if NodePorts will be automatically + allocated for services with type LoadBalancer. Default is "true". It + may be set to "false" if the cluster load-balancer does not rely on + NodePorts. If the caller requests specific NodePorts (by specifying a + value), those requests will be respected, regardless of this field. + This field may only be set for services with type LoadBalancer and will + be cleared if the type is changed to any other type. + type: boolean + clusterIP: + description: |- + clusterIP is the IP address of the service and is usually assigned + randomly. If an address is specified manually, is in-range (as per + system configuration), and is not in use, it will be allocated to the + service; otherwise creation of the service will fail. This field may not + be changed through updates unless the type field is also being changed + to ExternalName (which requires this field to be blank) or the type + field is being changed from ExternalName (in which case this field may + optionally be specified, as describe above). Valid values are "None", + empty string (""), or a valid IP address. Setting this to "None" makes a + "headless service" (no virtual IP), which is useful when direct endpoint + connections are preferred and proxying is not required. Only applies to + types ClusterIP, NodePort, and LoadBalancer. If this field is specified + when creating a Service of type ExternalName, creation will fail. This + field will be wiped when updating a Service to type ExternalName. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + type: string + clusterIPs: + description: |- + ClusterIPs is a list of IP addresses assigned to this service, and are + usually assigned randomly. If an address is specified manually, is + in-range (as per system configuration), and is not in use, it will be + allocated to the service; otherwise creation of the service will fail. + This field may not be changed through updates unless the type field is + also being changed to ExternalName (which requires this field to be + empty) or the type field is being changed from ExternalName (in which + case this field may optionally be specified, as describe above). Valid + values are "None", empty string (""), or a valid IP address. Setting + this to "None" makes a "headless service" (no virtual IP), which is + useful when direct endpoint connections are preferred and proxying is + not required. Only applies to types ClusterIP, NodePort, and + LoadBalancer. If this field is specified when creating a Service of type + ExternalName, creation will fail. This field will be wiped when updating + a Service to type ExternalName. If this field is not specified, it will + be initialized from the clusterIP field. If this field is specified, + clients must ensure that clusterIPs[0] and clusterIP have the same + value. + + + This field may hold a maximum of two entries (dual-stack IPs, in either order). + These IPs must correspond to the values of the ipFamilies field. Both + clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + items: + type: string + type: array + x-kubernetes-list-type: atomic + externalIPs: + description: |- + externalIPs is a list of IP addresses for which nodes in the cluster + will also accept traffic for this service. These IPs are not managed by + Kubernetes. The user is responsible for ensuring that traffic arrives + at a node with this IP. A common example is external load-balancers + that are not part of the Kubernetes system. + items: + type: string + type: array + x-kubernetes-list-type: atomic + externalName: + description: |- + externalName is the external reference that discovery mechanisms will + return as an alias for this service (e.g. a DNS CNAME record). No + proxying will be involved. Must be a lowercase RFC-1123 hostname + (https://tools.ietf.org/html/rfc1123) and requires `type` to be "ExternalName". + type: string + externalTrafficPolicy: + description: |- + externalTrafficPolicy describes how nodes distribute service traffic they + receive on one of the Service's "externally-facing" addresses (NodePorts, + ExternalIPs, and LoadBalancer IPs). If set to "Local", the proxy will configure + the service in a way that assumes that external load balancers will take care + of balancing the service traffic between nodes, and so each node will deliver + traffic only to the node-local endpoints of the service, without masquerading + the client source IP. (Traffic mistakenly sent to a node with no endpoints will + be dropped.) The default value, "Cluster", uses the standard behavior of + routing to all endpoints evenly (possibly modified by topology and other + features). Note that traffic sent to an External IP or LoadBalancer IP from + within the cluster will always get "Cluster" semantics, but clients sending to + a NodePort from within the cluster may need to take traffic policy into account + when picking a node. + type: string + healthCheckNodePort: + description: |- + healthCheckNodePort specifies the healthcheck nodePort for the service. + This only applies when type is set to LoadBalancer and + externalTrafficPolicy is set to Local. If a value is specified, is + in-range, and is not in use, it will be used. If not specified, a value + will be automatically allocated. External systems (e.g. load-balancers) + can use this port to determine if a given node holds endpoints for this + service or not. If this field is specified when creating a Service + which does not need it, creation will fail. This field will be wiped + when updating a Service to no longer need it (e.g. changing type). + This field cannot be updated once set. + format: int32 + type: integer + internalTrafficPolicy: + description: |- + InternalTrafficPolicy describes how nodes distribute service traffic they + receive on the ClusterIP. If set to "Local", the proxy will assume that pods + only want to talk to endpoints of the service on the same node as the pod, + dropping the traffic if there are no local endpoints. The default value, + "Cluster", uses the standard behavior of routing to all endpoints evenly + (possibly modified by topology and other features). + type: string + ipFamilies: + description: |- + IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this + service. This field is usually assigned automatically based on cluster + configuration and the ipFamilyPolicy field. If this field is specified + manually, the requested family is available in the cluster, + and ipFamilyPolicy allows it, it will be used; otherwise creation of + the service will fail. This field is conditionally mutable: it allows + for adding or removing a secondary IP family, but it does not allow + changing the primary IP family of the Service. Valid values are "IPv4" + and "IPv6". This field only applies to Services of types ClusterIP, + NodePort, and LoadBalancer, and does apply to "headless" services. + This field will be wiped when updating a Service to type ExternalName. + + + This field may hold a maximum of two entries (dual-stack families, in + either order). These families must correspond to the values of the + clusterIPs field, if specified. Both clusterIPs and ipFamilies are + governed by the ipFamilyPolicy field. + items: + description: |- + IPFamily represents the IP Family (IPv4 or IPv6). This type is used + to express the family of an IP expressed by a type (e.g. service.spec.ipFamilies). + type: string + type: array + x-kubernetes-list-type: atomic + ipFamilyPolicy: + description: |- + IPFamilyPolicy represents the dual-stack-ness requested or required by + this Service. If there is no value provided, then this field will be set + to SingleStack. Services can be "SingleStack" (a single IP family), + "PreferDualStack" (two IP families on dual-stack configured clusters or + a single IP family on single-stack clusters), or "RequireDualStack" + (two IP families on dual-stack configured clusters, otherwise fail). The + ipFamilies and clusterIPs fields depend on the value of this field. This + field will be wiped when updating a service to type ExternalName. + type: string + loadBalancerClass: + description: |- + loadBalancerClass is the class of the load balancer implementation this Service belongs to. + If specified, the value of this field must be a label-style identifier, with an optional prefix, + e.g. "internal-vip" or "example.com/internal-vip". Unprefixed names are reserved for end-users. + This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load + balancer implementation is used, today this is typically done through the cloud provider integration, + but should apply for any default implementation. If set, it is assumed that a load balancer + implementation is watching for Services with a matching class. Any default load balancer + implementation (e.g. cloud providers) should ignore Services that set this field. + This field can only be set when creating or updating a Service to type 'LoadBalancer'. + Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type. + type: string + loadBalancerIP: + description: |- + Only applies to Service Type: LoadBalancer. + This feature depends on whether the underlying cloud-provider supports specifying + the loadBalancerIP when a load balancer is created. + This field will be ignored if the cloud-provider does not support the feature. + Deprecated: This field was under-specified and its meaning varies across implementations. + Using it is non-portable and it may not support dual-stack. + Users are encouraged to use implementation-specific annotations when available. + type: string + loadBalancerSourceRanges: + description: |- + If specified and supported by the platform, this will restrict traffic through the cloud-provider + load-balancer will be restricted to the specified client IPs. This field will be ignored if the + cloud-provider does not support the feature." + More info: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/ + items: + type: string + type: array + x-kubernetes-list-type: atomic + ports: + description: |- + The list of ports that are exposed by this service. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + items: + description: ServicePort contains information + on service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed + by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + - protocol + x-kubernetes-list-type: map + publishNotReadyAddresses: + description: |- + publishNotReadyAddresses indicates that any agent which deals with endpoints for this + Service should disregard any indications of ready/not-ready. + The primary use case for setting this field is for a StatefulSet's Headless Service to + propagate SRV DNS records for its Pods for the purpose of peer discovery. + The Kubernetes controllers that generate Endpoints and EndpointSlice resources for + Services interpret this to mean that all endpoints are considered "ready" even if the + Pods themselves are not. Agents which consume only Kubernetes generated endpoints + through the Endpoints or EndpointSlice resources can safely assume this behavior. + type: boolean + selector: + additionalProperties: + type: string + description: |- + Route service traffic to pods with label keys and values matching this + selector. If empty or not present, the service is assumed to have an + external process managing its endpoints, which Kubernetes will not + modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. + Ignored if type is ExternalName. + More info: https://kubernetes.io/docs/concepts/services-networking/service/ + type: object + x-kubernetes-map-type: atomic + sessionAffinity: + description: |- + Supports "ClientIP" and "None". Used to maintain session affinity. + Enable client IP based session affinity. + Must be ClientIP or None. + Defaults to None. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + type: string + sessionAffinityConfig: + description: sessionAffinityConfig contains + the configurations of session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + trafficDistribution: + description: |- + TrafficDistribution offers a way to express preferences for how traffic is + distributed to Service endpoints. Implementations can use this field as a + hint, but are not required to guarantee strict adherence. If the field is + not set, the implementation will apply its default routing strategy. If set + to "PreferClose", implementations should prioritize endpoints that are + topologically close (e.g., same zone). + This is an alpha field and requires enabling ServiceTrafficDistribution feature. + type: string + type: + description: |- + type determines how the Service is exposed. Defaults to ClusterIP. Valid + options are ExternalName, ClusterIP, NodePort, and LoadBalancer. + "ClusterIP" allocates a cluster-internal IP address for load-balancing + to endpoints. Endpoints are determined by the selector or if that is not + specified, by manual construction of an Endpoints object or + EndpointSlice objects. If clusterIP is "None", no virtual IP is + allocated and the endpoints are published as a set of endpoints rather + than a virtual IP. + "NodePort" builds on ClusterIP and allocates a port on every node which + routes to the same endpoints as the clusterIP. + "LoadBalancer" builds on NodePort and creates an external load-balancer + (if supported in the current cloud) which routes to the same endpoints + as the clusterIP. + "ExternalName" aliases this service to the specified externalName. + Several other fields do not apply to ExternalName services. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: string + type: object + type: object + updateStrategy: + default: patch + description: UpdateStrategy describes how the service + differences should be reconciled + enum: + - patch + - replace + type: string + required: + - selectorType + - serviceTemplate + type: object + type: array + disabledDefaultServices: + description: |- + DisabledDefaultServices is a list of service types that are disabled by default. + Valid values are "r", and "ro", representing read, and read-only services. + items: + description: |- + ServiceSelectorType describes a valid value for generating the service selectors. + It indicates which type of service the selector applies to, such as read-write, read, or read-only + enum: + - rw + - r + - ro + type: string + type: array + type: object type: object maxSyncReplicas: default: 0 @@ -3812,6 +4429,18 @@ spec: type: string type: object type: array + tls: + description: |- + Configure TLS communication for the metrics endpoint. + Changing tls.enabled option will force a rollout of all instances. + properties: + enabled: + default: false + description: |- + Enable TLS for the monitoring endpoint. + Changing this option will force a rollout of all instances. + type: boolean + type: object type: object nodeMaintenanceWindow: description: Define a maintenance window for the Kubernetes nodes @@ -3994,6 +4623,54 @@ spec: required: - enabled type: object + synchronous: + description: Configuration of the PostgreSQL synchronous replication + feature + properties: + maxStandbyNamesFromCluster: + description: |- + Specifies the maximum number of local cluster pods that can be + automatically included in the `synchronous_standby_names` option in + PostgreSQL. + type: integer + method: + description: |- + Method to select synchronous replication standbys from the listed + servers, accepting 'any' (quorum-based synchronous replication) or + 'first' (priority-based synchronous replication) as values. + enum: + - any + - first + type: string + number: + description: |- + Specifies the number of synchronous standby servers that + transactions must wait for responses from. + type: integer + x-kubernetes-validations: + - message: The number of synchronous replicas should be greater + than zero + rule: self > 0 + standbyNamesPost: + description: |- + A user-defined list of application names to be added to + `synchronous_standby_names` after local cluster pods (the order is + only useful for priority-based synchronous replication). + items: + type: string + type: array + standbyNamesPre: + description: |- + A user-defined list of application names to be added to + `synchronous_standby_names` before local cluster pods (the order is + only useful for priority-based synchronous replication). + items: + type: string + type: array + required: + - method + - number + type: object type: object primaryUpdateMethod: default: restart @@ -4375,13 +5052,35 @@ spec: object store or via streaming through pg_basebackup. Refer to the Replica clusters page of the documentation for more information. type: boolean + minApplyDelay: + description: |- + When replica mode is enabled, this parameter allows you to replay + transactions only when the system time is at least the configured + time past the commit time. This provides an opportunity to correct + data loss errors. Note that when this parameter is set, a promotion + token cannot be used. + type: string + primary: + description: |- + Primary defines which Cluster is defined to be the primary in the distributed PostgreSQL cluster, based on the + topology specified in externalClusters + type: string + promotionToken: + description: |- + A demotion token generated by an external cluster used to + check if the promotion requirements are met. + type: string + self: + description: |- + Self defines the name of this cluster. It is used to determine if this is a primary + or a replica cluster, comparing it with `primary` + type: string source: description: The name of the external cluster which is the replication origin minLength: 1 type: string required: - - enabled - source type: object replicationSlots: @@ -4558,6 +5257,10 @@ spec: and services. More info: http://kubernetes.io/docs/user-guide/labels type: object + name: + description: The name of the resource. Only supported for + certain types + type: string type: object required: - metadata @@ -5677,6 +6380,13 @@ spec: items: type: string type: array + demotionToken: + description: |- + DemotionToken is a JSON token containing the information + from pg_controldata such as Database system identifier, Latest checkpoint's + TimeLineID, Latest checkpoint's REDO location, Latest checkpoint's REDO + WAL file, and Time of latest checkpoint + type: string firstRecoverabilityPoint: description: |- The first recoverability point, stored as a date in RFC3339 format. @@ -5744,6 +6454,11 @@ spec: lastFailedBackup: description: Stored as a date in RFC3339 format type: string + lastPromotionToken: + description: |- + LastPromotionToken is the last verified promotion token that + was used to promote a replica cluster + type: string lastSuccessfulBackup: description: |- Last successful backup, stored as a date in RFC3339 format @@ -5837,6 +6552,10 @@ spec: items: type: string type: array + status: + description: Status contain the status reported by the plugin + through the SetStatusInCluster interface + type: string version: description: |- Version is the version of the plugin loaded by the @@ -6510,6 +7229,10 @@ spec: and services. More info: http://kubernetes.io/docs/user-guide/labels type: object + name: + description: The name of the resource. Only supported for + certain types + type: string type: object spec: description: |- @@ -6888,6 +7611,10 @@ spec: and services. More info: http://kubernetes.io/docs/user-guide/labels type: object + name: + description: The name of the resource. Only supported for + certain types + type: string type: object spec: description: |- diff --git a/charts/cloudnative-pg/templates/rbac.yaml b/charts/cloudnative-pg/templates/rbac.yaml index 864ea0df3..9d2ba14b9 100644 --- a/charts/cloudnative-pg/templates/rbac.yaml +++ b/charts/cloudnative-pg/templates/rbac.yaml @@ -171,14 +171,6 @@ rules: verbs: - get - patch -- apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - get - - list - - update - apiGroups: - apps resources: diff --git a/charts/cluster/Chart.yaml b/charts/cluster/Chart.yaml index d69a5f0b7..d18c91d03 100644 --- a/charts/cluster/Chart.yaml +++ b/charts/cluster/Chart.yaml @@ -18,7 +18,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.0.9 +version: 0.0.11 sources: - https://github.com/cloudnative-pg/charts keywords: diff --git a/charts/cluster/README.md b/charts/cluster/README.md index 503adb5ba..5b3fcd204 100644 --- a/charts/cluster/README.md +++ b/charts/cluster/README.md @@ -1,6 +1,6 @@ # cluster -![Version: 0.0.9](https://img.shields.io/badge/Version-0.0.9-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) +![Version: 0.0.11](https://img.shields.io/badge/Version-0.0.11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) > **Warning** > ### This chart is under active development. @@ -153,6 +153,7 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | cluster.annotations | object | `{}` | | | cluster.certificates | object | `{}` | The configuration for the CA and related certificates. See: https://cloudnative-pg.io/documentation/current/cloudnative-pg.v1/#postgresql-cnpg-io-v1-CertificatesConfiguration | | cluster.enableSuperuserAccess | bool | `true` | When this option is enabled, the operator will use the SuperuserSecret to update the postgres user password. If the secret is not present, the operator will automatically create one. When this option is disabled, the operator will ignore the SuperuserSecret content, delete it when automatically created, and then blank the password of the postgres user by setting it to NULL. | +| cluster.imageCatalogRef | object | `{}` | Reference to `ImageCatalog` of `ClusterImageCatalog`, if specified takes precedence over `cluster.imageName` | | cluster.imageName | string | `""` | Name of the container image, supporting both tags (:) and digests for deterministic and repeatable deployments: :@sha256: | | cluster.imagePullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never or IfNotPresent. If not defined, it defaults to IfNotPresent. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | | cluster.imagePullSecrets | list | `[]` | The list of pull secrets to be used to pull the images. See: https://cloudnative-pg.io/documentation/current/cloudnative-pg.v1/#postgresql-cnpg-io-v1-LocalObjectReference | @@ -164,10 +165,13 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | cluster.monitoring.podMonitor.enabled | bool | `true` | Whether to enable the PodMonitor | | cluster.monitoring.prometheusRule.enabled | bool | `true` | Whether to enable the PrometheusRule automated alerts | | cluster.monitoring.prometheusRule.excludeRules | list | `[]` | Exclude specified rules | -| cluster.postgresGID | int | `26` | The GID of the postgres user inside the image, defaults to 26 | -| cluster.postgresUID | int | `26` | The UID of the postgres user inside the image, defaults to 26 | -| cluster.postgresql | object | `{}` | Configuration of the PostgreSQL server. See: https://cloudnative-pg.io/documentation/current/cloudnative-pg.v1/#postgresql-cnpg-io-v1-PostgresConfiguration | -| cluster.primaryUpdateMethod | string | `"switchover"` | Method to follow to upgrade the primary server during a rolling update procedure, after all replicas have been successfully updated. It can be switchover (default) or in-place (restart). | +| cluster.postgresGID | int | `-1` | The GID of the postgres user inside the image, defaults to 26 | +| cluster.postgresUID | int | `-1` | The UID of the postgres user inside the image, defaults to 26 | +| cluster.postgresql.parameters | object | `{}` | PostgreSQL configuration options (postgresql.conf) | +| cluster.postgresql.pg_hba | list | `[]` | PostgreSQL Host Based Authentication rules (lines to be appended to the pg_hba.conf file) | +| cluster.postgresql.pg_ident | list | `[]` | PostgreSQL User Name Maps rules (lines to be appended to the pg_ident.conf file) | +| cluster.postgresql.shared_preload_libraries | list | `[]` | Lists of shared preload libraries to add to the default ones | +| cluster.primaryUpdateMethod | string | `"switchover"` | Method to follow to upgrade the primary server during a rolling update procedure, after all replicas have been successfully updated. It can be switchover (default) or restart. | | cluster.primaryUpdateStrategy | string | `"unsupervised"` | Strategy to follow to upgrade the primary server during a rolling update procedure, after all replicas have been successfully updated: it can be automated (unsupervised - default) or manual (supervised) | | cluster.priorityClassName | string | `""` | | | cluster.resources | object | `{}` | Resources requirements of every generated Pod. Please refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ for more information. We strongly advise you use the same setting for limits and requests so that your cluster pods are given a Guaranteed QoS. See: https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/ | @@ -175,9 +179,12 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | cluster.storage.size | string | `"8Gi"` | | | cluster.storage.storageClass | string | `""` | | | cluster.superuserSecret | string | `""` | | +| cluster.walStorage.enabled | bool | `false` | | | cluster.walStorage.size | string | `"1Gi"` | | | cluster.walStorage.storageClass | string | `""` | | | fullnameOverride | string | `""` | Override the full name of the chart | +| imageCatalog.create | bool | `true` | Whether to provision an image catalog. If imageCatalog.images is empty this option will be ignored. | +| imageCatalog.images | list | `[]` | List of images to be provisioned in an image catalog. | | mode | string | `"standalone"` | Cluster mode of operation. Available modes: * `standalone` - default mode. Creates new or updates an existing CNPG cluster. * `replica` - Creates a replica cluster from an existing CNPG cluster. # TODO * `recovery` - Same as standalone but creates a cluster from a backup, object store or via pg_basebackup. | | nameOverride | string | `""` | Override the name of the chart | | pooler.enabled | bool | `false` | Whether to enable PgBouncer | @@ -207,6 +214,24 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | recovery.google.gkeEnvironment | bool | `false` | | | recovery.google.path | string | `"/"` | | | recovery.method | string | `"backup"` | Available recovery methods: * `backup` - Recovers a CNPG cluster from a CNPG backup (PITR supported) Needs to be on the same cluster in the same namespace. * `object_store` - Recovers a CNPG cluster from a barman object store (PITR supported). * `pg_basebackup` - Recovers a CNPG cluster viaa streaming replication protocol. Useful if you want to migrate databases to CloudNativePG, even from outside Kubernetes. # TODO | +| recovery.pgBaseBackup.database | string | `"app"` | Name of the database used by the application. Default: `app`. | +| recovery.pgBaseBackup.owner | string | `""` | Name of the secret containing the initial credentials for the owner of the user database. If empty a new secret will be created from scratch | +| recovery.pgBaseBackup.secret | string | `""` | Name of the owner of the database in the instance to be used by applications. Defaults to the value of the `database` key. | +| recovery.pgBaseBackup.source.database | string | `"app"` | | +| recovery.pgBaseBackup.source.host | string | `""` | | +| recovery.pgBaseBackup.source.passwordSecret.create | bool | `false` | Whether to create a secret for the password | +| recovery.pgBaseBackup.source.passwordSecret.key | string | `"password"` | The key in the secret containing the password | +| recovery.pgBaseBackup.source.passwordSecret.name | string | `""` | Name of the secret containing the password | +| recovery.pgBaseBackup.source.passwordSecret.value | string | `""` | The password value to use when creating the secret | +| recovery.pgBaseBackup.source.port | int | `5432` | | +| recovery.pgBaseBackup.source.sslCertSecret.key | string | `""` | | +| recovery.pgBaseBackup.source.sslCertSecret.name | string | `""` | | +| recovery.pgBaseBackup.source.sslKeySecret.key | string | `""` | | +| recovery.pgBaseBackup.source.sslKeySecret.name | string | `""` | | +| recovery.pgBaseBackup.source.sslMode | string | `"verify-full"` | | +| recovery.pgBaseBackup.source.sslRootCertSecret.key | string | `""` | | +| recovery.pgBaseBackup.source.sslRootCertSecret.name | string | `""` | | +| recovery.pgBaseBackup.source.username | string | `""` | | | recovery.pitrTarget.time | string | `""` | Time in RFC3339 format | | recovery.provider | string | `"s3"` | One of `s3`, `azure` or `google` | | recovery.s3.accessKey | string | `""` | | @@ -216,7 +241,10 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | recovery.s3.secretKey | string | `""` | | | recovery.secret.create | bool | `true` | Whether to create a secret for the backup credentials | | recovery.secret.name | string | `""` | Name of the backup credentials secret | -| type | string | `"postgresql"` | Type of the CNPG database. Available types: * `postgresql` * `postgis` | +| type | string | `"postgresql"` | Type of the CNPG database. Available types: * `postgresql` * `postgis` * `timescaledb` | +| version.postgis | string | `"3.4"` | If using PostGIS, specify the version | +| version.postgresql | string | `"16"` | PostgreSQL major version to use | +| version.timescaledb | string | `"2.15"` | If using TimescaleDB, specify the version | ## Maintainers diff --git a/charts/cluster/docs/Recovery.md b/charts/cluster/docs/Recovery.md index 6a1be6593..1a7a9153c 100644 --- a/charts/cluster/docs/Recovery.md +++ b/charts/cluster/docs/Recovery.md @@ -10,7 +10,7 @@ You can find more information about the recovery process in the [CNPG documentat There are 3 types of recovery possible with CNPG: * Recovery from a backup object in the same Kubernetes namespace. * Recovery from a Barman Object Store, that could be located anywhere. -* Streaming replication from an operating cluster using `pg_basebackup` (not supported by the chart yet). +* Streaming replication from an operating cluster using `pg_basebackup`. When performing a recovery you are strongly advised to use the same configuration and PostgreSQL version as the original cluster. @@ -18,10 +18,10 @@ To begin, create a `values.yaml` that contains the following: 1. Set `mode: recovery` to indicate that you want to perform bootstrap the new cluster from an existing one. 2. Set the `recovery.method` to the type of recovery you want to perform. -3. Set either the `recovery.backupName` or the Barman Object Store configuration - i.e. `recovery.provider` and appropriate S3, Azure or GCS configuration. -4. Optionally set the `recovery.pitrTarget.time` in RFC3339 format to perform a point-in-time recovery. -4. Retain the identical PostgreSQL version and configuration as the original cluster. -5. Make sure you don't use the same backup section name as the original cluster. We advise you change the `path` within the storage location if you want to reuse the same storage location/bucket. +3. Set either the `recovery.backupName` or the Barman Object Store configuration - i.e. `recovery.provider` and appropriate S3, Azure or GCS configuration. In case of `pg_basebackup` complete the `recovery.pgBaseBackup` section. +4. Optionally set the `recovery.pitrTarget.time` in RFC3339 format to perform a point-in-time recovery (not applicable for `pgBaseBackup`). +5. Retain the identical PostgreSQL version and configuration as the original cluster. +6. Make sure you don't use the same backup section name as the original cluster. We advise you change the `path` within the storage location if you want to reuse the same storage location/bucket. One pattern is adding a version number at the end of the path, e.g. `/v1` or `/v2` after each recovery procedure. Example recovery configurations can be found in the [examples](../examples) directory. diff --git a/charts/cluster/examples/basic.yaml b/charts/cluster/examples/basic.yaml index 5b608c267..b4c15bb15 100644 --- a/charts/cluster/examples/basic.yaml +++ b/charts/cluster/examples/basic.yaml @@ -1,4 +1,7 @@ +type: postgresql mode: standalone +version: + postgresql: "16" cluster: instances: 1 backups: diff --git a/charts/cluster/examples/image-catalog-ref.yaml b/charts/cluster/examples/image-catalog-ref.yaml new file mode 100644 index 000000000..e4833a3b6 --- /dev/null +++ b/charts/cluster/examples/image-catalog-ref.yaml @@ -0,0 +1,12 @@ +type: postgresql +mode: standalone +version: + major: "16" + timescaledb: "2.15" +cluster: + instances: 1 + imageCatalogRef: + kind: ImageCatalog + name: my-image-catalog +backups: + enabled: false diff --git a/charts/cluster/examples/image-catalog.yaml b/charts/cluster/examples/image-catalog.yaml new file mode 100644 index 000000000..c610229b0 --- /dev/null +++ b/charts/cluster/examples/image-catalog.yaml @@ -0,0 +1,14 @@ +type: postgresql +mode: standalone +version: + major: "16" + timescaledb: "2.15" +cluster: + instances: 1 +backups: + enabled: false +imageCatalog: + create: true + images: + - major: 16 + image: my-custom-postgres-image:mytag diff --git a/charts/cluster/examples/pgbouncer.yaml b/charts/cluster/examples/pgbouncer.yaml index 1da966275..cfa641e45 100644 --- a/charts/cluster/examples/pgbouncer.yaml +++ b/charts/cluster/examples/pgbouncer.yaml @@ -1,3 +1,4 @@ +type: postgresql mode: standalone cluster: instances: 1 diff --git a/charts/cluster/examples/postgis.yaml b/charts/cluster/examples/postgis.yaml index 6c686dc62..168ac9fbf 100644 --- a/charts/cluster/examples/postgis.yaml +++ b/charts/cluster/examples/postgis.yaml @@ -1,6 +1,9 @@ type: postgis mode: standalone +version: + postgresql: "16" + postgis: "3.4" cluster: instances: 1 backups: - enabled: false \ No newline at end of file + enabled: false diff --git a/charts/cluster/examples/recovery-backup.yaml b/charts/cluster/examples/recovery-backup.yaml index d11187f5c..c478e7a9e 100644 --- a/charts/cluster/examples/recovery-backup.yaml +++ b/charts/cluster/examples/recovery-backup.yaml @@ -1,3 +1,4 @@ +type: postgresql mode: recovery recovery: diff --git a/charts/cluster/examples/recovery-object_store.yaml b/charts/cluster/examples/recovery-object_store.yaml index 92722a159..060f5328d 100644 --- a/charts/cluster/examples/recovery-object_store.yaml +++ b/charts/cluster/examples/recovery-object_store.yaml @@ -1,8 +1,9 @@ +type: postgresql mode: recovery recovery: method: object_store - serverName: "cluster-name-to-recover-from" + clusterName: "cluster-name-to-recover-from" provider: s3 s3: region: "eu-west-1" @@ -27,4 +28,4 @@ backups: - name: daily-backup # Daily at midnight schedule: "0 0 0 * * *" # Daily at midnight backupOwnerReference: self - retentionPolicy: "30d" \ No newline at end of file + retentionPolicy: "30d" diff --git a/charts/cluster/examples/recovery-pg_basebackup.yaml b/charts/cluster/examples/recovery-pg_basebackup.yaml new file mode 100644 index 000000000..05358bf50 --- /dev/null +++ b/charts/cluster/examples/recovery-pg_basebackup.yaml @@ -0,0 +1,15 @@ +type: postgresql +mode: "recovery" + +recovery: + method: "pg_basebackup" + pgBaseBackup: + sourceHost: "source-db.foo.com" + sourceUsername: "streaming_replica" + existingPasswordSecret: "source-db-replica-password" + +cluster: + instances: 1 + +backups: + enabled: false \ No newline at end of file diff --git a/charts/cluster/examples/standalone-s3.yaml b/charts/cluster/examples/standalone-s3.yaml index bf1794d06..44a4bb104 100644 --- a/charts/cluster/examples/standalone-s3.yaml +++ b/charts/cluster/examples/standalone-s3.yaml @@ -1,3 +1,4 @@ +type: postgresql mode: standalone cluster: diff --git a/charts/cluster/examples/timescaledb.yaml b/charts/cluster/examples/timescaledb.yaml new file mode 100644 index 000000000..328b6c1eb --- /dev/null +++ b/charts/cluster/examples/timescaledb.yaml @@ -0,0 +1,9 @@ +type: timescaledb +mode: standalone +version: + postgresql: "15.7" + timescaledb: "2.15" +cluster: + instances: 1 +backups: + enabled: false diff --git a/charts/cluster/templates/NOTES.txt b/charts/cluster/templates/NOTES.txt index dd5142ecc..6a28fa592 100644 --- a/charts/cluster/templates/NOTES.txt +++ b/charts/cluster/templates/NOTES.txt @@ -37,26 +37,50 @@ Configuration {{- $redundancyColor = "ok" -}} {{- end }} -{{ $scheduledBackups := (first .Values.backups.scheduledBackups).name }} +{{- $scheduledBackups := (first .Values.backups.scheduledBackups).name -}} {{- range (rest .Values.backups.scheduledBackups) -}} {{ $scheduledBackups = printf "%s, %s" $scheduledBackups .name }} {{- end -}} +{{- if eq (len .Values.backups.scheduledBackups) 0 }} + {{- $scheduledBackups = "None" -}} +{{- end -}} + +{{- $mode := .Values.mode -}} +{{- $source := "" -}} +{{- if eq .Values.mode "recovery" }} +{{- $mode = printf "%s (%s)" .Values.mode .Values.recovery.method -}} + {{- if eq .Values.recovery.method "pg_basebackup" }} + {{- $source = printf "postgresql://%s@%s:%.0f/%s" .Values.recovery.pgBaseBackup.source.username .Values.recovery.pgBaseBackup.source.host .Values.recovery.pgBaseBackup.source.port .Values.recovery.pgBaseBackup.source.database -}} + {{- end -}} +{{- end -}} -╭───────────────────┬────────────────────────────────────────────────────────╮ -│ Configuration │ Value │ -┝━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥ -│ Cluster mode │ {{ (printf "%-54s" .Values.mode) }} │ -│ Type │ {{ (printf "%-54s" .Values.type) }} │ -│ Image │ {{ include "cluster.color-info" (printf "%-54s" (include "cluster.imageName" .)) }} │ -│ Instances │ {{ include (printf "%s%s" "cluster.color-" $redundancyColor) (printf "%-54s" (toString .Values.cluster.instances)) }} │ -│ Backups │ {{ include (printf "%s%s" "cluster.color-" (ternary "ok" "error" .Values.backups.enabled)) (printf "%-54s" (ternary "Enabled" "Disabled" .Values.backups.enabled)) }} │ -│ Backup Provider │ {{ (printf "%-54s" (title .Values.backups.provider)) }} │ -│ Scheduled Backups │ {{ (printf "%-54s" $scheduledBackups) }} │ -│ Storage │ {{ (printf "%-54s" .Values.cluster.storage.size) }} │ -│ Storage Class │ {{ (printf "%-54s" (default "Default" .Values.cluster.storage.storageClass)) }} │ -│ PGBouncer │ {{ (printf "%-54s" (ternary "Enabled" "Disabled" .Values.pooler.enabled)) }} │ -│ Monitoring │ {{ include (printf "%s%s" "cluster.color-" (ternary "ok" "error" .Values.cluster.monitoring.enabled)) (printf "%-54s" (ternary "Enabled" "Disabled" .Values.cluster.monitoring.enabled)) }} │ -╰───────────────────┴────────────────────────────────────────────────────────╯ +{{- $image := (include "cluster.image" .) | fromYaml -}} +{{- if $image.imageCatalogRef -}} + {{- $image = printf "%s: %s(%s)" $image.imageCatalogRef.kind $image.imageCatalogRef.name (include "cluster.postgresqlMajor" .) -}} +{{- else if $image.imageName -}} + {{- $image = $image.imageName -}} +{{- end }} + +╭───────────────────┬──────────────────────────────────────────────────────────╮ +│ Configuration │ Value │ +┝━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥ +│ Cluster mode │ {{ printf "%-56s" $mode }} │ +│ Type │ {{ printf "%-56s" .Values.type }} │ +│ Image │ {{ include "cluster.color-info" (printf "%-56s" $image) }} │ +{{- if eq .Values.mode "recovery" }} +│ Source │ {{ printf "%-56s" $source }} │ +{{- end }} +│ Instances │ {{ include (printf "%s%s" "cluster.color-" $redundancyColor) (printf "%-56s" (toString .Values.cluster.instances)) }} │ +│ Backups │ {{ include (printf "%s%s" "cluster.color-" (ternary "ok" "error" .Values.backups.enabled)) (printf "%-56s" (ternary "Enabled" "Disabled" .Values.backups.enabled)) }} │ +{{- if .Values.backups.enabled }} +│ Backup Provider │ {{ printf "%-56s" (title .Values.backups.provider) }} │ +│ Scheduled Backups │ {{ printf "%-56s" $scheduledBackups }} │ +{{- end }} +│ Storage │ {{ printf "%-56s" .Values.cluster.storage.size }} │ +│ Storage Class │ {{ printf "%-56s" (default "Default" .Values.cluster.storage.storageClass) }} │ +│ PGBouncer │ {{ printf "%-56s" (ternary "Enabled" "Disabled" .Values.pooler.enabled) }} │ +│ Monitoring │ {{ include (printf "%s%s" "cluster.color-" (ternary "ok" "error" .Values.cluster.monitoring.enabled)) (printf "%-56s" (ternary "Enabled" "Disabled" .Values.cluster.monitoring.enabled)) }} │ +╰───────────────────┴──────────────────────────────────────────────────────────╯ {{ if not .Values.backups.enabled }} {{- include "cluster.color-error" "Warning! Backups not enabled. Recovery will not be possible! Do not use this configuration in production.\n" }} diff --git a/charts/cluster/templates/_barman_object_store.tpl b/charts/cluster/templates/_barman_object_store.tpl index becb9cfca..881047655 100644 --- a/charts/cluster/templates/_barman_object_store.tpl +++ b/charts/cluster/templates/_barman_object_store.tpl @@ -6,8 +6,8 @@ {{- if or (.scope.endpointCA.create) (.scope.endpointCA.name) }} endpointCA: - name: {{ .chartFullname }}-ca-bundle - key: ca-bundle.crt + name: {{.scope.endpointCA.name }} + key: {{ .scope.endpointCA.key }} {{- end }} {{- if .scope.destinationPath }} diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index dd016f5d8..2b1eb8a1c 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -26,6 +26,50 @@ bootstrap: {{- end -}} {{- else if eq .Values.mode "recovery" -}} bootstrap: +{{- if eq .Values.recovery.method "pg_basebackup" }} + pg_basebackup: + source: pgBaseBackupSource + {{ with .Values.recovery.pgBaseBackup.database }} + database: {{ . }} + {{- end }} + {{ with .Values.recovery.pgBaseBackup.owner }} + owner: {{ . }} + {{- end }} + {{ with .Values.recovery.pgBaseBackup.secret }} + secret: + {{- toYaml . | nindent 6 }} + {{- end }} + +externalClusters: +- name: pgBaseBackupSource + connectionParameters: + host: {{ .Values.recovery.pgBaseBackup.source.host | quote }} + port: {{ .Values.recovery.pgBaseBackup.source.port | quote }} + user: {{ .Values.recovery.pgBaseBackup.source.username | quote }} + dbname: {{ .Values.recovery.pgBaseBackup.source.database | quote }} + sslmode: {{ .Values.recovery.pgBaseBackup.source.sslMode | quote }} + {{- if .Values.recovery.pgBaseBackup.source.passwordSecret.name }} + password: + name: {{ default (printf "%s-pg-basebackup-password" (include "cluster.fullname" .)) .Values.recovery.pgBaseBackup.source.passwordSecret.name }} + key: {{ .Values.recovery.pgBaseBackup.source.passwordSecret.key }} + {{- end }} + {{- if .Values.recovery.pgBaseBackup.source.sslKeySecret.name }} + sslKey: + name: {{ .Values.recovery.pgBaseBackup.source.sslKeySecret.name }} + key: {{ .Values.recovery.pgBaseBackup.source.sslKeySecret.key }} + {{- end }} + {{- if .Values.recovery.pgBaseBackup.source.sslCertSecret.name }} + sslCert: + name: {{ .Values.recovery.pgBaseBackup.source.sslCertSecret.name }} + key: {{ .Values.recovery.pgBaseBackup.source.sslCertSecret.key }} + {{- end }} + {{- if .Values.recovery.pgBaseBackup.source.sslRootCertSecret.name }} + sslRootCert: + name: {{ .Values.recovery.pgBaseBackup.source.sslRootCertSecret.name }} + key: {{ .Values.recovery.pgBaseBackup.source.sslRootCertSecret.key }} + {{- end }} + +{{- else }} recovery: {{- with .Values.recovery.pitrTarget.time }} recoveryTarget: @@ -41,9 +85,10 @@ bootstrap: externalClusters: - name: objectStoreRecoveryCluster barmanObjectStore: - serverName: {{ default (include "cluster.fullname" .) .Values.recovery.clusterName }} + serverName: {{ .Values.recovery.clusterName }} {{- $d := dict "chartFullname" (include "cluster.fullname" .) "scope" .Values.recovery "secretPrefix" "recovery" -}} {{- include "cluster.barmanObjectStoreConfig" $d | nindent 4 }} +{{- end }} {{- else }} {{ fail "Invalid cluster mode!" }} {{- end }} diff --git a/charts/cluster/templates/_helpers.tpl b/charts/cluster/templates/_helpers.tpl index db3c253e5..96726fdfe 100644 --- a/charts/cluster/templates/_helpers.tpl +++ b/charts/cluster/templates/_helpers.tpl @@ -51,6 +51,20 @@ app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/part-of: cloudnative-pg {{- end }} +{{/* +Whether we need to use TimescaleDB defaults +*/}} +{{- define "cluster.useTimescaleDBDefaults" -}} +{{ and (eq .Values.type "timescaledb") .Values.imageCatalog.create (empty .Values.cluster.imageCatalogRef.name) (empty .Values.imageCatalog.images) (empty .Values.cluster.imageName) }} +{{- end -}} + +{{/* +Get the PostgreSQL major version from .Values.version.postgresql +*/}} +{{- define "cluster.postgresqlMajor" -}} +{{ index (regexSplit "\\." (toString .Values.version.postgresql) 2) 0 }} +{{- end -}} + {{/* Cluster Image Name If a custom imageName is available, use it, otherwise use the defaults based on the .Values.type @@ -59,12 +73,63 @@ If a custom imageName is available, use it, otherwise use the defaults based on {{- if .Values.cluster.imageName -}} {{- .Values.cluster.imageName -}} {{- else if eq .Values.type "postgresql" -}} - {{- "ghcr.io/cloudnative-pg/postgresql:15.2" -}} + {{- printf "ghcr.io/cloudnative-pg/postgresql:%s" .Values.version.postgresql -}} {{- else if eq .Values.type "postgis" -}} - {{- "ghcr.io/cloudnative-pg/postgis:14" -}} - {{- else if eq .Values.type "timescaledb" -}} - {{ fail "You need to provide your own cluster.imageName as an official timescaledb image doesn't exist yet." }} + {{- printf "ghcr.io/cloudnative-pg/postgis:%s-%s" .Values.version.postgresql .Values.version.postgis -}} {{- else -}} {{ fail "Invalid cluster type!" }} {{- end }} {{- end -}} + +{{/* +Cluster Image +If imageCatalogRef defined, use it, otherwice calculate ordinary imageName. +*/}} +{{- define "cluster.image" }} +{{- if .Values.cluster.imageCatalogRef.name }} +imageCatalogRef: + apiGroup: postgresql.cnpg.io + {{- toYaml .Values.cluster.imageCatalogRef | nindent 2 }} + major: {{ include "cluster.postgresqlMajor" . }} +{{- else if and .Values.imageCatalog.create (not (empty .Values.imageCatalog.images )) }} +imageCatalogRef: + apiGroup: postgresql.cnpg.io + kind: ImageCatalog + name: {{ include "cluster.fullname" . }} + major: {{ include "cluster.postgresqlMajor" . }} +{{- else if eq (include "cluster.useTimescaleDBDefaults" .) "true" -}} +imageCatalogRef: + apiGroup: postgresql.cnpg.io + kind: ImageCatalog + name: {{ include "cluster.fullname" . }}-timescaledb-ha + major: {{ include "cluster.postgresqlMajor" . }} +{{- else }} +imageName: {{ include "cluster.imageName" . }} +{{- end }} +{{- end }} + +{{/* +Postgres UID +*/}} +{{- define "cluster.postgresUID" -}} + {{- if ge (int .Values.cluster.postgresUID) 0 -}} + {{- .Values.cluster.postgresUID }} + {{- else if and (eq (include "cluster.useTimescaleDBDefaults" .) "true") (eq .Values.type "timescaledb") -}} + {{- 1000 -}} + {{- else -}} + {{- 26 -}} + {{- end -}} +{{- end -}} + +{{/* +Postgres GID +*/}} +{{- define "cluster.postgresGID" -}} + {{- if ge (int .Values.cluster.postgresGID) 0 -}} + {{- .Values.cluster.postgresGID }} + {{- else if and (eq (include "cluster.useTimescaleDBDefaults" .) "true") (eq .Values.type "timescaledb") -}} + {{- 1000 -}} + {{- else -}} + {{- 26 -}} + {{- end -}} +{{- end -}} diff --git a/charts/cluster/templates/cluster.yaml b/charts/cluster/templates/cluster.yaml index 2dace8e34..c1879cfef 100644 --- a/charts/cluster/templates/cluster.yaml +++ b/charts/cluster/templates/cluster.yaml @@ -13,18 +13,18 @@ metadata: {{- end }} spec: instances: {{ .Values.cluster.instances }} - imageName: {{ include "cluster.imageName" . }} + {{- include "cluster.image" . | nindent 2 }} imagePullPolicy: {{ .Values.cluster.imagePullPolicy }} - {{- with .Values.cluster.imagePullSecrets}} + {{- with .Values.cluster.imagePullSecrets }} imagePullSecrets: {{- . | toYaml | nindent 4 }} {{- end }} - postgresUID: {{ .Values.cluster.postgresUID }} - postgresGID: {{ .Values.cluster.postgresGID }} + postgresUID: {{ include "cluster.postgresUID" . }} + postgresGID: {{ include "cluster.postgresGID" . }} storage: size: {{ .Values.cluster.storage.size }} storageClass: {{ .Values.cluster.storage.storageClass }} -{{- if .Values.cluster.walStorage }} +{{- if .Values.cluster.walStorage.enabled }} walStorage: size: {{ .Values.cluster.walStorage.size }} storageClass: {{ .Values.cluster.walStorage.storageClass }} @@ -56,9 +56,16 @@ spec: {{- if eq .Values.type "timescaledb" }} - timescaledb {{- end }} + {{- with .Values.cluster.postgresql.shared_preload_libraries }} + {{- toYaml . | nindent 6 }} + {{- end }} {{- with .Values.cluster.postgresql }} parameters: - {{- toYaml . | nindent 6 }} + {{- toYaml .parameters | nindent 6 }} + pg_hba: + {{- toYaml .pg_hba | nindent 6 }} + pg_ident: + {{- toYaml .pg_ident | nindent 6 }} {{ end }} managed: diff --git a/charts/cluster/templates/image-catalog-timescaledb-ha.yaml b/charts/cluster/templates/image-catalog-timescaledb-ha.yaml new file mode 100644 index 000000000..d611171e1 --- /dev/null +++ b/charts/cluster/templates/image-catalog-timescaledb-ha.yaml @@ -0,0 +1,18 @@ +{{- if eq (include "cluster.useTimescaleDBDefaults" .) "true" -}} +apiVersion: postgresql.cnpg.io/v1 +kind: ImageCatalog +metadata: + name: {{ include "cluster.fullname" . }}-timescaledb-ha +spec: + images: + - major: 12 + image: timescale/timescaledb-ha:pg15-ts{{ .Values.version.timescaledb }} + - major: 13 + image: timescale/timescaledb-ha:pg15-ts{{ .Values.version.timescaledb }} + - major: 14 + image: timescale/timescaledb-ha:pg15-ts{{ .Values.version.timescaledb }} + - major: 15 + image: timescale/timescaledb-ha:pg15-ts{{ .Values.version.timescaledb }} + - major: 16 + image: timescale/timescaledb-ha:pg16-ts{{ .Values.version.timescaledb }} +{{ end }} diff --git a/charts/cluster/templates/image-catalog.yaml b/charts/cluster/templates/image-catalog.yaml new file mode 100644 index 000000000..6dc707222 --- /dev/null +++ b/charts/cluster/templates/image-catalog.yaml @@ -0,0 +1,12 @@ +{{ if and .Values.imageCatalog.create (not (empty .Values.imageCatalog.images )) }} +apiVersion: postgresql.cnpg.io/v1 +kind: ImageCatalog +metadata: + name: {{ include "cluster.fullname" . }} +spec: + images: + {{- range $image := .Values.imageCatalog.images }} + - image: {{ $image.image }} + major: {{ $image.major }} + {{- end }} +{{- end }} diff --git a/charts/cluster/templates/recovery-pg_basebackup-password.yaml b/charts/cluster/templates/recovery-pg_basebackup-password.yaml new file mode 100644 index 000000000..456ee75d9 --- /dev/null +++ b/charts/cluster/templates/recovery-pg_basebackup-password.yaml @@ -0,0 +1,8 @@ +{{- if and (eq .Values.mode "recovery") (eq .Values.recovery.method "pg_basebackup") .Values.recovery.pgBaseBackup.source.passwordSecret.create }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ default (printf "%s-pg-basebackup-password" (include "cluster.fullname" .)) .Values.recovery.pgBaseBackup.source.passwordSecret.name }} +data: + {{ .Values.recovery.pgBaseBackup.source.passwordSecret.key }}: {{ required ".Values.recovery.pgBaseBackup.source.passwordSecret.value required when creating a password secret." .Values.recovery.pgBaseBackup.source.passwordSecret.value | b64enc | quote }} +{{- end }} diff --git a/charts/cluster/test/monitoring/01-monitoring_cluster-assert.yaml b/charts/cluster/test/monitoring/01-monitoring_cluster-assert.yaml new file mode 100644 index 000000000..d69c702c0 --- /dev/null +++ b/charts/cluster/test/monitoring/01-monitoring_cluster-assert.yaml @@ -0,0 +1,38 @@ +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: monitoring-cluster +spec: + selector: + matchLabels: + cnpg.io/cluster: monitoring-cluster +--- +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: monitoring-cluster-pooler-rw +spec: + selector: + matchLabels: + cnpg.io/poolerName: monitoring-cluster-pooler-rw +--- +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: monitoring-cluster-alert-rules +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: monitoring-cluster-monitoring +data: + custom-queries: | + pg_cache_hit_ratio: + query: "SELECT current_database() as datname, sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) as ratio FROM pg_statio_user_tables;" + metrics: + - datname: + description: Name of the database + usage: LABEL + - ratio: + description: Cache hit ratio + usage: GAUGE diff --git a/charts/cluster/test/monitoring/01-monitoring_cluster.yaml b/charts/cluster/test/monitoring/01-monitoring_cluster.yaml new file mode 100644 index 000000000..2e94c2fc0 --- /dev/null +++ b/charts/cluster/test/monitoring/01-monitoring_cluster.yaml @@ -0,0 +1,26 @@ +type: postgresql +mode: standalone +cluster: + instances: 2 + storage: + size: 256Mi + storageClass: standard + monitoring: + enabled: true + customQueries: + - name: "pg_cache_hit_ratio" + query: "SELECT current_database() as datname, sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) as ratio FROM pg_statio_user_tables;" + metrics: + - datname: + usage: "LABEL" + description: "Name of the database" + - ratio: + usage: GAUGE + description: "Cache hit ratio" +backups: + enabled: false +pooler: + enabled: true + instances: 1 + monitoring: + enabled: true diff --git a/charts/cluster/test/monitoring/chainsaw-test.yaml b/charts/cluster/test/monitoring/chainsaw-test.yaml new file mode 100644 index 000000000..ce647a48d --- /dev/null +++ b/charts/cluster/test/monitoring/chainsaw-test.yaml @@ -0,0 +1,29 @@ +## +# This is a test that checks if PodMonitors, ConfigMaps and PrometheusRules are correctly provisioned when requested. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: monitoring +spec: + timeouts: + apply: 1s + assert: 20s + cleanup: 30s + steps: + - name: Install the monitoring cluster + try: + - script: + content: | + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./01-monitoring_cluster.yaml \ + --wait \ + monitoring ../../ + - assert: + file: ./01-monitoring_cluster-assert.yaml + - name: Cleanup + try: + - script: + content: | + helm uninstall --namespace $NAMESPACE monitoring diff --git a/charts/cluster/test/pooler/01-pooler_cluster-assert.yaml b/charts/cluster/test/pooler/01-pooler_cluster-assert.yaml new file mode 100644 index 000000000..1b6178394 --- /dev/null +++ b/charts/cluster/test/pooler/01-pooler_cluster-assert.yaml @@ -0,0 +1,18 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pooler-cluster-pooler-rw +status: + readyReplicas: 2 +--- +apiVersion: postgresql.cnpg.io/v1 +kind: Pooler +metadata: + name: pooler-cluster-pooler-rw +spec: + cluster: + name: pooler-cluster + instances: 2 + pgbouncer: + poolMode: transaction + type: rw diff --git a/charts/cluster/test/pooler/01-pooler_cluster.yaml b/charts/cluster/test/pooler/01-pooler_cluster.yaml new file mode 100644 index 000000000..3f197ce7e --- /dev/null +++ b/charts/cluster/test/pooler/01-pooler_cluster.yaml @@ -0,0 +1,12 @@ +type: postgresql +mode: standalone +cluster: + instances: 2 + storage: + size: 256Mi + storageClass: standard +backups: + enabled: false +pooler: + enabled: true + instances: 2 diff --git a/charts/cluster/test/pooler/chainsaw-test.yaml b/charts/cluster/test/pooler/chainsaw-test.yaml new file mode 100644 index 000000000..427b84eb4 --- /dev/null +++ b/charts/cluster/test/pooler/chainsaw-test.yaml @@ -0,0 +1,30 @@ +## +# This is a test that verifies that non-default configuration options are correctly propagated to the CNPG cluster. +# P.S. This test is not designed to have a good running configuration, it is designed to test the configuration propagation! +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: pooler +spec: + timeouts: + apply: 1s + assert: 20s + cleanup: 30s + steps: + - name: Install the non-default configuration cluster + try: + - script: + content: | + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./01-pooler_cluster.yaml \ + --wait \ + pooler ../../ + - assert: + file: ./01-pooler_cluster-assert.yaml + - name: Cleanup + try: + - script: + content: | + helm uninstall --namespace $NAMESPACE pooler diff --git a/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster-assert.yaml b/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster-assert.yaml new file mode 100644 index 000000000..5f5c62a68 --- /dev/null +++ b/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster-assert.yaml @@ -0,0 +1,82 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: non-default-configuration-cluster + labels: + foo: bar + annotations: + foo: bar +spec: + imageName: ghcr.io/cloudnative-pg/crazycustomimage:99.99 + imagePullPolicy: Always + postgresUID: 1001 + postgresGID: 1002 + instances: 2 + postgresql: + parameters: + max_connections: "42" + pg_hba: + - host all 1.2.3.4/32 trust + pg_ident: + - mymap /^(.*)@mydomain\.com$ \1 + shared_preload_libraries: + - pgaudit + bootstrap: + initdb: + database: mydb + owner: dante + secret: + name: mydb-secret + postInitApplicationSQL: + - CREATE TABLE mytable (id serial PRIMARY KEY, name VARCHAR(255)); + postInitTemplateSQL: + - CREATE TABLE mytable (id serial PRIMARY KEY, name VARCHAR(255)); + postInitSQL: + - CREATE TABLE mytable (id serial PRIMARY KEY, name VARCHAR(255)); + superuserSecret: + name: supersecret-secret + enableSuperuserAccess: true + certificates: + serverCASecret: ca-secret + serverTLSSecret: tls-secret + replicationTLSSecret: replication-tls-secret + clientCASecret: client-ca-secret + imagePullSecrets: + - name: image-pull-secret + storage: + size: 256Mi + storageClass: standard + walStorage: + size: 256Mi + storageClass: standard + affinity: + topologyKey: kubernetes.io/hostname + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - node1 + - node2 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 100m + memory: 256Mi + priorityClassName: mega-high + primaryUpdateStrategy: supervised + primaryUpdateMethod: restart + logLevel: warning + managed: + roles: + - name: dante + ensure: present + comment: Dante Alighieri + login: true + inRoles: + - pg_monitor + - pg_signal_backend diff --git a/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster.yaml b/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster.yaml new file mode 100644 index 000000000..570ea8409 --- /dev/null +++ b/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster.yaml @@ -0,0 +1,81 @@ +type: postgresql +mode: standalone +cluster: + instances: 2 + imageName: ghcr.io/cloudnative-pg/crazycustomimage:99.99 + imagePullPolicy: Always + imagePullSecrets: + - name: "image-pull-secret" + storage: + size: 256Mi + storageClass: standard + walStorage: + enabled: true + size: 256Mi + storageClass: standard + postgresUID: 1001 + postgresGID: 1002 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 100m + memory: 256Mi + priorityClassName: mega-high + primaryUpdateMethod: restart + primaryUpdateStrategy: supervised + logLevel: warning + affinity: + topologyKey: kubernetes.io/hostname + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - node1 + - node2 + certificates: + serverCASecret: ca-secret + serverTLSSecret: tls-secret + replicationTLSSecret: replication-tls-secret + clientCASecret: client-ca-secret + enableSuperuserAccess: true + superuserSecret: supersecret-secret + roles: + - name: dante + ensure: present + comment: Dante Alighieri + login: true + inRoles: + - pg_monitor + - pg_signal_backend + postgresql: + parameters: + max_connections: "42" + pg_hba: + - host all 1.2.3.4/32 trust + pg_ident: + - mymap /^(.*)@mydomain\.com$ \1 + shared_preload_libraries: + - pgaudit + initdb: + database: mydb + owner: dante + secret: + name: mydb-secret + postInitApplicationSQL: + - CREATE TABLE mytable (id serial PRIMARY KEY, name VARCHAR(255)); + postInitTemplateSQL: + - CREATE TABLE mytable (id serial PRIMARY KEY, name VARCHAR(255)); + postInitSQL: + - CREATE TABLE mytable (id serial PRIMARY KEY, name VARCHAR(255)); + additionalLabels: + foo: bar + annotations: + foo: bar + +backups: + enabled: false diff --git a/charts/cluster/test/postgresql-cluster-configuration/chainsaw-test.yaml b/charts/cluster/test/postgresql-cluster-configuration/chainsaw-test.yaml new file mode 100644 index 000000000..b02b4900d --- /dev/null +++ b/charts/cluster/test/postgresql-cluster-configuration/chainsaw-test.yaml @@ -0,0 +1,30 @@ +## +# This is a test that verifies that non-default configuration options are correctly propagated to the CNPG cluster. +# P.S. This test is not designed to have a good running configuration, it is designed to test the configuration propagation! +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: postgresql-cluster-configuration +spec: + timeouts: + apply: 1s + assert: 5s + cleanup: 30s + steps: + - name: Install the non-default configuration cluster + try: + - script: + content: | + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./01-non_default_configuration_cluster.yaml \ + --wait \ + non-default-configuration ../../ + - assert: + file: ./01-non_default_configuration_cluster-assert.yaml + - name: Cleanup + try: + - script: + content: | + helm uninstall --namespace $NAMESPACE non-default-configuration diff --git a/charts/cluster/test/postgresql-minio-backup-restore/00-minio_cleanup-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/00-minio_cleanup-assert.yaml new file mode 100644 index 000000000..9c0f3eb48 --- /dev/null +++ b/charts/cluster/test/postgresql-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/postgresql-minio-backup-restore/00-minio_cleanup.yaml b/charts/cluster/test/postgresql-minio-backup-restore/00-minio_cleanup.yaml new file mode 100644 index 000000000..97cfc7389 --- /dev/null +++ b/charts/cluster/test/postgresql-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/postgresql-minio-backup-restore diff --git a/charts/cluster/test/postgresql-minio-backup-restore/01-standalone_cluster-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/01-standalone_cluster-assert.yaml new file mode 100644 index 000000000..0663e78c9 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/01-standalone_cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: standalone-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/postgresql-minio-backup-restore/01-standalone_cluster.yaml b/charts/cluster/test/postgresql-minio-backup-restore/01-standalone_cluster.yaml new file mode 100644 index 000000000..7db3fe9af --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/01-standalone_cluster.yaml @@ -0,0 +1,27 @@ +type: postgresql +mode: standalone + +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: "/postgresql-minio-backup-restore/v1" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/02-data_write-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/02-data_write-assert.yaml new file mode 100644 index 000000000..831f963d9 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/02-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/postgresql-minio-backup-restore/02-data_write.yaml b/charts/cluster/test/postgresql-minio-backup-restore/02-data_write.yaml new file mode 100644 index 000000000..e674d8b53 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/02-data_write.yaml @@ -0,0 +1,23 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: standalone-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|/\*|/|" ) + psql "$DB_URI" -c "CREATE TABLE mygoodtable (id serial PRIMARY KEY);" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/03-backup.yaml b/charts/cluster/test/postgresql-minio-backup-restore/03-backup.yaml new file mode 100644 index 000000000..c3afd4676 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/03-backup.yaml @@ -0,0 +1,8 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + method: barmanObjectStore + cluster: + name: standalone-cluster diff --git a/charts/cluster/test/postgresql-minio-backup-restore/03-backup_completed-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/03-backup_completed-assert.yaml new file mode 100644 index 000000000..7b1e9e534 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/03-backup_completed-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + cluster: + name: standalone-cluster + method: barmanObjectStore +status: + phase: completed diff --git a/charts/cluster/test/postgresql-minio-backup-restore/03-backup_running-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/03-backup_running-assert.yaml new file mode 100644 index 000000000..cbd9645c5 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/03-backup_running-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + cluster: + name: standalone-cluster + method: barmanObjectStore +status: + phase: running diff --git a/charts/cluster/test/postgresql-minio-backup-restore/03-checkpoint.yaml b/charts/cluster/test/postgresql-minio-backup-restore/03-checkpoint.yaml new file mode 100644 index 000000000..52862bf07 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/03-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: standalone-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/postgresql-minio-backup-restore/04-post_backup_data_write-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/04-post_backup_data_write-assert.yaml new file mode 100644 index 000000000..ad9be77a7 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/04-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/postgresql-minio-backup-restore/04-post_backup_data_write.yaml b/charts/cluster/test/postgresql-minio-backup-restore/04-post_backup_data_write.yaml new file mode 100644 index 000000000..2e56595de --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/04-post_backup_data_write.yaml @@ -0,0 +1,57 @@ +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-post-backup +spec: + template: + spec: + serviceAccountName: configmap-creator-sa + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: standalone-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 kubectl coreutils + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + DATE_NO_BAD_TABLE=$(date --rfc-3339=ns) + sleep 30 + psql "$DB_URI" -c "CREATE TABLE mybadtable (id serial PRIMARY KEY);" + kubectl create configmap date-no-bad-table --from-literal=date="$DATE_NO_BAD_TABLE" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/05-recovery_backup_cluster-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/05-recovery_backup_cluster-assert.yaml new file mode 100644 index 000000000..90c4b24db --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/05-recovery_backup_cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: recovery-backup-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/postgresql-minio-backup-restore/05-recovery_backup_cluster.yaml b/charts/cluster/test/postgresql-minio-backup-restore/05-recovery_backup_cluster.yaml new file mode 100644 index 000000000..c2731b3bf --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/05-recovery_backup_cluster.yaml @@ -0,0 +1,48 @@ +type: postgresql +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: "/postgresql-minio-backup-restore/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: "/postgresql-minio-backup-restore/v2" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/06-data_test-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/06-data_test-assert.yaml new file mode 100644 index 000000000..2ef11b8c9 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/06-data_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test-backup +status: + succeeded: 1 diff --git a/charts/cluster/test/postgresql-minio-backup-restore/06-data_test.yaml b/charts/cluster/test/postgresql-minio-backup-restore/06-data_test.yaml new file mode 100644 index 000000000..86a15439b --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/06-data_test.yaml @@ -0,0 +1,23 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test-backup +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: recovery-backup-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|/\*|/|" ) + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $$mygoodtable$$)' --csv -q 2>/dev/null)" = "t" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/07-recovery_object_store_cluster-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/07-recovery_object_store_cluster-assert.yaml new file mode 100644 index 000000000..f8693036b --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/07-recovery_object_store_cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: recovery-object-store-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/postgresql-minio-backup-restore/07-recovery_object_store_cluster.yaml b/charts/cluster/test/postgresql-minio-backup-restore/07-recovery_object_store_cluster.yaml new file mode 100644 index 000000000..7f059e394 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/07-recovery_object_store_cluster.yaml @@ -0,0 +1,48 @@ +type: postgresql +mode: recovery + +cluster: + instances: 2 + storage: + size: 256Mi + +recovery: + method: object_store + clusterName: "standalone-cluster" + 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: "/postgresql-minio-backup-restore/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: "/postgresql-minio-backup-restore/v2" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/08-data_test-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/08-data_test-assert.yaml new file mode 100644 index 000000000..36eb4ff81 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/08-data_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test-object-store +status: + succeeded: 1 diff --git a/charts/cluster/test/postgresql-minio-backup-restore/08-data_test.yaml b/charts/cluster/test/postgresql-minio-backup-restore/08-data_test.yaml new file mode 100644 index 000000000..94ac2c34e --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/08-data_test.yaml @@ -0,0 +1,23 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test-object-store +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: recovery-object-store-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|/\*|/|" ) + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $$mygoodtable$$)' --csv -q 2>/dev/null)" = "t" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/09-recovery_backup_pitr_cluster-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/09-recovery_backup_pitr_cluster-assert.yaml new file mode 100644 index 000000000..2b6b9651f --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/09-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/postgresql-minio-backup-restore/09-recovery_backup_pitr_cluster.yaml b/charts/cluster/test/postgresql-minio-backup-restore/09-recovery_backup_pitr_cluster.yaml new file mode 100644 index 000000000..c2731b3bf --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/09-recovery_backup_pitr_cluster.yaml @@ -0,0 +1,48 @@ +type: postgresql +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: "/postgresql-minio-backup-restore/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: "/postgresql-minio-backup-restore/v2" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/10-data_test-assert.yaml b/charts/cluster/test/postgresql-minio-backup-restore/10-data_test-assert.yaml new file mode 100644 index 000000000..6f14d5f23 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/10-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/postgresql-minio-backup-restore/10-data_test.yaml b/charts/cluster/test/postgresql-minio-backup-restore/10-data_test.yaml new file mode 100644 index 000000000..5fb4faf39 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/10-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/postgresql-minio-backup-restore/chainsaw-test.yaml b/charts/cluster/test/postgresql-minio-backup-restore/chainsaw-test.yaml new file mode 100644 index 000000000..79a7d2d76 --- /dev/null +++ b/charts/cluster/test/postgresql-minio-backup-restore/chainsaw-test.yaml @@ -0,0 +1,148 @@ +## +# This test sets up a CNPG cluster with MinIO backups and then restores the cluster from the backup using backup, +# object store, and object store with PITR recovery. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: postgresql-minio-backup-restore +spec: + timeouts: + apply: 1s + assert: 2m + cleanup: 1m + steps: + - name: Clear the MinIO bucket + try: + - apply: + file: ./00-minio_cleanup.yaml + - assert: + file: ./00-minio_cleanup-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=minio_cleanup + - name: Install the standalone 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-standalone_cluster.yaml \ + --wait \ + standalone ../../ + - assert: + file: 01-standalone_cluster-assert.yaml + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + - name: Write some data to the cluster + try: + - apply: + file: ./02-data_write.yaml + - assert: + file: ./02-data_write-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=data-write + - name: Create a backup + try: + - apply: + file: ./03-backup.yaml + - assert: + file: ./03-backup_running-assert.yaml + - apply: + file: ./03-checkpoint.yaml + - assert: + file: ./03-backup_completed-assert.yaml + - name: Write more data to the database after the backup + try: + - apply: + file: ./04-post_backup_data_write.yaml + - assert: + file: ./04-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 + try: + - script: + content: | + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./05-recovery_backup_cluster.yaml \ + --wait \ + recovery-backup ../../ + - assert: + file: ./05-recovery_backup_cluster-assert.yaml + - name: Verify the data on the backup recovery cluster exists + try: + - apply: + file: 06-data_test.yaml + - assert: + file: 06-data_test-assert.yaml + - name: Create a recovery cluster from object store + try: + - script: + content: | + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./07-recovery_object_store_cluster.yaml \ + --wait \ + recovery-object-store ../../ + - assert: + file: ./07-recovery_object_store_cluster-assert.yaml + - name: Verify the data on the object store recovery cluster exists + try: + - apply: + file: 08-data_test.yaml + - assert: + file: 08-data_test-assert.yaml + - 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 ./09-recovery_backup_pitr_cluster.yaml \ + --set recovery.pitrTarget.time="$DATE_NO_BAD_TABLE" \ + --wait \ + recovery-backup-pitr ../../ + - assert: + file: ./09-recovery_backup_pitr_cluster-assert.yaml + - name: Verify the pre-backup data on the recovery cluster exists but not the post-backup data + try: + - apply: + file: 10-data_test.yaml + - assert: + file: 10-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 standalone + helm uninstall --namespace $NAMESPACE recovery-backup + helm uninstall --namespace $NAMESPACE recovery-object-store + helm uninstall --namespace $NAMESPACE recovery-backup-pitr diff --git a/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster-assert.yaml b/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster-assert.yaml new file mode 100644 index 000000000..90ea90fd5 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: source-cluster +status: + readyInstances: 1 diff --git a/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster.yaml b/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster.yaml new file mode 100644 index 000000000..c11fed595 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster.yaml @@ -0,0 +1,6 @@ +type: postgresql +mode: "standalone" +cluster: + instances: 1 +backups: + enabled: false \ No newline at end of file diff --git a/charts/cluster/test/postgresql-pg_basebackup/01-data_write-assert.yaml b/charts/cluster/test/postgresql-pg_basebackup/01-data_write-assert.yaml new file mode 100644 index 000000000..831f963d9 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/01-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/postgresql-pg_basebackup/01-data_write.yaml b/charts/cluster/test/postgresql-pg_basebackup/01-data_write.yaml new file mode 100644 index 000000000..cc5a743ad --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/01-data_write.yaml @@ -0,0 +1,30 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_USER + valueFrom: + secretKeyRef: + name: source-cluster-superuser + key: username + - name: DB_PASS + valueFrom: + secretKeyRef: + name: source-cluster-superuser + key: password + - name: DB_URI + value: postgres://$(DB_USER):$(DB_PASS)@source-cluster-rw:5432 + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + psql "$DB_URI" -c "CREATE DATABASE mygooddb;" + psql "$DB_URI/mygooddb" -c "CREATE TABLE mygoodtable (id serial PRIMARY KEY);" diff --git a/charts/cluster/test/postgresql-pg_basebackup/02-pg_basebackup-cluster-assert.yaml b/charts/cluster/test/postgresql-pg_basebackup/02-pg_basebackup-cluster-assert.yaml new file mode 100644 index 000000000..9b953d44a --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/02-pg_basebackup-cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: pg-basebackup-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/postgresql-pg_basebackup/02-pg_basebackup-cluster.yaml b/charts/cluster/test/postgresql-pg_basebackup/02-pg_basebackup-cluster.yaml new file mode 100644 index 000000000..d389200e8 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/02-pg_basebackup-cluster.yaml @@ -0,0 +1,22 @@ +type: postgresql +mode: "recovery" +recovery: + method: "pg_basebackup" + pgBaseBackup: + source: + host: "source-cluster-rw" + database: "mygooddb" + username: "streaming_replica" + sslMode: "require" + sslKeySecret: + name: source-cluster-replication + key: tls.key + sslCertSecret: + name: source-cluster-replication + key: tls.crt + +cluster: + instances: 2 + +backups: + enabled: false \ No newline at end of file diff --git a/charts/cluster/test/postgresql-pg_basebackup/03-data_test-assert.yaml b/charts/cluster/test/postgresql-pg_basebackup/03-data_test-assert.yaml new file mode 100644 index 000000000..04df941e4 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/03-data_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test +status: + succeeded: 1 diff --git a/charts/cluster/test/postgresql-pg_basebackup/03-data_test.yaml b/charts/cluster/test/postgresql-pg_basebackup/03-data_test.yaml new file mode 100644 index 000000000..40eb9029a --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/03-data_test.yaml @@ -0,0 +1,23 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: pg-basebackup-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|/\*|/|" ) + test "$(psql "${DB_URI}mygooddb" -t -c 'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $$mygoodtable$$)' --csv -q 2>/dev/null)" = "t" diff --git a/charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml b/charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml new file mode 100644 index 000000000..85f2d9743 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml @@ -0,0 +1,64 @@ +## +# This is a test that provisions a regular (non CNPG) PostgreSQL cluster and attempts to perform a pg_basebackup recovery. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: postgresql-pg-basebackup +spec: + timeouts: + apply: 1s + assert: 2m + cleanup: 1m + steps: + - name: Install the external PostgreSQL cluster + try: + - script: + content: | + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./00-source-cluster.yaml \ + --wait \ + source ../../ + - assert: + file: ./00-source-cluster-assert.yaml + - apply: + file: ./01-data_write.yaml + - assert: + file: ./01-data_write-assert.yaml + - name: Install the pg_basebackup cluster + timeouts: + assert: 5m + try: + - script: + content: | + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./02-pg_basebackup-cluster.yaml \ + --wait \ + pg-basebackup ../../ + - assert: + file: ./02-pg_basebackup-cluster-assert.yaml + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + - name: Verify the data from step 1 exists + try: + - apply: + file: ./03-data_test.yaml + - assert: + file: ./03-data_test-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=data-test + - name: Cleanup + try: + - script: + content: | + helm uninstall --namespace $NAMESPACE source + helm uninstall --namespace $NAMESPACE pg-basebackup diff --git a/charts/cluster/test/timescale-minio-backup-restore/00-minio_cleanup-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/00-minio_cleanup-assert.yaml new file mode 100644 index 000000000..9c0f3eb48 --- /dev/null +++ b/charts/cluster/test/timescale-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/timescale-minio-backup-restore/00-minio_cleanup.yaml b/charts/cluster/test/timescale-minio-backup-restore/00-minio_cleanup.yaml new file mode 100644 index 000000000..ce71b1ef7 --- /dev/null +++ b/charts/cluster/test/timescale-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/timescale diff --git a/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster-assert.yaml new file mode 100644 index 000000000..3bbd2f8fe --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: timescale-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster.yaml b/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster.yaml new file mode 100644 index 000000000..f84117fe0 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster.yaml @@ -0,0 +1,28 @@ +type: timescaledb +mode: standalone + +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: "/timescale/v1" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test-assert.yaml new file mode 100644 index 000000000..aa63a21c9 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: timescale-test +status: + succeeded: 1 diff --git a/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test.yaml b/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test.yaml new file mode 100644 index 000000000..9b7581f96 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test.yaml @@ -0,0 +1,22 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: timescale-test +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: timescale-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 = '\''timescaledb'\'')' --csv -q 2>/dev/null)" = "t" \ No newline at end of file diff --git a/charts/cluster/test/timescale-minio-backup-restore/04-data_write-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/04-data_write-assert.yaml new file mode 100644 index 000000000..831f963d9 --- /dev/null +++ b/charts/cluster/test/timescale-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/timescale-minio-backup-restore/04-data_write.yaml b/charts/cluster/test/timescale-minio-backup-restore/04-data_write.yaml new file mode 100644 index 000000000..b827de143 --- /dev/null +++ b/charts/cluster/test/timescale-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: timescale-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/timescale-minio-backup-restore/05-backup.yaml b/charts/cluster/test/timescale-minio-backup-restore/05-backup.yaml new file mode 100644 index 000000000..be5e4b181 --- /dev/null +++ b/charts/cluster/test/timescale-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: timescale-cluster diff --git a/charts/cluster/test/timescale-minio-backup-restore/05-backup_completed-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/05-backup_completed-assert.yaml new file mode 100644 index 000000000..040b1a49e --- /dev/null +++ b/charts/cluster/test/timescale-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: timescale-cluster + method: barmanObjectStore +status: + phase: completed diff --git a/charts/cluster/test/timescale-minio-backup-restore/05-backup_running-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/05-backup_running-assert.yaml new file mode 100644 index 000000000..dc35727a0 --- /dev/null +++ b/charts/cluster/test/timescale-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: timescale-cluster + method: barmanObjectStore +status: + phase: running diff --git a/charts/cluster/test/timescale-minio-backup-restore/05-checkpoint.yaml b/charts/cluster/test/timescale-minio-backup-restore/05-checkpoint.yaml new file mode 100644 index 000000000..3ba7fc727 --- /dev/null +++ b/charts/cluster/test/timescale-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: timescale-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/timescale-minio-backup-restore/06-post_backup_data_write-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/06-post_backup_data_write-assert.yaml new file mode 100644 index 000000000..ad9be77a7 --- /dev/null +++ b/charts/cluster/test/timescale-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/timescale-minio-backup-restore/06-post_backup_data_write.yaml b/charts/cluster/test/timescale-minio-backup-restore/06-post_backup_data_write.yaml new file mode 100644 index 000000000..8585b247d --- /dev/null +++ b/charts/cluster/test/timescale-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: timescale-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/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml new file mode 100644 index 000000000..2b6b9651f --- /dev/null +++ b/charts/cluster/test/timescale-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/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml b/charts/cluster/test/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml new file mode 100644 index 000000000..7e9c38f55 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml @@ -0,0 +1,48 @@ +type: timescaledb +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: "/timescale/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: "/timescale/v2" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/timescale-minio-backup-restore/08-data_test-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/08-data_test-assert.yaml new file mode 100644 index 000000000..6f14d5f23 --- /dev/null +++ b/charts/cluster/test/timescale-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/timescale-minio-backup-restore/08-data_test.yaml b/charts/cluster/test/timescale-minio-backup-restore/08-data_test.yaml new file mode 100644 index 000000000..5fb4faf39 --- /dev/null +++ b/charts/cluster/test/timescale-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/timescale-minio-backup-restore/chainsaw-test.yaml b/charts/cluster/test/timescale-minio-backup-restore/chainsaw-test.yaml new file mode 100644 index 000000000..496153398 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/chainsaw-test.yaml @@ -0,0 +1,129 @@ +## +# This test sets up a timescale cluster with MinIO backups and ensured that timescale extensions are installed and +# PITR recovery is enabled and working. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: timescale +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 timescale 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-timescale_cluster.yaml \ + --wait \ + timescale ../../ + - assert: + file: ./01-timescale_cluster-assert.yaml + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + - podLogs: + selector: cnpg.io/cluster=timescale-cluster + - name: Verify timescale extensions are installed + timeouts: + apply: 1s + assert: 30s + try: + - apply: + file: 03-timescale_test.yaml + - assert: + file: 03-timescale_test-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=data-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 timescale diff --git a/charts/cluster/values.schema.json b/charts/cluster/values.schema.json index a2354a8f8..9e35d7a90 100644 --- a/charts/cluster/values.schema.json +++ b/charts/cluster/values.schema.json @@ -187,6 +187,9 @@ "enableSuperuserAccess": { "type": "boolean" }, + "imageCatalogRef": { + "type": "object" + }, "imageName": { "type": "string" }, @@ -242,7 +245,21 @@ "type": "integer" }, "postgresql": { - "type": "object" + "type": "object", + "properties": { + "parameters": { + "type": "object" + }, + "pg_hba": { + "type": "array" + }, + "pg_ident": { + "type": "array" + }, + "shared_preload_libraries": { + "type": "array" + } + } }, "primaryUpdateMethod": { "type": "string" @@ -276,6 +293,9 @@ "walStorage": { "type": "object", "properties": { + "enabled": { + "type": "boolean" + }, "size": { "type": "string" }, @@ -289,6 +309,17 @@ "fullnameOverride": { "type": "string" }, + "imageCatalog": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "images": { + "type": "array" + } + } + }, "mode": { "type": "string" }, @@ -423,6 +454,90 @@ "method": { "type": "string" }, + "pgBaseBackup": { + "type": "object", + "properties": { + "database": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "database": { + "type": "string" + }, + "host": { + "type": "string" + }, + "passwordSecret": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "port": { + "type": "integer" + }, + "sslCertSecret": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "sslKeySecret": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "sslMode": { + "type": "string" + }, + "sslRootCertSecret": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "username": { + "type": "string" + } + } + } + } + }, "pitrTarget": { "type": "object", "properties": { @@ -469,6 +584,20 @@ }, "type": { "type": "string" + }, + "version": { + "type": "object", + "properties": { + "postgis": { + "type": "string" + }, + "postgresql": { + "type": "string" + }, + "timescaledb": { + "type": "string" + } + } } } } diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index b0591d58a..8398c74d1 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -7,8 +7,17 @@ fullnameOverride: "" # -- Type of the CNPG database. Available types: # * `postgresql` # * `postgis` +# * `timescaledb` type: postgresql +version: + # -- PostgreSQL major version to use + postgresql: "16" + # -- If using TimescaleDB, specify the version + timescaledb: "2.15" + # -- If using PostGIS, specify the version + postgis: "3.4" + ### # -- Cluster mode of operation. Available modes: # * `standalone` - default mode. Creates new or updates an existing CNPG cluster. @@ -81,6 +90,39 @@ recovery: # -- Name of the backup credentials secret name: "" + # See https://cloudnative-pg.io/documentation/1.22/bootstrap/#bootstrap-from-a-live-cluster-pg_basebackup + pgBaseBackup: + # -- Name of the database used by the application. Default: `app`. + database: app + # -- Name of the owner of the database in the instance to be used by applications. Defaults to the value of the `database` key. + secret: "" + # -- Name of the secret containing the initial credentials for the owner of the user database. If empty a new secret will be created from scratch + owner: "" + source: + host: "" + port: 5432 + username: "" + database: "app" + sslMode: "verify-full" + passwordSecret: + # -- Whether to create a secret for the password + create: false + # -- Name of the secret containing the password + name: "" + # -- The key in the secret containing the password + key: "password" + # -- The password value to use when creating the secret + value: "" + sslKeySecret: + name: "" + key: "" + sslCertSecret: + name: "" + key: "" + sslRootCertSecret: + name: "" + key: "" + cluster: # -- Number of instances @@ -90,6 +132,11 @@ cluster: # :@sha256: imageName: "" # Default value depends on type (postgresql/postgis/timescaledb) + # -- Reference to `ImageCatalog` of `ClusterImageCatalog`, if specified takes precedence over `cluster.imageName` + imageCatalogRef: {} + # kind: ImageCatalog + # name: postgresql + # -- Image pull policy. One of Always, Never or IfNotPresent. If not defined, it defaults to IfNotPresent. Cannot be updated. # More info: https://kubernetes.io/docs/concepts/containers/images#updating-images imagePullPolicy: IfNotPresent @@ -103,14 +150,15 @@ cluster: storageClass: "" walStorage: + enabled: false size: 1Gi storageClass: "" # -- The UID of the postgres user inside the image, defaults to 26 - postgresUID: 26 + postgresUID: -1 # -- The GID of the postgres user inside the image, defaults to 26 - postgresGID: 26 + postgresGID: -1 # -- Resources requirements of every generated Pod. # Please refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ for more information. @@ -127,7 +175,7 @@ cluster: priorityClassName: "" # -- Method to follow to upgrade the primary server during a rolling update procedure, after all replicas have been - # successfully updated. It can be switchover (default) or in-place (restart). + # successfully updated. It can be switchover (default) or restart. primaryUpdateMethod: switchover # -- Strategy to follow to upgrade the primary server during a rolling update procedure, after all replicas have been @@ -180,20 +228,27 @@ cluster: # - CNPGClusterZoneSpreadWarning # -- Custom Prometheus metrics customQueries: [] - # - name: "pg_cache_hit_ratio" - # query: "SELECT current_database() as datname, sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) as ratio FROM pg_statio_user_tables;" - # metrics: - # - datname: - # usage: "LABEL" - # description: "Name of the database" - # - ratio: - # usage: GAUGE - # description: "Cache hit ratio" - - # -- Configuration of the PostgreSQL server. - # See: https://cloudnative-pg.io/documentation/current/cloudnative-pg.v1/#postgresql-cnpg-io-v1-PostgresConfiguration - postgresql: {} - # max_connections: 300 + # - name: "pg_cache_hit_ratio" + # query: "SELECT current_database() as datname, sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) as ratio FROM pg_statio_user_tables;" + # metrics: + # - datname: + # usage: "LABEL" + # description: "Name of the database" + # - ratio: + # usage: GAUGE + # description: "Cache hit ratio" + + postgresql: + # -- PostgreSQL configuration options (postgresql.conf) + parameters: {} + # max_connections: 300 + # -- PostgreSQL Host Based Authentication rules (lines to be appended to the pg_hba.conf file) + pg_hba: [] + # - host all all 10.244.0.0/16 md5 + pg_ident: [] + # - mymap /^(.*)@mydomain\.com$ \1 + shared_preload_libraries: [] + # - pgaudit # -- BootstrapInitDB is the configuration of the bootstrap process when initdb is used. # See: https://cloudnative-pg.io/documentation/current/bootstrap/ @@ -201,9 +256,14 @@ cluster: initdb: {} # database: app # owner: "" # Defaults to the database name - # secret: "" # Name of the secret containing the initial credentials for the owner of the user database. If empty a new secret will be created from scratch + # secret: + # name: "" # Name of the secret containing the initial credentials for the owner of the user database. If empty a new secret will be created from scratch + # options: [] + # encoding: UTF8 # postInitSQL: # - CREATE EXTENSION IF NOT EXISTS vector; + # postInitApplicationSQL: [] + # postInitTemplateSQL: [] additionalLabels: {} annotations: {} @@ -286,6 +346,13 @@ backups: # -- Retention policy for backups retentionPolicy: "30d" +imageCatalog: + # -- Whether to provision an image catalog. If imageCatalog.images is empty this option will be ignored. + create: true + # -- List of images to be provisioned in an image catalog. + images: [] + # - image: ghcr.io/your_repo/your_image:your_tag + # major: 16 pooler: # -- Whether to enable PgBouncer