An open API service indexing awesome lists of open source software.

https://github.com/pascalinthecloud/terraform-module-cnpg-database

Terraform module for declarative PostgreSQL database creation in CloudNative-PG with automatic user and secret management.
https://github.com/pascalinthecloud/terraform-module-cnpg-database

Last synced: 4 months ago
JSON representation

Terraform module for declarative PostgreSQL database creation in CloudNative-PG with automatic user and secret management.

Awesome Lists containing this project

README

          

# Terraform Module: CloudNative-PG Database

Terraform module for creating PostgreSQL databases in CloudNative-PG clusters with automatic user and secret management.

## Features

- 🔐 Automatic password secret creation with hot-reload support
- 📦 Database and user provisioned declaratively
- 🔌 Connection details secret for easy application integration
- 🏷️ Customizable labels for all resources
- ✅ Input validation for security best practices
- 💾 S3-based backup configuration with CloudNativePG/Barman
- 📅 Automated scheduled backups with retention policies
- 🔄 Point-in-Time Recovery (PITR) support

## Prerequisites

- CloudNative-PG operator installed in your Kubernetes cluster

## Usage

### Basic Example

```hcl
module "my_app_database" {
source = "github.com/pascalinthecloud/terraform-module-cnpg-database"

databases = [{
name = "my-app"
owner = "my_app_user"
password = var.database_password # Use a secure variable or secret manager
}]

cluster = {
name = "shared-postgres-prod"
namespace = "databases-prod"
}
}
```

### Complete Example with Labels

```hcl
module "my_app_database" {
source = "github.com/pascalinthecloud/terraform-module-cnpg-database"

databases = [{
name = "my-app"
owner = "my_app_user"
password = var.database_password
}]

cluster = {
name = "shared-postgres-prod"
namespace = "databases-prod"
}

labels = {
app = "my-app"
environment = "production"
managed-by = "terraform"
}
}
```

### Custom PostgreSQL Database Name

```hcl
module "my_app_database" {
source = "github.com/pascalinthecloud/terraform-module-cnpg-database"

databases = [{
name = "my-app"
pg_database_name = "myapp_production" # Custom PostgreSQL database name
owner = "my_app_user"
password = var.database_password
}]

cluster = {
name = "shared-postgres-prod"
namespace = "databases-prod"
}
}
```

### Connection Secret in Different Namespace

```hcl
module "my_app_database" {
source = "github.com/pascalinthecloud/terraform-module-cnpg-database"

databases = [{
name = "my-app"
owner = "my_app_user"
password = var.database_password
connection_secret_namespace = "my-app-namespace" # Deploy connection secret to app namespace
}]

cluster = {
name = "shared-postgres-prod"
namespace = "databases-prod"
}
}
```

### With S3 Backups Enabled

```hcl
module "my_app_database" {
source = "github.com/pascalinthecloud/terraform-module-cnpg-database"

databases = [{
name = "my-app"
owner = "my_app_user"
password = var.database_password
}]

cluster = {
name = "shared-postgres-prod"
namespace = "databases-prod"
}

# Enable S3 backups
backup = {
enabled = true
s3_endpoint_url = "https://s3.amazonaws.com" # Or your S3-compatible endpoint
s3_bucket_name = "my-postgres-backups"
s3_access_key_id = var.s3_access_key_id
s3_secret_access_key = var.s3_secret_access_key
retention_policy = "30d"
schedule = "0 2 * * *" # Daily at 2 AM UTC
target = "prefer-standby"
wal_compression = "gzip"
data_compression = "gzip"
}

labels = {
app = "my-app"
environment = "production"
}
}
```

## Requirements

| Name | Version |
|------|---------|
| [terraform](#requirement\_terraform) | >= 1.9.0 |
| [kubernetes](#requirement\_kubernetes) | >= 2.0 |

## Providers

| Name | Version |
|------|---------|
| [kubernetes](#provider\_kubernetes) | >= 2.0 |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [kubernetes_manifest.cluster](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource |
| [kubernetes_manifest.database](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource |
| [kubernetes_manifest.scheduled_backup](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource |
| [kubernetes_role_binding_v1.backup_secret_reader](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/role_binding_v1) | resource |
| [kubernetes_role_v1.backup_secret_reader](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/role_v1) | resource |
| [kubernetes_secret_v1.backup_credentials](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |
| [kubernetes_secret_v1.connection](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |
| [kubernetes_secret_v1.database_password](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| [backup](#input\_backup) | Backup configuration for S3-based backups using Barman |

object({
enabled = optional(bool, false)
s3_endpoint_url = optional(string, "")
s3_bucket_name = optional(string, "")
s3_access_key_id = optional(string, "")
s3_secret_access_key = optional(string, "")
retention_policy = optional(string, "30d")
schedule = optional(string, "0 2 * * *")
wal_compression = optional(string, "gzip")
data_compression = optional(string, "gzip")
jobs = optional(number, 2)
target = optional(string, "prefer-standby")
create_scheduled_backup = optional(bool, true)
immediate = optional(bool, false)
})
| `{}` | no |
| [cluster](#input\_cluster) | CloudNative-PG cluster configuration object |
object({
name = optional(string, "default-cluster")
namespace = optional(string, "default")
instances = optional(number, 1)
storage_class = optional(string, "longhorn") # Override with your cluster's available storage class
storage_size = optional(string, "10Gi")
inherited_labels = optional(map(string), {})
inherited_annotations = optional(map(string), {})
postgresql_max_connections = optional(string, "100")
postgresql_shared_buffers = optional(string, "256MB")
postgresql_effective_cache_size = optional(string, "1GB")
postgresql_maintenance_work_mem = optional(string, "64MB")
postgresql_checkpoint_completion_target = optional(string, "0.9")
postgresql_wal_buffers = optional(string, "16MB")
postgresql_default_statistics_target = optional(string, "100")
postgresql_random_page_cost = optional(string, "1.1")
postgresql_effective_io_concurrency = optional(string, "200")
postgresql_work_mem = optional(string, "2621kB")
postgresql_min_wal_size = optional(string, "512MB")
postgresql_max_wal_size = optional(string, "2GB")
bootstrap_database = optional(string, "postgres")
bootstrap_owner = optional(string, "postgres")
enable_pod_monitor = optional(bool, true)
resources = optional(object({
requests = optional(object({
memory = optional(string, "512Mi")
cpu = optional(string, "250m")
}), {})
limits = optional(object({
memory = optional(string)
cpu = optional(string)
}), null)
}), {})
})
| `{}` | no |
| [databases](#input\_databases) | List of databases to create. Each object must have name, owner, password, and database\_reclaim\_policy.
If the list is empty, the cluster will be created with no managed database users.
Users can manually add roles to the cluster or add databases through this module later. |
list(object({
name = string
owner = string
password = string
database_reclaim_policy = optional(string, "retain")
pg_database_name = optional(string, "")
create_connection_secret = optional(bool, true)
connection_secret_namespace = optional(string, "")
}))
| `[]` | no |
| [labels](#input\_labels) | Additional labels to add to all resources | `map(string)` | `{}` | no |

## Outputs

| Name | Description |
|------|-------------|
| [backup\_destination\_path](#output\_backup\_destination\_path) | S3 destination path for backups |
| [backup\_enabled](#output\_backup\_enabled) | Whether backups are configured for this cluster |
| [backup\_secret\_name](#output\_backup\_secret\_name) | Name of the Kubernetes secret containing backup credentials |
| [connection\_host](#output\_connection\_host) | Database connection hostname |
| [connection\_port](#output\_connection\_port) | Database connection port |
| [connection\_secret\_names](#output\_connection\_secret\_names) | Names of the Kubernetes secrets containing connection details |
| [connection\_uris](#output\_connection\_uris) | Full PostgreSQL connection URIs for each database |
| [database\_names](#output\_database\_names) | Names of the created databases in PostgreSQL |
| [owner\_usernames](#output\_owner\_usernames) | Usernames of the database owners |
| [password\_secret\_names](#output\_password\_secret\_names) | Names of the Kubernetes secrets containing the database passwords |
| [scheduled\_backup\_name](#output\_scheduled\_backup\_name) | Name of the ScheduledBackup resource |

## Backup Configuration

This module supports S3-based backups using CloudNativePG's Barman integration.

### Prerequisites for Backups

1. **S3-Compatible Storage**: AWS S3, MinIO, or other S3-compatible storage
2. **S3 Bucket**: Pre-created bucket for storing backups
3. **S3 Credentials**: Access key ID and secret access key

### Backup Features

- **Continuous WAL Archiving**: Write-Ahead Logs continuously archived to S3
- **Scheduled Backups**: Automated backups based on cron schedule
- **Retention Policies**: Automatic cleanup of old backups
- **Compression**: Configurable compression (gzip, bzip2, snappy)
- **PITR**: Point-in-Time Recovery support

### Backup Configuration

The `backup` object accepts the following properties:

| Property | Description | Default | Required |
|----------|-------------|---------|----------|
| `enabled` | Enable S3 backups | `false` | No |
| `s3_endpoint_url` | S3 endpoint URL (empty for AWS S3) | `""` | When enabled |
| `s3_bucket_name` | S3 bucket name | `""` | When enabled |
| `s3_access_key_id` | S3 access key ID | `""` | When enabled |
| `s3_secret_access_key` | S3 secret access key | `""` | When enabled |
| `retention_policy` | Retention policy (e.g., "30d") | `"30d"` | No |
| `schedule` | Cron schedule | `"0 2 * * *"` | No |
| `wal_compression` | WAL compression algorithm | `"gzip"` | No |
| `data_compression` | Data compression algorithm | `"gzip"` | No |
| `jobs` | Parallel backup jobs | `2` | No |
| `target` | Backup target instance | `"prefer-standby"` | No |
| `create_scheduled_backup` | Create ScheduledBackup resource | `true` | No |
| `immediate` | Take immediate backup on creation | `false` | No |

### What Gets Created for Backups

When `backup.enabled = true`, the module creates:

1. **Kubernetes Secret**: Stores S3 credentials securely
2. **RBAC Role**: Allows cluster service account to read backup credentials
3. **RBAC RoleBinding**: Binds the role to the cluster's service account
4. **Cluster Backup Config**: Configures Barman object store in cluster spec
5. **ScheduledBackup Resource**: Creates automated backup schedule (if `create_scheduled_backup = true`)

### Backup Schedule Examples

```hcl
# Daily at 2 AM UTC
backup = {
schedule = "0 2 * * *"
}

# Every 6 hours
backup = {
schedule = "0 */6 * * *"
}

# Weekly on Sunday at 3 AM
backup = {
schedule = "0 3 * * 0"
}

# Monthly on the 1st at 1 AM
backup = {
schedule = "0 1 1 * *"
}
```

### Monitoring Backups

Check backup status:

```bash
# List all backups
kubectl get backups -n

# Check scheduled backup
kubectl get scheduledbackup -n

# Describe backup details
kubectl describe backup -n

# View cluster backup status
kubectl describe cluster -n
```

### Point-in-Time Recovery (PITR)

To restore from backups, create a new cluster with recovery configuration. See the [CloudNativePG documentation](https://cloudnative-pg.io/documentation/current/recovery/) for details.

## Connection Details Secret

When `create_connection_secret = true` (default), the module creates a secret with the following keys:

- `host` - Database hostname
- `port` - Database port (5432)
- `database` - Database name
- `username` - Database username
- `password` - Database password
- `uri` - Full connection URI

Example usage in a pod:

```yaml
env:
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: my-app-db-connection
key: host
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: my-app-db-connection
key: uri
```

## How It Works

1. **Password Secret**: Creates a `kubernetes.io/basic-auth` secret with the `cnpg.io/reload` label for hot password updates
2. **Database CRD**: Creates a CloudNative-PG Database resource that triggers database creation
3. **Connection Secret**: Optionally creates a connection details secret for application use

The CloudNative-PG operator reconciles the managed role in the cluster and creates the database with the specified owner.

## Security Best Practices

- Always use a secure method to provide passwords (e.g., Terraform variables with encryption, secret managers)
- Never commit passwords to version control
- Use strong passwords (minimum 8 characters enforced by validation)
- Consider using Kubernetes RBAC to restrict access to password secrets

## License

MIT

## Author

Pascal Toepke