Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Version columns show what the child `Application` currently pins. `—` in the V
|---|---|---|---|
| [`cert-manager`](./infra/cert-manager/) | `install` / `selfsigned` / `cluster-ca` / `vault-pki` | `v1.19.2` + — + — + — | cert-manager chart, self-signed `ClusterIssuer`, CA chain (`cluster-ca` Certificate + ClusterIssuer + one or two wildcards), Vault PKI `ClusterIssuer` (token auth) |
| [`cilium`](./infra/cilium/) | `chart` / `lb` / `gateway` | `1.18.5` + — + — | CNI with kube-proxy replacement, L2 LoadBalancer IP pool, Gateway API `Gateway` |
| [`cloudnative-pg`](./infra/cloudnative-pg/) | `install` / `cluster` | `0.29.0` + `0.8.1` | CloudNativePG operator + a PostgreSQL `Cluster` CR (official `cnpg/cluster` chart) |
| [`external-secrets`](./infra/external-secrets/) | `install` / `cluster-secret-store-vault` | `2.4.1` + — | External Secrets Operator (ESO) + a templated `ClusterSecretStore` for Vault (k8s-auth, KV v2). Cluster overlays consume `cluster-secret-store-vault` per `(cluster, KV path)` pair |
| [`kyverno`](./infra/kyverno/) | `install` | `3.8.0` | Kyverno admission controller (policy engine) — `ClusterPolicy` / `Policy` / `PolicyException` CRDs. Controller only; policies are cluster-specific |
| [`longhorn`](./infra/longhorn/) | `install` | `1.11.2` | Longhorn distributed block storage; GitOps-friendly defaults (`preUpgradeChecker.jobEnabled: false`, `defaultClassReplicaCount: 1` for single-node-safe install) |
Expand Down
247 changes: 247 additions & 0 deletions infra/cloudnative-pg/README.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions infra/cloudnative-pg/cluster/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
apiVersion: v2
name: cloudnative-pg-cluster
description: App-of-apps chart packaging the official CloudNativePG cluster Helm chart (cnpg/cluster) — renders one postgresql.cnpg.io/v1 Cluster
type: application
version: 0.1.0
appVersion: "0.8.1"
84 changes: 84 additions & 0 deletions infra/cloudnative-pg/cluster/templates/chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{{- $destKey := .Values.destination.server | default .Values.destination.name }}
{{/*
Derive from clusterName + destination, not destination alone: several Postgres
clusters can land on one target cluster and must not collide.
*/}}
{{- $defaultName := printf "cnpg-cluster-%s" (sha1sum (printf "%s@%s" .Values.clusterName $destKey) | trunc 8) }}
{{- $appName := .Values.applicationName | default $defaultName }}
{{/*
initdb — emit `secret` ONLY when appSecretName is non-empty. Otherwise CNPG
generates `<clusterName>-app` (a kubernetes.io/basic-auth Secret carrying
username/password/dbname), and an empty `secret.name` breaks bootstrap.

Note: upstream runs `cluster.initdb.owner` through `tpl`, so a literal
`{{ ... }}` reaching that value is evaluated by the upstream chart. The schema
forbids braces in `owner` to keep that from happening.
*/}}
{{- $initdb := dict
"database" .Values.database
"owner" .Values.owner
}}
{{- if .Values.appSecretName }}
{{- $_ := set $initdb "secret" (dict "name" .Values.appSecretName) }}
{{- end }}
{{- $clusterBlock := dict
"instances" (int .Values.instances)
"storage" (dict
"size" .Values.storage.size
"storageClass" .Values.storage.storageClass
)
"walStorage" (dict
"enabled" .Values.walStorage.enabled
"size" .Values.walStorage.size
)
"initdb" $initdb
"enableSuperuserAccess" .Values.enableSuperuserAccess
"enablePDB" .Values.enablePDB
"monitoring" (dict
"enabled" .Values.monitoring.enabled
"podMonitor" (dict "enabled" .Values.monitoring.podMonitor)
"prometheusRule" (dict "enabled" .Values.monitoring.prometheusRule)
)
}}
{{- $computed := dict
"nameOverride" .Values.clusterName
"fullnameOverride" .Values.clusterName
"type" .Values.type
"mode" .Values.mode
"version" (dict "postgresql" .Values.postgresqlVersion)
"cluster" $clusterBlock
"backups" (dict "enabled" .Values.backups.enabled)
}}
{{- $extra := .Values.extraValues | default dict }}
{{- $merged := mergeOverwrite $computed (deepCopy $extra) }}
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: {{ $appName | quote }}
namespace: argocd
annotations:
# 0 — after the operator's -10 wave, so the CRDs exist before the Cluster CR.
# Sync waves only order resources within ONE Application: syncing this entry
# against a cluster with no operator fails the dry-run on the unknown
# postgresql.cnpg.io/v1 kind, and syncPolicy.retry is what recovers it.
argocd.argoproj.io/sync-wave: "0"
spec:
project: {{ .Values.project | quote }}
source:
repoURL: https://cloudnative-pg.github.io/charts
chart: cluster
targetRevision: {{ .Values.chartVersion | quote }}
helm:
releaseName: {{ .Values.clusterName | quote }}
valuesObject:
{{- toYaml $merged | nindent 8 }}
destination:
{{- if .Values.destination.name }}
name: {{ .Values.destination.name | quote }}
{{- else }}
server: {{ .Values.destination.server | quote }}
{{- end }}
namespace: {{ .Values.destination.namespace | quote }}
syncPolicy:
{{- toYaml .Values.syncPolicy | nindent 4 }}
112 changes: 112 additions & 0 deletions infra/cloudnative-pg/cluster/values.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "cloudnative-pg cluster app-of-apps chart",
"type": "object",
"additionalProperties": false,
"required": ["project", "destination", "chartVersion", "clusterName"],
"properties": {
"applicationName": {
"type": "string",
"minLength": 1,
"description": "Optional explicit name for the rendered ArgoCD Application. Defaults to a name derived from clusterName + destination, so several Postgres clusters can land on one target cluster without colliding."
},
"project": { "type": "string", "minLength": 1 },
"destination": {
"type": "object",
"additionalProperties": false,
"description": "Argo destination for the rendered Application. Provide `server` (cluster URL) or `name` (registered cluster name) — at least one required. Template prefers `name` when both are set. `namespace` is required and has no good default: a CNPG Cluster belongs in the namespace of the app that consumes it, not in the operator's namespace.",
"properties": {
"server": { "type": "string", "format": "uri" },
"name": { "type": "string", "minLength": 1 },
"namespace": { "type": "string", "minLength": 1 }
},
"required": ["namespace"],
"anyOf": [
{ "required": ["server"] },
{ "required": ["name"] }
]
},
"chartVersion": { "type": "string", "minLength": 1 },
"clusterName": {
"type": "string",
"minLength": 1,
"maxLength": 50,
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$",
"description": "Name of the Cluster CR (upstream nameOverride + fullnameOverride). Renaming creates a new Cluster and orphans the old PVC."
},
"type": {
"type": "string",
"enum": ["postgresql", "postgis", "timescaledb"]
},
"mode": {
"type": "string",
"enum": ["standalone", "recovery", "replica"]
},
"postgresqlVersion": {
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$",
"description": "Major PostgreSQL version, as a string."
},
"instances": { "type": "integer", "minimum": 1 },
"storage": {
"type": "object",
"additionalProperties": false,
"properties": {
"size": { "type": "string", "minLength": 1, "pattern": "^[0-9]+(Ki|Mi|Gi|Ti|Pi|Ei)?$" },
"storageClass": { "type": "string", "description": "Empty string = the target cluster's default StorageClass." }
},
"required": ["size"]
},
"walStorage": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean" },
"size": { "type": "string", "minLength": 1, "pattern": "^[0-9]+(Ki|Mi|Gi|Ti|Pi|Ei)?$" }
},
"required": ["enabled"]
},
"database": { "type": "string", "minLength": 1 },
"owner": {
"type": "string",
"minLength": 1,
"pattern": "^[^{}]*$",
"description": "Owner role for the application database. Upstream runs this value through `tpl`, so Go-template braces are forbidden here."
},
"appSecretName": {
"type": "string",
"description": "Existing kubernetes.io/basic-auth Secret with the owner's credentials. Empty = CNPG generates `<clusterName>-app`. Emitted to the upstream chart only when non-empty."
},
"enableSuperuserAccess": { "type": "boolean" },
"enablePDB": { "type": "boolean" },
"monitoring": {
"type": "object",
"additionalProperties": false,
"description": "Prometheus Operator integration for the PostgreSQL pods. `podMonitor` and `prometheusRule` are gated by `enabled`.",
"properties": {
"enabled": { "type": "boolean" },
"podMonitor": { "type": "boolean" },
"prometheusRule": { "type": "boolean" }
},
"required": ["enabled"]
},
"backups": {
"type": "object",
"additionalProperties": false,
"description": "Backups are off by default; the upstream chart warns that recovery will not be possible. Configure the object-store target through extraValues.",
"properties": {
"enabled": { "type": "boolean" }
},
"required": ["enabled"]
},
"extraValues": {
"type": "object",
"description": "Deep-merged on top of the computed valuesObject passed to the upstream chart.",
"additionalProperties": true
},
"syncPolicy": {
"type": "object",
"additionalProperties": true
}
}
}
119 changes: 119 additions & 0 deletions infra/cloudnative-pg/cluster/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
# ArgoCD AppProject the rendered Application belongs to.
project: default

# Where the rendered Application deploys.
#
# There is NO good default for `namespace` — it is `required` in the schema and
# is the value a consumer is most likely to get wrong. A CNPG Cluster belongs in
# the namespace of the app that consumes it (backstage's cluster in `backstage`,
# zitadel's in `zitadel`), NOT in the operator's namespace (`postgres`). The
# value below is a placeholder so the chart renders standalone — override it.
destination:
server: https://kubernetes.default.svc
namespace: cnpg-cluster

# Official CloudNativePG cluster Helm chart (https://cloudnative-pg.github.io/charts).
# renovate: datasource=helm depName=cluster registryUrl=https://cloudnative-pg.github.io/charts
chartVersion: 0.8.1

# Name of the Cluster CR (upstream nameOverride + fullnameOverride). Also seeds
# the derived Application name, so two Clusters on one target cluster don't
# collide. Renaming this creates a NEW Cluster and orphans the old PVC —
# see the teardown caveat in the README.
clusterName: postgres-cluster

# Cluster flavour: postgresql | postgis | timescaledb.
type: postgresql

# Bootstrap mode: standalone | recovery | replica.
# `recovery` and `replica` need their own upstream config — use extraValues.
mode: standalone

# Major PostgreSQL version. Quoted — upstream expects a string.
postgresqlVersion: "17"

# Number of PostgreSQL instances.
#
# 1 is lab-shaped, matching stuttgart-things/helm's database/postgres-cluster.yaml.gotmpl.
# At 1 an instance failure is downtime and possible data loss — there is no
# replica to fail over to. Raise to 3 for anything you care about.
instances: 1

# PGDATA volume. Empty storageClass = the cluster's default StorageClass.
storage:
size: 8Gi
storageClass: ""

# Separate volume for the write-ahead log. Off by default; enable to keep WAL
# I/O off the data volume on busy clusters.
walStorage:
enabled: false
size: 1Gi

# initdb bootstrap: the application database and its owner role.
database: app
owner: app

# Name of an existing Secret holding the owner's credentials — must be a
# kubernetes.io/basic-auth Secret with `username` / `password` (and `dbname`).
#
# Leave EMPTY to let CNPG generate `<clusterName>-app` itself. An empty
# `secret.name` reaching the upstream chart breaks bootstrap, so the template
# emits `cluster.initdb.secret` only when this is non-empty.
#
# Point it at a Secret materialised by External Secrets — see the README.
appSecretName: ""

# Superuser (`postgres`) access. Off by default: when disabled the operator
# blanks the postgres password, so the only way in is the owner role.
enableSuperuserAccess: false

# PodDisruptionBudget. Off by default because it blocks node drains on a
# single-instance cluster. Turn on together with `instances: 3`.
enablePDB: false

# Prometheus Operator integration for the PostgreSQL pods. All off by default;
# each requires the Prometheus Operator CRDs on the target cluster.
# `podMonitor` and `prometheusRule` are additionally gated by `enabled`.
monitoring:
enabled: false
podMonitor: false
prometheusRule: false

# Backups. Off by default, which is why the upstream chart prints
# Warning! Backups not enabled. Recovery will not be possible!
# Enabling backups needs an object-store target — configure the rest of the
# upstream `backups.*` tree through extraValues.
backups:
enabled: false

# Deep-merged on top of the computed valuesObject passed to the upstream chart.
# Use for any upstream key not exposed as a first-class value above
# (backups.barmanObjectStore, cluster.resources, cluster.affinity,
# cluster.postgresql.parameters, poolers, recovery.*, replica.*, …).
extraValues: {}

# Sync policy applied to the rendered Application.
#
# ServerSideApply is NOT optional: the clusters.postgresql.cnpg.io CRD is far
# past the 262144-byte limit for the kubectl.kubernetes.io/last-applied-
# configuration annotation, so a client-side apply fails outright.
#
# The retry block is load-bearing, not boilerplate: sync waves only order
# resources WITHIN one Argo Application, so syncing this entry against a cluster
# whose operator isn't up yet fails the dry-run on the unknown
# postgresql.cnpg.io/v1 kind. The retry is what recovers it. Keep it.
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
retry:
limit: 5
backoff:
duration: 10s
factor: 2
maxDuration: 3m
7 changes: 7 additions & 0 deletions infra/cloudnative-pg/install/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
apiVersion: v2
name: cloudnative-pg-install
description: App-of-apps chart packaging the upstream CloudNativePG operator Helm chart (cnpg/cloudnative-pg)
type: application
version: 0.1.0
appVersion: "1.30.0"
43 changes: 43 additions & 0 deletions infra/cloudnative-pg/install/templates/chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{{- $destKey := .Values.destination.server | default .Values.destination.name }}
{{- $defaultName := printf "cloudnative-pg-%s" (sha1sum $destKey | trunc 8) }}
{{- $appName := .Values.applicationName | default $defaultName }}
{{/*
The operator chart needs almost nothing from us — only the knobs a cluster
actually varies get a first-class value. Everything else goes via extraValues.
*/}}
{{- $computed := dict
"replicaCount" (int .Values.replicaCount)
"monitoring" (dict "podMonitorEnabled" .Values.monitoring.podMonitorEnabled)
"resources" (.Values.resources | default dict)
}}
{{- $extra := .Values.extraValues | default dict }}
{{- $merged := mergeOverwrite $computed (deepCopy $extra) }}
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: {{ $appName | quote }}
namespace: argocd
annotations:
# -10 alongside cert-manager / crossplane / external-secrets / kyverno:
# the postgresql.cnpg.io CRDs must register before any Cluster CR is applied.
argocd.argoproj.io/sync-wave: "-10"
spec:
project: {{ .Values.project | quote }}
source:
repoURL: https://cloudnative-pg.github.io/charts
chart: cloudnative-pg
targetRevision: {{ .Values.chartVersion | quote }}
helm:
releaseName: cloudnative-pg
valuesObject:
{{- toYaml $merged | nindent 8 }}
destination:
{{- if .Values.destination.name }}
name: {{ .Values.destination.name | quote }}
{{- else }}
server: {{ .Values.destination.server | quote }}
{{- end }}
namespace: {{ .Values.destination.namespace | quote }}
syncPolicy:
{{- toYaml .Values.syncPolicy | nindent 4 }}
Loading