Architecture
Three controllers, three CRDs, one Server-Side Apply engine. This page covers the system components, reconciliation flow, and key design decisions.
System Overview
Datasource support
MySQL: fully supported (v1.0+). PostgreSQL: planned for v1.2.
Components at a Glance
| Component | Purpose | Example |
|---|---|---|
| LynqHub | Reads database, creates LynqNode CRs | MySQL every 30s → 6 LynqNode CRs |
| LynqForm | Resource blueprint per row | Deployment + Service per active node |
| LynqNode | Instance for one row × one form | acme-corp-web-app → 5 K8s resources |
Naming: LynqNode CRs follow {uid}-{form-name}. A hub with 3 active rows (acme, beta, corp) and 2 forms (web-app, worker) creates 6 LynqNodes: acme-web-app, acme-worker, beta-web-app, beta-worker, corp-web-app, corp-worker.
Reconciliation Flow
Three-Controller Design
LynqHub Controller
Syncs the database on spec.source.syncInterval (default: 1m):
- Queries external datasource; filters rows where
activateis truthy - Calculates desired LynqNode set:
referencingForms × activeRows - Creates missing LynqNode CRs, updates existing ones, deletes excess
- Emits events:
LynqNodeDeleting,LynqNodeDeleted,LynqNodeDeletionFailed - Updates
status.{referencingTemplates, desired, ready, failed}
LynqForm Controller
Validates form-hub relationships:
- Verifies
spec.hubIdreferences an existing LynqHub - Ensures resource IDs are unique within the form
- Validates Go template syntax
- Detects dependency cycles in
dependIds
LynqNode Controller
The core reconciler. Runs on LynqNode create/update, resource changes, and 30s periodic requeue:
- Finalizer handling — run cleanup before deletion, then remove finalizer (
lynqnode.operator.lynq.sh/finalizer) - Template evaluation — render all resource specs with node data
- Orphan detection — compare
status.appliedResourceswith current desired set; handle each orphan per itsDeletionPolicy - Dependency resolution — build DAG from
dependIds; fail fast on cycles - Resource application — apply in topological order; skip if dependency failed (respects
skipOnDependencyFailure) - Readiness gate — wait for Ready condition when
waitForReady: true(default); timeout attimeoutSeconds(default: 300) - Status update — write
readyResources,failedResources,desiredResources,appliedResources
Key Design Patterns
Server-Side Apply (SSA)
All resources use SSA with fieldManager: lynq. This means:
- Operator owns only the fields it sets; other controllers can own other fields
- Conflict detection when another manager owns a field Lynq wants to change
ConflictPolicy: Forceoverrides withforce=trueflag- Drift auto-correction: SSA re-applies the desired spec on every reconcile
Resource Tracking
Two mechanisms, chosen automatically based on namespace and deletion policy:
| Scenario | Mechanism | Why |
|---|---|---|
Same-namespace, DeletionPolicy: Delete | OwnerReference | Kubernetes GC handles cleanup automatically |
Cross-namespace, Namespace resources, DeletionPolicy: Retain | Labels lynq.sh/node + lynq.sh/node-namespace | OwnerReferences can't cross namespaces; Retain requires manual lifecycle |
Dependency Management
deployments:
- id: app
# ...
services:
- id: svc
dependIds: ["app"] # applies only after app is ready
waitForReady: trueLynq builds a DAG, detects cycles (fails fast), performs topological sort, and applies in order. Blocked dependencies (not-yet-ready) wait silently; failed dependencies trigger skipOnDependencyFailure logic.
Orphan Resource Management
When a resource is removed from a LynqForm template, Lynq detects it by comparing status.appliedResources (previous state) against the current desired set. The stored DeletionPolicy annotation determines what happens next:
DeletionPolicy: Delete— resource is removed from the clusterDeletionPolicy: Retain— resource receives orphan markers and stays:- Label:
lynq.sh/orphaned: "true" - Annotation:
lynq.sh/orphaned-at: "<RFC3339>" - Annotation:
lynq.sh/orphaned-reason: "RemovedFromTemplate"
- Label:
Re-adding a resource to the template removes all orphan markers and re-adopts it.
# Find all orphaned resources cluster-wide
kubectl get all -A -l lynq.sh/orphaned=truePerformance
Controller Concurrency
--hub-concurrency=3 # default
--form-concurrency=5 # default
--node-concurrency=10 # defaultWatch Predicates
Lynq watches 12 resource types via Owns() (same-namespace) and Watches() (cross-namespace, label-based). Predicates filter out status-only updates — only generation or annotation changes trigger reconciliation.
Requeue Strategy
The 30-second periodic requeue (RequeueAfter: 30s) ensures child resource status changes (e.g., a Deployment becoming Ready) are reflected in the LynqNode status quickly, without relying solely on watch events.
See Also
- Introduction — what Lynq is and when to use it
- API Reference — full CRD schemas for LynqHub, LynqForm, LynqNode
- Policies — creation, deletion, and conflict policy reference
- Dependencies — dependency graph, ordering, and failure handling
