Skip to content
GitHub stars

Resource Lifecycle

How Lynq tracks, cleans up, and re-adopts resources — covering labels, annotations, finalizers, cross-namespace support, and reconciliation triggers.

Related


Tracking Labels and Annotations

Labels Set on LynqNode CRs

The hub controller sets these labels when creating LynqNode CRs:

LabelValuePurpose
lynq.sh/hubHub nameWhich LynqHub owns this node
lynq.sh/uidRow UIDWhich database row this node represents

Labels Set on Managed Resources

The operator sets these on resources it creates, depending on the tracking mechanism used:

LabelValueWhen set
lynq.sh/nodeLynqNode nameCross-namespace resources; Retain resources; Namespaces
lynq.sh/node-namespaceLynqNode namespaceSame — paired with lynq.sh/node

Same-namespace resources with deletionPolicy: Delete (the default) use Kubernetes ownerReferences instead of these labels. The garbage collector then handles cleanup automatically.

Annotations Set on Managed Resources

AnnotationValuePurpose
lynq.sh/deletion-policy"Delete" or "Retain"Stored at creation time; used during orphan cleanup
lynq.sh/created-once"true"Set when creationPolicy: Once resource is created; prevents re-application

Annotations Set on LynqNode CRs

The LynqHub controller stamps these annotations on every LynqNode CR it creates. The LynqNode controller reads them when rendering templates.

AnnotationValuePurpose
lynq.sh/hubIdLynqHub nameSource hub for this node
lynq.sh/activate"true"/"false"Activation column value (string form)
lynq.sh/hostOrUrlURL/host columnDeprecated .hostOrUrl template variable (removed in v1.3.0)
lynq.sh/extraJSON-encoded objectAll extraValueMappings keys/values packed into a single annotation
lynq.sh/template-generationint (from LynqForm.metadata.generation)Used by rollout tracking to detect template changes
lynq.sh/rollout-update-startRFC3339 timestampStamped each time the hub re-renders the node spec

(The .uid template variable comes from the LynqNode's lynq.sh/uid label, not an annotation.)


Orphan Markers

When a resource is removed from a LynqForm but its deletionPolicy is Retain, the operator:

  1. Removes tracking labels (lynq.sh/node, lynq.sh/node-namespace)
  2. Adds orphan markers
FieldKeyValue
Labellynq.sh/orphaned"true" — for label selector queries
Annotationlynq.sh/orphaned-atRFC3339 timestamp
Annotationlynq.sh/orphaned-reason"RemovedFromTemplate" or "LynqNodeDeleted"

Why split label/annotation? Kubernetes label values must be short and RFC 1123 compliant, so the label carries only "true" for selector use. Annotations hold the richer metadata (timestamp, reason).

Finding Orphaned Resources

bash
# All orphaned resources across all namespaces
kubectl get all -A -l lynq.sh/orphaned=true

# Filter by reason
kubectl get all -A -l lynq.sh/orphaned=true \
  -o jsonpath='{range .items[?(@.metadata.annotations.lynq\.sh/orphaned-reason=="RemovedFromTemplate")]}{.kind}/{.metadata.name}{"\n"}{end}'

# Orphaned resources from a specific node
kubectl get all -A -l lynq.sh/orphaned=true,lynq.sh/node=my-node

Orphan Marker Lifecycle

  1. Resource removed from LynqForm → orphan markers added
  2. Resource re-added to LynqForm → orphan markers automatically removed on next reconcile
  3. LynqNode deleted (with deletionPolicy: Retain) → orphan markers added with reason LynqNodeDeleted

Before (orphaned):

yaml
metadata:
  labels:
    lynq.sh/orphaned: "true"
  annotations:
    lynq.sh/orphaned-at: "2025-01-15T10:30:00Z"
    lynq.sh/orphaned-reason: "RemovedFromTemplate"
    lynq.sh/deletion-policy: "Retain"
  # No ownerReferences — never set for Retain resources

After re-adoption:

yaml
metadata:
  labels:
    lynq.sh/node: "acme-customer-web-app"
    lynq.sh/node-namespace: "lynq-system"
    # lynq.sh/orphaned removed
  annotations:
    lynq.sh/deletion-policy: "Retain"
    # lynq.sh/orphaned-at removed
    # lynq.sh/orphaned-reason removed
bash
# Verify re-adoption
kubectl get deployment acme-worker \
  -o jsonpath='{.metadata.labels.lynq\.sh/orphaned}'
# (empty output = re-adopted)

Finalizers

Lynq uses finalizers to ensure proper resource cleanup before Kubernetes deletes objects.

ResourceFinalizer
LynqNodelynqnode.operator.lynq.sh/finalizer
LynqHublynq.sh/hub-finalizer

LynqNode finalizer flow:

  1. User deletes LynqNode CR (or operator marks it for deletion)
  2. Kubernetes sets deletionTimestamp but does NOT delete yet (finalizer present)
  3. LynqNode controller runs cleanupLynqNodeResources():
    • For each managed resource, checks lynq.sh/deletion-policy annotation
    • Delete: removes resource from cluster
    • Retain: removes tracking labels, adds orphan markers, leaves resource in place
  4. Controller removes the finalizer
  5. Kubernetes garbage collects the LynqNode CR

LynqHub finalizer flow:

  1. Hub is deleted → finalizer prevents immediate GC
  2. Hub controller deletes all LynqNode CRs owned by this hub
  3. Each LynqNode runs its own finalizer cleanup (above)
  4. Hub finalizer is removed

Cross-Namespace Resources

Normally, resources are created in the same namespace as their LynqNode CR. Setting targetNamespace on a TResource places it in a different namespace.

yaml
# In LynqForm
deployments:
  - id: app
    targetNamespace: "{{ .uid }}-ns"   # different namespace
    nameTemplate: "{{ .uid }}-app"
    spec:
      # ...

Tracking difference: Kubernetes ownerReferences only work within a single namespace. For cross-namespace resources (and for Namespace resources, which are cluster-scoped), the operator uses label-based tracking (lynq.sh/node + lynq.sh/node-namespace) instead.

Reconciliation trigger: The operator watches labeled resources across all namespaces. Any change to a resource bearing lynq.sh/node triggers reconciliation of the owning LynqNode.

Deletion: On LynqNode deletion, the operator queries for all resources with lynq.sh/node=<name> across all namespaces and deletes or retains them per their lynq.sh/deletion-policy annotation.


Watch Predicates

The LynqNode controller uses two watch mechanisms:

MechanismScopeTrigger condition
Owns()Same namespaceAny generation or annotation change
Watches() (label selector)All namespacesGeneration or annotation change on labeled resources

Smart filtering: Both watches use a predicate that fires on (a) metadata.generation changes, (b) annotation changes outside the lynq.sh/* namespace, and (c) targeted status fields needed for real-time readiness propagation — availableReplicas / readyReplicas / conditions on Deployments, StatefulSets, DaemonSets, Jobs, the load-balancer status on Ingresses, and currentReplicas / desiredReplicas on HPAs. Updates that don't touch any of those (pure resourceVersion bumps, internal lynq.sh/* annotation rewrites, status fields on resource types not enumerated above) are filtered.

Requeue interval: In addition to event-driven reconciliation, LynqNodes are requeued every 30 seconds to pick up status changes in child resources (e.g., a Deployment becoming ready).


appliedResources Format

LynqNode.status.appliedResources tracks all successfully applied resources for orphan detection.

Format per entry: Kind/Namespace/Name@id

Examples:

Deployment/default/acme-app@deploy-main
Service/default/acme-svc@svc-main
Namespace/acme-ns@ns-main

During reconciliation, the controller compares this list against the current template. Entries present in the status but absent from the template are treated as orphans.

Template Evolution

Lynq handles template changes at runtime. Resources are added, updated, or removed automatically on the next reconcile.

Adding Resources

New resources are created for all existing LynqNodes using this template:

yaml
# Add a new service — created for all existing nodes on next sync
services:
  - id: api-service
    nameTemplate: "{{ .uid }}-api"
    spec:
      apiVersion: v1
      kind: Service
      # ...

Modifying Resources

Resources are updated according to their patchStrategy (default: SSA — only managed fields are changed).

Removing Resources

When a resource is removed from the template, Lynq checks its deletionPolicy:

  • Delete (default): resource is deleted from the cluster
  • Retain: resource is kept with orphan markers added
yaml
# Before: worker and cache exist alongside web
deployments:
  - id: web
    deletionPolicy: Delete
  - id: worker
    deletionPolicy: Retain
  - id: cache
    deletionPolicy: Delete

# After: only web in template
deployments:
  - id: web
    deletionPolicy: Delete

Result:

  • worker — retained with orphan labels
  • cache — deleted from cluster
  • web — continues managed normally

Re-adopting Orphaned Resources

When you re-add a previously removed resource back to the template, Lynq automatically removes orphan markers and restores management.

bash
# Confirm re-adoption
kubectl get deployment acme-worker -o jsonpath='{.metadata.labels.lynq\.sh/orphaned}'
# (empty output = successfully re-adopted)

Best Practices for Template Changes

  • Test in non-production first to validate resource cleanup behavior.
  • Use deletionPolicy: Retain for stateful resources (PVCs, databases) before removing them from templates.
  • Use creationPolicy: Once for init resources (one-time Jobs, seed ConfigMaps).
bash
# Check current tracked resources before changing a template
kubectl get lynqnode <name> -o jsonpath='{.status.appliedResources}' | jq .

See Also