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/v1alpha1. - 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 —
webhookv1alpha1.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/v1alpha1if 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 stable reason; Warning Event; no hot-loop |
| Transient | *TransientError (ErrTransient) |
setSyncedError / QMC fail path returns RequeueAfter: 30s (workload) or connection equivalent |
| NotFound | *NotFoundError |
Ensure: create path; Delete: treat as already gone |
Also handled without MQ types: Kubernetes NotFound on connection or Secret
(mapped to Warning reasons ConnectionNotFound, SecretNotFound).
Principles: wrap errors with context; never panic in reconcile; let
controller-runtime rate-limit when returning a bare error from Reconcile.
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 |
| 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