kadet-hub logokadet-hub

CloudNativePG on Kubernetes without Helm, with Kapitan

The CloudNativePG operator 1.30.1 with its webhooks and RBAC, and optionally its CRDs, rendered by the kadet-hub kubernetes generator 1.9.0 from the project’s Helm chart cloudnative-pg 0.29.1.

What it deploys

Required parameters

No parameter has to be set.

Include

A target includes the recipe and its options as classes and can override anything the recipe sets:

classes:
  - recipes.cnpg
  - recipes.cnpg.crds   # the chart's crds.create
parameters:
  namespace: cnpg-system
  kapitan:
    vars:
      target: cnpg

Fetching it

A separate fetch inventory pins the recipe by version and digest. This is fetch/targets/recipes.yml for version 1.0.1:

parameters:
  kapitan:
    dependencies:
      - type: oci
        source: ghcr.io/kadet-hub/recipes/cnpg:1.0.1@sha256:732fa7a16e702f0accddd8dea3456ca0f8a4c67efe225810f9497af090b1419a
        output_path: inventory/classes/recipes/cnpg

The first command fetches the recipe into inventory/classes/recipes/cnpg, the second the generators and compiles:

kapitan compile --inventory-path fetch --fetch --output-path .
kapitan compile --fetch

The first command exits with status 1 because the fetch target compiles nothing. Kapitan's --fetch checks neither the digest nor the attestation. This verifies the attestation of each recipe the fetch inventory pins:

gh attestation verify \
  oci://ghcr.io/kadet-hub/recipes/cnpg@sha256:732fa7a16e702f0accddd8dea3456ca0f8a4c67efe225810f9497af090b1419a \
  --owner kadet-hub \
  --cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main

The attested index must also list each digest under its recipe with the pinned version, and not as yanked. Verifying by hand shows how to read it.

The output rules found admission-webhook, host-path in the compiled output of the recipe's test target. The consumer workflow fails unless the policy .kapitan-sandbox.yaml accepts these capabilities for the target's output path:

output_capabilities:
  "compiled/<target>/*": [admission-webhook, host-path]

Settings

Chart value Inventory
namespace (release namespace) namespace
image.tag components.cnpg-cloudnative-pg.image, tag and digest
replicaCount components.cnpg-cloudnative-pg.replicas
config.maxConcurrentReconciles components.cnpg-cloudnative-pg.args.max-concurrent-reconciles
additionalArgs components.cnpg-cloudnative-pg.args.<key>
additionalEnv components.cnpg-cloudnative-pg.env.<name>
config.data generators.kubernetes.configs.cnpg-controller-manager-config.data
monitoringQueriesConfigMap.queries generators.kubernetes.configs.cnpg-default-monitoring.data.queries.value
resources, nodeSelector, tolerations, affinity components.cnpg-cloudnative-pg.resources, .node_selector, .tolerations, .affinity

The operator writes its CA into the two webhook configurations, which it finds by the names cnpg-mutating-webhook-configuration and cnpg-validating-webhook-configuration, and it finds its own Deployment by the label app.kubernetes.io/name: cloudnative-pg. Do not change these. The chart passes the operator image by tag in OPERATOR_IMAGE_NAME, and the operator gives it to the bootstrap container of every instance pod.

The pod annotations checksum/rbac, checksum/config and checksum/monitoring-config are the chart’s hashes for tests/values.yaml. They do not change with the recipe, so restart the operator with kubectl rollout restart deployment/cnpg-cloudnative-pg after changing its ConfigMaps. The monitoring queries are the chart’s, Apache-2.0, with the license in operator.yml.LICENSE.

Proof

check cnpg --level deploys with tests/values.yaml and tests/target.yml, on kind kindest/node:v1.37.0. The objects differ in their selector scheme, the explicit automountServiceAccountToken: true of the pod and the ServiceAccount, and the container name, which the generator derives from the component. The permissions of the operator’s ServiceAccount are equal.

tests/smoke creates a one-instance Cluster in another namespace. The webhooks admit it, the mutating webhook fills in its defaults, and it becomes ready.

Versions

The recipe's files are in kadet-hub/recipes. All recipes