Skip to content
GitHub stars

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

ComponentPurposeExample
LynqHubReads database, creates LynqNode CRsMySQL every 30s → 6 LynqNode CRs
LynqFormResource blueprint per rowDeployment + Service per active node
LynqNodeInstance for one row × one formacme-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):

  1. Queries external datasource; filters rows where activate is truthy
  2. Calculates desired LynqNode set: referencingForms × activeRows
  3. Creates missing LynqNode CRs, updates existing ones, deletes excess
  4. Emits events: LynqNodeDeleting, LynqNodeDeleted, LynqNodeDeletionFailed
  5. Updates status.{referencingTemplates, desired, ready, failed}

LynqForm Controller

Validates form-hub relationships:

  • Verifies spec.hubId references 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:

  1. Finalizer handling — run cleanup before deletion, then remove finalizer (lynqnode.operator.lynq.sh/finalizer)
  2. Template evaluation — render all resource specs with node data
  3. Orphan detection — compare status.appliedResources with current desired set; handle each orphan per its DeletionPolicy
  4. Dependency resolution — build DAG from dependIds; fail fast on cycles
  5. Resource application — apply in topological order; skip if dependency failed (respects skipOnDependencyFailure)
  6. Readiness gate — wait for Ready condition when waitForReady: true (default); timeout at timeoutSeconds (default: 300)
  7. 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: Force overrides with force=true flag
  • Drift auto-correction: SSA re-applies the desired spec on every reconcile

Resource Tracking

Two mechanisms, chosen automatically based on namespace and deletion policy:

ScenarioMechanismWhy
Same-namespace, DeletionPolicy: DeleteOwnerReferenceKubernetes GC handles cleanup automatically
Cross-namespace, Namespace resources, DeletionPolicy: RetainLabels lynq.sh/node + lynq.sh/node-namespaceOwnerReferences can't cross namespaces; Retain requires manual lifecycle

Dependency Management

yaml
deployments:
  - id: app
    # ...
services:
  - id: svc
    dependIds: ["app"]   # applies only after app is ready
    waitForReady: true

Lynq 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 cluster
  • DeletionPolicy: Retain — resource receives orphan markers and stays:
    • Label: lynq.sh/orphaned: "true"
    • Annotation: lynq.sh/orphaned-at: "<RFC3339>"
    • Annotation: lynq.sh/orphaned-reason: "RemovedFromTemplate"

Re-adding a resource to the template removes all orphan markers and re-adopts it.

bash
# Find all orphaned resources cluster-wide
kubectl get all -A -l lynq.sh/orphaned=true

Performance

Controller Concurrency

bash
--hub-concurrency=3    # default
--form-concurrency=5   # default
--node-concurrency=10  # default

Watch 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