Operator runtime¶
How the MKurator controller-manager process starts, registers controllers and webhooks, talks to IBM MQ through a cached client factory, and reconciles each custom resource kind. Module layout is in GO_MODULE.md; CR examples and security are in ARCHITECTURE.md.
Process bootstrap (cmd/main.go)¶
Startup order:
- Flags and env — metrics/probe addresses, leader election, TLS cert paths
for webhook and metrics, logging (
--log-config,KURATOR_LOG_*), andMaxConcurrentReconciles(--max-concurrent-reconcilesorKURATOR_MAX_CONCURRENT_RECONCILES, minimum 1). - Logging —
internal/loggingconfigures structured output (JSON in cluster). - Scheme — core Kubernetes types plus
messaging.mkurator.dev/v1beta1. - Manager — controller-runtime
Managerwith metrics server (HTTPS + authz filter by default), webhook server, health probes, optional leader election (LeaderElectionID:bdd44880.mkurator.dev). - MQ factory —
mqrest.NewClientFactory(mgr.GetClient())implementsmqadmin.Factory. - Reconcilers — six controllers, shared
events.EventRecordernamemkurator-controller-manager. - Webhooks —
webhookv1beta1.SetupWithManager(mgr)(validating only). - Probes —
healthzping;readyzviahealth.NewMQConnectivityChecker. - Run —
mgr.Starton SIGTERM/SIGINT.
Nothing in main performs MQ work directly; it only wires dependencies.
Reconciler registration¶
| Controller | Primary object | MQ via Admin |
|---|---|---|
QueueManagerConnectionReconciler |
QueueManagerConnection |
Ping |
QueueReconciler |
Queue |
Get/Define/DeleteQueue |
TopicReconciler |
Topic |
Get/Define/DeleteTopic |
ChannelReconciler |
Channel |
Get/Define/DeleteChannel |
ChannelAuthRuleReconciler |
ChannelAuthRule |
Get/Set/DeleteChannelAuth |
AuthorityRecordReconciler |
AuthorityRecord |
Get/Set/DeleteAuthority |
All workload reconcilers share setupMQObjectController in
internal/controller/reconcile_shared.go: they watch
QueueManagerConnection and enqueue dependent CRs when connection readiness or
generation changes.
Connection client cache¶
mqrest.ClientFactory (internal/adapter/mqrest/factory.go) implements
mqadmin.Factory:
ForConnection— builds or returns a cachedmqadmin.Admin(HTTPSmqrest.Client) for aQueueManagerConnection.- Cache key — namespace/name, connection generation, credentials Secret resourceVersion, and optional CA Secret resourceVersion. Rotating Secrets or changing the connection spec invalidates the cache entry.
ReleaseConnection— removes the cache entry when a connection CR is deleted (called from the QMC reconciler during finalizer removal).
The factory resolves credentialsSecretRef and optional caSecretRef, builds
TLS config (insecureSkipVerify opt-in), and parses spec.endpoint and
spec.queueManager. Credentials never appear in CR specs — only Secret refs.
Workload reconcilers obtain admin only after the connection is Ready (see
below).
Reconcile flow (workload CRs)¶
Queue, Topic, Channel, ChannelAuthRule, and AuthorityRecord follow the
same skeleton (implemented per kind with shared helpers):
flowchart TD
start["Reconcile requested"]
get["Get CR"]
conn["Resolve QueueManagerConnection"]
wait{"Connection Ready?"}
prog["Status: Synced=False Progressing\nRequeueAfter 15s"]
admin["MQFactory.ForConnection"]
del{"DeletionTimestamp set?"}
fin{"Finalizer present?"}
addFin["Add finalizer, return"]
ensure["Ensure on IBM MQ\n(display / define or set auth)"]
sync["Status: Synced=True Available"]
delMQ["Delete on MQ, remove finalizer"]
start --> get --> conn --> wait
wait -->|no| prog
wait -->|yes| admin --> del
del -->|yes| delMQ
del -->|no| fin
fin -->|no| addFin
fin -->|yes| ensure --> sync
Steps in code (internal/controller/*_controller.go + reconcile_shared.go):
- Get the CR; ignore
NotFound. connectionRefName/resolveConnection— load the namedQueueManagerConnectionin the same namespace.waitForConnectionReady— ifReadyis not True, patchSynced=Falsewith reasonProgressing, emit a Normal event on transition, returnRequeueAfter: 15s.MQFactory.ForConnection— terminal errors (missing Secret, bad endpoint) go throughsetSyncedError.- Deletion — if
deletionTimestampis set, run kind-specifichandleDeletion(MQ delete, then remove finalizer). - Finalizer — add the kind finalizer constant from
api/v1beta1if missing; return and reconcile again. - Ensure — populate
status.desiredMQSCviamqrest.Format*MQSC; call port methods to observe and converge MQ state. Drift behaviour for queues/topics/channels is in ATTRIBUTE_RECONCILIATION.md; auth kinds use GET/replace semantics documented there. - Success —
patchSyncedAvailablesetsSynced=True,observedGeneration, optionallastSyncedTime.
Observe-only: annotation messaging.mkurator.dev/drift-policy=observe-only
reports drift without applying REPLACE/SET (see attribute doc).
QueueManagerConnection reconcile¶
The connection reconciler does not use the shared workload skeleton:
- On delete —
ReleaseConnection, removeQueueManagerConnectionFinalizer. - On live object — add finalizer if needed.
- Set
Ready=False, reasonProgressing, status update. ForConnection+Ping.- On success —
Ready=True, reasonAvailable,observedGeneration; Normal event on transition to Available. - On failure — classified error on
Ready; transient failures useRequeueAfter: 30s(seequeuemanagerconnection_controller.go).
Readiness probe (internal/health): if there are zero QMCs, the operator is ready;
otherwise at least one connection must have Ready=True.
Connection watch and fan-out¶
When a QueueManagerConnection becomes ready (or its spec generation changes),
connectionEnqueueMapper lists all workload CRs in the namespace that reference
that connection and enqueues reconcile requests for each. That avoids workloads
stuck behind a long RequeueAfter while only the QMC reconciler was running.
Predicates on the watch ignore updates that do not change readiness or generation.
Finalizers¶
| Kind | Finalizer constant (API package) |
|---|---|
QueueManagerConnection |
QueueManagerConnectionFinalizer |
Queue |
QueueFinalizer |
Topic |
TopicFinalizer |
Channel |
ChannelFinalizer |
ChannelAuthRule |
ChannelAuthRuleFinalizer |
AuthorityRecord |
AuthorityRecordFinalizer |
Finalizers ensure MQ objects are removed (or connection clients released) before
the CR disappears from etcd. Deletion sets Synced=False, reason Deleting, with
a Normal event on transition.
Validating webhooks vs reconcile¶
| Concern | Webhooks (internal/webhook, internal/validation) |
Reconcilers |
|---|---|---|
| When | Admission (create/update/delete) | Async loop after persist |
| MQ access | Never | Via mqadmin.Admin |
| Typical checks | Required fields, names, same-namespace connectionRef, Secret refs exist for QMC, deny QMC delete if dependents exist |
Connectivity, MQSC, drift, delete on QM |
| Failure | Request rejected (failurePolicy: Fail) |
Status conditions + optional Events |
Webhooks keep invalid specs out of etcd; reconcilers converge valid specs against a live Queue Manager. Do not duplicate MQ calls in webhooks.
Status conditions and Kubernetes Events¶
Conditions
QueueManagerConnection:Ready(connectivity).- Workload CRs:
Synced(object matches spec on MQ).
Events (ADR-0015) — emitted on
transitions only, implemented in internal/controller/events.go:
| Transition | Type | Typical reason |
|---|---|---|
| Connection / object healthy | Normal | Available |
| Waiting on connection | Normal | Progressing |
| Deleting | Normal | Deleting |
| MQ object removed | Normal | Deleted |
| Terminal failure | Warning | TerminalError.Reason, ConnectionNotFound, … |
| Transient MQ/network | (none) | Status updated; requeue only |
recordReconcileWarning skips Events for ErrTransient to avoid noise during
retries.
Error handling and requeue (ADR-0014)¶
Errors are classified in mqrest and returned as port types; reconcilers use
errors.Is / errors.As only.
| Class | Port signal | Reconciler behaviour |
|---|---|---|
| Terminal | *TerminalError (ErrTerminal) |
Synced or Ready False with a stable reason distinguishable from retryable failures (default TerminalError, or a specific reason like UnsupportedChannelType); Warning Event; no requeue for workloads (a spec edit re-enqueues), QMC uses the 2m backstop |
| Transient | *TransientError (ErrTransient) |
setSyncedError / QMC fail path returns RequeueAfter: 30s (workload) or connection equivalent; context deadline expiry/cancellation in the mqweb adapter is classified transient |
| NotFound | *NotFoundError |
Ensure: create path; Delete: treat as already gone |
| Unclassified | anything else (incl. missing connection/Secret) | Workload reconcilers: error returned to controller-runtime → rate-limited backoff requeue + ERROR log; never (Result{}, nil) (REQ-REL-2026-08). QMC: fail() treats any non-transient error the same way — Ready=False + Warning Event + RequeueAfter: TerminalRetryInterval() with a nil error, so no ERROR line |
Also handled without MQ types: Kubernetes NotFound on connection or Secret
(mapped to Warning reasons ConnectionNotFound, SecretNotFound). A workload CR
whose connection is missing retries via the unclassified path above, with the
QMC-ready watch as the fast path; a QMC whose Secret is missing retries on the
2m backstop and reports through its condition and Event rather than the log —
see Expected ERROR logs during bootstrap ordering.
Expected ERROR logs during bootstrap ordering¶
Applying a directory in one shot — the normal GitOps case — routinely creates objects before the things they reference. The two orderings surface differently, so only one of them produces ERROR logs.
Workload CR before its QueueManagerConnection (Queue, Topic, Channel, …).
Since REQ-REL-2026-08 this reconcile returns the error rather than waiting
silently on the watch, so until the QMC appears the manager emits rate-limited
Reconciler error lines at ERROR:
ERROR Reconciler error {"controller": "queue", "error": "get connection \"qm1\": queuemanagerconnections.messaging.mkurator.dev \"qm1\" not found"}
QMC before its credentials Secret. This one logs no ERROR line. A missing
Secret surfaces as SecretNotFoundError, which is neither transient nor
terminal, so the QMC reconciler's fail() path sets Ready=False, emits a
Warning Event (SecretNotFound), and returns a RequeueAfter with a nil
error — controller-runtime has nothing to log. Watch this one through the
condition and the Event, not the log stream.
Both are the deliberate retry backstop, not an incident, and both clear on
their own once the referenced object is created (the watch re-enqueues within
seconds; the requeue is the fallback). Treat them as actionable only if they
persist after the QMC and its Secret exist and the QMC reports Ready=True —
that points at a genuine reference typo or a cross-namespace mistake rather than
ordering. Alerting on this controller's ERROR rate should therefore use a
tolerance window wider than a single bootstrap, not a bare > 0 threshold.
Principles: wrap errors with context; never panic in reconcile; let
controller-runtime rate-limit when returning a bare error from Reconcile; a
workload reconcile that fails must always schedule a retry unless the failure is
terminal — every Synced=False transition that does not return an error logs the
error and the scheduled retry interval.
Concurrency and metrics¶
MaxConcurrentReconciles— global default applied to every controller viacontrollerOptions()(internal/controller/options.go).- Metrics — each reconciler calls
metrics.RecordReconcile(controllerName, err)after its inner reconcile.
Operational flags (summary)¶
| Flag / env | Effect |
|---|---|
--leader-elect |
Single active manager replica |
--metrics-bind-address |
Prometheus (often :8443 in deployment) |
--health-probe-bind-address |
:8081 for healthz / readyz |
--max-concurrent-reconciles / KURATOR_MAX_CONCURRENT_RECONCILES |
Worker pool size per controller |
--drift-resync-min / --drift-resync-max |
Jitter window for the periodic re-check of successfully synced workload CRs (defaults 5m / 10m) |
--connection-wait-interval |
Requeue delay while a workload CR waits for its QueueManagerConnection to become Ready (default 15s) |
--transient-requeue-interval |
Requeue delay after a transient MQ or connection error (default 30s) |
--terminal-retry-interval |
Backstop requeue for QMC terminal (non-transient) errors — not only auth (default 2m; Secret watches remain the fast path). See ADR-0014 |
--mq-request-timeout |
Per-request deadline for mqweb Admin calls from reconcilers (default 30s) |
| Webhook / metrics cert paths | TLS for admission and metrics servers |
Full logging options: LOGGING.md. NFR summary: NON_FUNCTIONAL_REQUIREMENTS.md.
See also¶
- GO_MODULE.md — packages, generated artifacts, test tiers
- ARCHITECTURE.md — component diagram, security, local kind topology
- ATTRIBUTE_RECONCILIATION.md — DEFINE vs DISPLAY drift
- IBM_MQ_REST_API.md — REST paths and CSRF header