Monedula
monedula-gitops docs menu

Configure the Kubernetes operator

Install and configure the monedula-gitops operator with Helm: admission webhooks, reconciliation, credentials from Secrets and multi-tenancy.

How-to guides 18 of 18

6 min read

Updated 9 October 2026

Markdown

On this page

The chart’s defaults work for a first run. These are the values to review before production; the chart’s values.yaml lists every value with its default.

Apply your values

Put the values you change in a file, values.yaml on this page. This one turns on the admission webhooks:

webhook:
  enabled: true

Install or upgrade the release with the file, and run the same command after every change:

helm upgrade --install monedula-gitops oci://ghcr.io/monedula-dev/charts/monedula-gitops \
  --namespace monedula-system --create-namespace \
  -f values.yaml

The chart renders the custom resource definitions from its templates (crds.enabled, default true), so an upgrade keeps them current, and crds.keep (default true) leaves them and your custom resources in place on helm uninstall.

Admission webhooks

The webhooks are off by default. These are the chart’s defaults for them:

webhook:
  enabled: false       # opt-in; cert-free by default (matches --enable-webhooks default)
  failurePolicy: Fail
  certManager:
    enabled: true      # cert-manager issues + injects the serving cert (prerequisite)
  certSecretName: ""   # defaults to <fullname>-webhook-serving-cert
  caBundle: ""         # BYO mode (certManager.enabled=false): base64 CA bundle

With webhook.enabled: true, the operator validates KafkaTopic, KafkaQuota, KafkaAccessPolicy, KafkaRoleBinding and KafkaUser resources when they are created or updated. It rejects duplicate identities, changes to identity fields, invalid shapes, tenancy violations of topics and conflicting Allow and Deny ACL rules before Kubernetes stores the resource. It never blocks a deletion. Without the webhooks, the custom resource definitions still keep identity fields immutable, and the operator still detects duplicates and tenancy violations, but only after the resource is created.

  • failurePolicy: Fail means that Kubernetes rejects creates and updates of these kinds while no operator pod can answer. The operator pod reports ready only once its webhook server runs, so a rollout does not reject requests.
  • With certManager.enabled: true, cert-manager must run in the cluster: the chart has it issue the serving certificate and inject its CA into the webhook configuration.
  • To bring your own certificate, set certManager.enabled: false, create a TLS Secret with the serving certificate and key, and set caBundle to the base64-encoded CA bundle; the chart refuses to render without it. For the release on this page, the Secret is named monedula-gitops-webhook-serving-cert unless you set certSecretName, and the certificate must be valid for monedula-gitops-webhook.monedula-system.svc.

Reconciliation

  • resyncInterval (empty, so the operator default 5m applies; minimum 30s): how often every resource is re-checked without a change in Git. It bounds how long an out-of-band broker change goes undetected and uncorrected, and how long a resource that lost a duplicate-identity contest waits after the winner is deleted. Each reconcile reads live state from Kafka, so at hundreds of resources this is the main load lever.
  • maxConcurrentReconciles (empty, so 1): how many reconciles of one kind run in parallel. Values above 1 need leaderElection.enabled: true; otherwise the chart refuses to render and the operator refuses to start, with exit code 2. Writers on the same cluster’s ACLs, and on the same cluster’s role bindings, still run one at a time, so a higher value helps installs with several Kafka clusters or many quotas and users, and not hundreds of topics on one cluster.
  • leaderElection.enabled (default true): only one replica is active at a time. With replicaCount above its default of 1, the extra replicas wait as standbys.

A KafkaCluster that is created after the resources that reference it, or whose spec changes, re-reconciles those resources at once, without waiting for the resync.

Resolve every KafkaCluster from one namespace

By default the operator looks up the KafkaCluster that a resource references in the resource’s own namespace:

clusterNamespace: ""   # --cluster-namespace; empty = resolve KafkaCluster in the resource's own namespace

For several teams on shared clusters, set clusterNamespace to a namespace that only the cluster owners can write to, and keep every KafkaCluster there with the Secrets it references, which the operator reads from the cluster’s own namespace. Every clusterRef then resolves to these central objects, and a KafkaCluster that a team creates in its own namespace is never used. A tenancy policy on each KafkaCluster limits which namespaces may use it, as Enforce tenancy with admission webhooks shows.

Represent each Kafka cluster by exactly one KafkaCluster object. Several KafkaCluster objects for the same brokers each get their own view of the cluster’s ACLs and role bindings, and with pruning on they delete each other’s grants.

Read credentials from Kubernetes Secrets

Every credential in an operator KafkaCluster is a valueFrom.secretKeyRef (Connect to a secured Kafka cluster shows one). Label each credential Secret with gitops.monedula.dev/credential-source: "true":

apiVersion: v1
kind: Secret
metadata:
  name: kafka-creds
  labels:
    gitops.monedula.dev/credential-source: "true"
type: Opaque
stringData:
  username: svc-monedula
  password: change-me

When a labeled Secret changes, the operator reconciles the KafkaCluster that references it, and the topics, policies, quotas and role bindings on that cluster, at once. Without the label, a rotated credential takes effect at the next resync, and the KafkaCluster reports the condition CredentialSourceUnwatched with the status True. The same label makes a KafkaUser pick up a changed password Secret at once. The values above are placeholders; create the real Secret from your secrets manager.

For workload identity, such as IRSA on EKS, GKE Workload Identity or Azure Workload Identity, add its annotations to the operator’s service account with serviceAccount.annotations. The chart creates the service account (serviceAccount.create: true), named after the chart’s full name, monedula-gitops for the release on this page, unless you set serviceAccount.name.

Expose metrics

The operator serves Prometheus metrics through a Service (metrics.service.enabled: true, metrics.service.port: 8080):

Metric Labels Meaning
monedula_reconcile_total controller, result (success or error) Reconcile runs.
monedula_reconcile_errors_total controller Reconcile runs that returned an error.
monedula_reconcile_duration_seconds controller Reconcile duration, as a histogram.
monedula_kafka_cluster_reachable namespace, name 1 while the KafkaCluster is reachable, else 0.
monedula_kafka_topic_drift_detected, monedula_access_policy_drift_detected, monedula_kafka_quota_drift_detected, monedula_kafka_rolebinding_drift_detected, monedula_kafka_user_drift_detected namespace, name 1 while the resource has drift, else 0. A KafkaUser drifts on its username, mechanism or iteration count, never its password.
monedula_managed_topics, monedula_managed_quotas, monedula_managed_rolebindings, monedula_managed_users none The number of resources of that kind the operator manages.
monedula_reconcile_terminal_total kind, reason Outcomes that wait for a person or a spec change, such as TenancyDenied, DuplicateIdentity or MDSNotConfigured. It has no per-resource labels, so it suits alerting.
  • metrics.serviceMonitor.enabled: true creates a Prometheus Operator ServiceMonitor. metrics.serviceMonitor.interval sets the scrape interval, and relabelings and metricRelabelings are passed through to it.
  • metrics.secure: true serves the metrics over HTTPS and requires a bearer token that is authorized for the non-resource URL /metrics. Set metrics.serviceMonitor.bearerTokenSecret and metrics.serviceMonitor.tlsConfig to match. Left empty, metrics.serviceMonitor.scheme becomes https when metrics.secure is on.

The operator pod

The pod runs as non-root with a read-only root file system, the RuntimeDefault seccomp profile and all capabilities dropped. It requests 50m CPU and 64Mi memory, with limits of 500m and 256Mi, and logs at warn (logLevel: error, warn, info or debug). Placement, image and pass-through values (extraArgs, extraEnv, extraVolumes, extraVolumeMounts) are listed in values.yaml; each extraVolumeMounts entry needs a matching extraVolumes entry, or the chart refuses to render. image.tag defaults to the chart’s app version.

The liveness probe calls /healthz and the readiness probe /readyz on port 8081, where the operator serves both (--health-probe-bind-address, default :8081). The chart fixes that port and has no value for it, so leave --health-probe-bind-address out of extraArgs, or the probes fail.

Check it worked

After an install or upgrade, Helm prints the chart’s notes, which say whether the admission webhook is enabled. Wait for the rollout:

kubectl -n monedula-system rollout status deploy/monedula-gitops

If the rollout does not finish, read the operator logs:

kubectl -n monedula-system logs deploy/monedula-gitops

With cert-manager, a pod that stays in ContainerCreating usually waits for its serving certificate: check that cert-manager is healthy.