---
title: "Configure the Kubernetes operator"
description: "Install and configure the monedula-gitops operator with Helm: admission webhooks, reconciliation, credentials from Secrets and multi-tenancy."
url: https://monedula.dev/flock/docs/gitops/how-to/configure-the-operator/
---

The chart's defaults work for a first run. These are the values to review before production; the chart's [values.yaml](https://github.com/monedula-dev/monedula-gitops/blob/main/charts/monedula-gitops/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:

```yaml
webhook:
  enabled: true
```

Install or upgrade the release with the file, and run the same command after every change:

```bash
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:

```yaml
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](https://cert-manager.io/) 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:

```yaml
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](/flock/docs/gitops/how-to/enforce-tenancy/) 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](/flock/docs/gitops/how-to/connect-to-a-secured-cluster/) shows one). Label each credential Secret with `gitops.monedula.dev/credential-source: "true"`:

```yaml
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:

```bash
kubectl -n monedula-system rollout status deploy/monedula-gitops
```

If the rollout does not finish, read the operator logs:

```bash
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.

## Related

- The [operator command reference](/flock/docs/gitops/reference/cli/operator/) lists the flags that these values set.