How Database-Driven Kubernetes Automation Actually Works
Source: Dev.to
Mental Model
- LynqHub – Connects to your database and watches for changes.
- LynqForm – Defines the Kubernetes resources to create (templates).
- LynqNode – The actual instance, one per database row per template.
When a row appears in the database, the hub creates a LynqNode. The node controller renders the templates and applies the resources. When the row disappears or is deactivated, cleanup happens automatically.
LynqHub
The hub tells Lynq where your data lives and how to map columns to template variables.
apiVersion: operator.lynq.sh/v1
kind: LynqHub
metadata:
name: my-saas-hub
spec:
source:
type: mysql
mysql:
host: mysql.default.svc.cluster.local
port: 3306
database: nodes
table: node_data
username: node_reader
passwordRef:
name: mysql-secret
key: password
syncInterval: 30s
valueMappings:
uid: node_id # unique identifier for each node
activate: is_active # boolean controlling resource existence
extraValueMappings:
planId: subscription_plan
region: deployment_region
valueMappings– Required columns (uid,activate).extraValueMappings– Optional custom fields that become template variables.
The hub polls the database at syncInterval. If activate=true, Lynq creates resources; if it becomes false, cleanup starts.
LynqForm
A form is the blueprint that defines which resources to create for each database row.
apiVersion: operator.lynq.sh/v1
kind: LynqForm
metadata:
name: web-app
spec:
hubId: my-saas-hub
deployments:
- id: app
nameTemplate: "{{ .uid }}-app"
labelsTemplate:
app: "{{ .uid }}"
plan: "{{ .planId | default \"basic\" }}"
spec:
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 2
template:
spec:
containers:
- name: app
image: "{{ .deployImage | default \"nginx:latest\" }}"
services:
- id: svc
nameTemplate: "{{ .uid }}-svc"
dependIds: ["app"]
spec:
apiVersion: v1
kind: Service
# …
Templates use Go’s text/template syntax with Sprig functions, giving you over 200 helper functions out of the box. Common variables:
{{ .uid }}– unique name suffix.{{ .planId | default "basic" }}– safe default for nullable columns.{{ .uid | trunc63 }}– respects Kubernetes naming limits.
Resource Policies
Each resource can define its own creation, deletion, and conflict handling policies.
Creation Policy
deployments:
- id: app
creationPolicy: WhenNeeded # default
deletionPolicy: Delete
conflictPolicy: Stuck
patchStrategy: apply
- WhenNeeded – Continuous sync; manual deletions are restored, template updates are applied.
- Once – Create once and never touch again (ideal for init jobs).
jobs:
- id: init-job
creationPolicy: Once
nameTemplate: "{{ .uid }}-init"
spec:
apiVersion: batch/v1
kind: Job
spec:
template:
spec:
containers:
- name: init
command: ["sh", "-c", "echo 'one-time setup'"]
restartPolicy: Never
Deletion Policy
- Delete (default) –
OwnerReferenceis set; Kubernetes garbage collection removes the resource. - Retain – Resource is kept; Lynq adds orphan labels for later discovery (useful for PVCs).
persistentVolumeClaims:
- id: data-pvc
deletionPolicy: Retain
nameTemplate: "{{ .uid }}-data"
Conflict Policy
- Stuck (default) – Reconciliation stops on conflict, emitting an event.
- Force – Takes ownership via Server‑Side Apply with
force=true(useful during migrations).
Dependency Management
Resources can depend on one another, ensuring correct creation order and optional readiness checks.
secrets:
- id: db-creds
nameTemplate: "{{ .uid }}-creds"
deployments:
- id: db
dependIds: ["db-creds"]
waitForReady: true
- id: app
dependIds: ["db"]
waitForReady: true
Lynq builds a directed acyclic graph (DAG) from dependIds and applies resources topologically. Cycles cause a fast failure.
waitForReady: true– Lynq waits for the dependency to become ready before proceeding.skipOnDependencyFailure(defaulttrue) – Dependent resources are skipped if a dependency fails; set tofalseto force execution.
jobs:
- id: cleanup-job
dependIds: ["main-app"]
skipOnDependencyFailure: false
Full Reconciliation Flow
- Hub controller polls the database every
syncInterval. - For each active row, it creates/updates a
LynqNodeCR. - Node controller picks up the
LynqNode. - It renders all templates with the row’s data.
- Builds a dependency graph and sorts resources.
- Applies each resource in order using Server‑Side Apply.
- Waits for readiness if configured.
- Updates
LynqNodestatus with the outcome.
Deactivation / Deletion
- Hub detects row deactivation or removal.
- Corresponding
LynqNodeCR is deleted. - Finalizer runs cleanup based on each resource’s
deletionPolicy.Delete→ resource removed.Retain→ orphan label added.
You can watch the process live:
kubectl get lynqnodes -w
kubectl describe lynqnode
Sample status output:
status:
desiredResources: 5
readyResources: 5
failedResources: 0
appliedResources:
- "Deployment/default/acme-app@app"
- "Service/default/acme-svc@svc"
When to Use This Pattern
- You already store business data in a relational database (users, tenants, orgs).
- You need fast provisioning without the typical commit‑sync‑reconcile loops.
- You must replicate the same set of resources many times with different values.
- Template versioning is more important than per‑instance versioning.
It’s not ideal for a handful of unique environments where traditional IaC tools suffice. For large‑scale, data‑driven provisioning, Lynq offers a concise, declarative approach.
References & Hands‑On
- Killercoda quick‑start (≈10 min)
- Documentation
- GitHub repository
Feel free to ask questions in the comments!