Skip to content

Latest commit

 

History

History
308 lines (221 loc) · 10.1 KB

File metadata and controls

308 lines (221 loc) · 10.1 KB

Tutorial: your first LeanNodes flow

A 20-minute hands-on walkthrough. You'll deploy a dummy app, bind it to a LeanNodes flow, scale it to zero, hit its URL from your browser, and watch it cold-start live in the UI.

Prerequisites: a working LeanNodes install (UI reachable, orchestrator /readyz = 200, Istio meshConfig has the leannodes-authz provider). If you're not there yet, follow getting-started.md first.


What we're building

One flow with one managed Deployment. The simplest possible shape:

  user → browser → GET http://demo.your-domain.com/hello
                         │
                         ▼
                Istio ingress calls LeanNodes ext_authz
                         │
                         ▼
            LeanNodes matches host+path to the flow
                         │
                         ▼
           scales `echo` from 0 → 1, waits for Ready
                         │
                         ▼
            returns OK; Envoy routes to the echo pod
                         │
                         ▼
                  "hello from leannodes echo"

After 10 minutes of inactivity (shortened to 15s in local dev), the idle reconciler scales echo back to 0. The next request does the whole cycle again.


Step 1 — Deploy the sample app

kubectl apply -f deploy/examples/sample-app.yaml

Confirms:

kubectl -n leannodes-demo get deploy echo svc echo
# NAME  READY  UP-TO-DATE  AVAILABLE  AGE
# echo  1/1    1           1          5s

This is just a plain hashicorp/http-echo Pod. Nothing LeanNodes-specific.


Step 2 — Set up Istio routing

Pick a hostname that resolves to your ingress. Replace demo.REPLACE_ME.com throughout this tutorial with your real choice.

# Real cluster: point DNS at your ingress LB first
#   (ExternalDNS automates this if you use the annotation in
#   deploy/examples/gateway-virtualservice.yaml)

# Local dev: add an /etc/hosts entry
echo "127.0.0.1 demo.REPLACE_ME.com" | sudo tee -a /etc/hosts

# Apply the Gateway + VirtualService
sed 's/demo.REPLACE_ME.com/demo.your-domain.com/g' \
  deploy/examples/gateway-virtualservice.yaml | kubectl apply -f -

Verify the route exists before moving on:

# Cloud install — hit the ingress directly
curl -v http://demo.your-domain.com/hello
# Expected while LeanNodes has NO flow yet: 200 OK + "hello from leannodes echo"
# (Envoy routes directly to echo; no ext_authz in play because there's
# no AuthorizationPolicy scoped to this host yet.)

# Local dev — port-forward first
kubectl --context kind-leannodes-dev -n istio-system port-forward \
  svc/istio-ingressgateway 18443:80 &
curl -v http://demo.REPLACE_ME.com:18443/hello

If this first curl returns 200 OK, the Istio routing is correct and we're ready to create the flow. If it returns no healthy upstream or no route matched, fix the Gateway/VirtualService before continuing — see troubleshooting.md.


Step 3 — Create the flow via the UI

Open the LeanNodes UI:

  • Cloud: https://leannodes.your-domain.com
  • Local dev: http://localhost:8080

Login. You land on the Dashboard.

3a. Create the flow

Click Flows in the left sidebar → New flow (top-right).

You land in the editor. On the left is the workload sidebar with every managed workload in the cluster, grouped by namespace. On the right is the flow settings panel.

3b. Fill in flow-level settings (right panel)

  • Name: demo-hello
  • Owner namespace: leannodes-demo
  • Binding 1 host: demo.REPLACE_ME.com (your real host)
  • Binding 1 path: /* (prefix match for all paths)
  • Leave the rest at defaults (windowSeconds: 600, fastHoldSec: 30, etc.)

3c. Drag the workload onto the canvas

In the left sidebar, expand the leannodes-demo namespace. Find echo and drag it onto the canvas in the middle. A single node card appears labelled echo.

The node properties panel opens on the right — close it (keep the defaults: targetReplicas: 1, readiness k8s + minStable: 1).

3d. Save

Click Create flow at the top-right.

On success the UI navigates to the flow detail page. The state pill next to the flow name shows Idle. The "Nodes" table shows echo at 1/1 ready (because we haven't scaled it down yet). The DAG visualisation shows one standalone node.

Behind the scenes: the orchestrator wrote a per-flow AuthorizationPolicy to istio-system, auto-granted you read+update+delete on this flow (F9), and emitted a flow.created audit event (bottom of the page).

Inspect what it wrote:

kubectl -n istio-system get authorizationpolicy \
  -l leannodes.io/flow-id=<your-flow-id> \
  -o yaml | grep -A 10 "rules:"

You should see hosts: [demo.REPLACE_ME.com, demo.REPLACE_ME.com:*] and paths: [/*] with action: CUSTOM + provider leannodes-authz.


Step 4 — Scale echo to 0 (cold state)

kubectl -n leannodes-demo scale deployment echo --replicas=0

Back in the UI, wait ~5s then refresh the flow detail page. The Nodes table shows echo 0/0. The no live pods pill appears next to the state pill ("state says READY but zero replicas — flow was scaled down externally"). This is the "cold" state — the flow definition is still in place, the underlying pod is just off.

After ~15s the idle reconciler notices and transitions the flow state to IDLE. On a real cluster this takes windowSeconds=600s (10 min) from the last matching traffic hit; local dev shortens to 15s via the LEANNODES_IDLE_RECONCILE_SEC env var.


Step 5 — Hit the URL from your browser (the moment of truth)

Open a second browser tab (keep the flow detail page open in tab 1 so you can watch the state flip).

Go to: http://demo.REPLACE_ME.com/hello (local dev: http://demo.REPLACE_ME.com:18443/hello)

What happens:

Time UI tab Browser tab
0.0s state pill: Idle "Connecting…"
~0.1s state pill flips: Idle → Starting (amber pulsing) still waiting
~0.2s "Live cold-start" card appears; echo node status: scaling still waiting
~1-3s Nodes table: echo 0 → 1 desired, then 1/1 ready still waiting
~3-5s +1.5s xDS settle delay (orchestrator waiting for istiod push) still waiting
~3-5s state pill: Starting → Ready; "Last cold-start" card populates renders "hello from leannodes echo"

First request = HTTP 200. No retry needed, no 503. The whole point of the design.

If you see a 503 no healthy upstream instead, you've hit one of the bugs we fixed in v1.0.0. Either your image is older than v1.0.0, or there's a config mismatch — see troubleshooting.md.


Step 6 — Try the warm path

Refresh the browser tab. Response is near-instant (<100ms) because LeanNodes's in-memory flow cache hits before any DDB lookup, and the pod is already 1/1 ready.

Same with curl, if you prefer the terminal view:

time curl -s http://demo.REPLACE_ME.com/hello
# hello from leannodes echo
# real    0m0.04s

The difference between 3-5s (cold) and 40ms (warm) is the whole product. Every first-hit pays the cold-start tax; every subsequent hit within the idle window is free.


Step 7 — Watch it drain (the other half of the loop)

Stop making requests and wait for windowSeconds to elapse — 10 min on a real install, 15s on local dev.

In the UI, you'll see:

  • State pill: Ready → Draining (brief, ~1s)
  • Nodes table: echo 1 → 0 desired
  • State pill: Draining → Idle
  • Audit log gets a new flow.drained event

Confirm kubectl-side:

kubectl -n leannodes-demo get deploy echo
# echo  0/0  0  0  (back to cold)

You can skip the wait by clicking Drain on the flow detail page. Same result, happens in ~2s.

Hit the URL again → the full cold-start loop repeats.


Step 8 — Observe the full lifecycle

The flow detail page tells the whole story of each cold-start cycle:

  • Header state pill — where the flow is right now (Idle / Starting / Ready / Draining / Failed)
  • Nodes table — live replica counts + health per managed workload
  • Last cold-start card — per-node elapsed times + attempt counts for the most recent DAG run. Greyed out + marked "(history)" when the flow is idle so you don't mistake stale history for current state.
  • Recent cold-starts table — last 20 DAG runs (7-day retention), with expandable per-level, per-node detail
  • Audit log — who did what: create, update, warm (manual cold-start trigger), drain (manual or idle-driven), pin/unpin, ACL grants (F9)
  • Access panel (admins only) — per-flow grants for user/group/role subjects

Nothing else to do — this is LeanNodes in steady state.


Step 9 — Tear down

# Remove the flow (cascades the AuthorizationPolicy + ACL rows)
curl -X DELETE http://leannodes.your-domain.com/api/v1/flows/<flow-id>

# Remove the Istio Gateway + VirtualService + sample app
kubectl delete -f deploy/examples/gateway-virtualservice.yaml
kubectl delete -f deploy/examples/sample-app.yaml

# Remove /etc/hosts entry (local dev)
sudo sed -i.bak '/demo.REPLACE_ME.com/d' /etc/hosts

What you learned

  • How a flow maps traffic (host+path) to a DAG of workloads
  • What each of the 5 states means + when transitions happen
  • Where to look in the UI for live vs historical state
  • How to confirm the plumbing worked (AuthorizationPolicy written, flow.created in audit log, xDS-settled Ready)

Next steps

  • Try the diamond DAG: deploy/examples/flow-diamond-dag.json shows parallel fan-out + fan-in across 4 nodes.
  • Try the External probe: deploy/examples/flow-with-external.json shows gating a managed DAG on a probe-only external dependency.
  • Read the concepts doc for the full mental model: cold-start lifecycle internals, DDB state layout, RBAC model.
  • Instrument alerts: the shipped PrometheusRule covers LeanNodesOrchestratorDown, ColdStartErrorSpike, LeaderElectionFlapping, etc. Wire them to your Alertmanager via the chart's monitoring.alerts.runbookBase value pointing at docs/runbook.md.