v0.7.11 — Workload autoscaler, user-defined notes, and three new example packs
Notes — the template vocabulary of a Katalog
ork init --pack intermediate/09-notes
Four sub-examples: built-in notes for live cluster queries and fallbacks (01-built-in), user-defined notes declared inline (02-user-defined), notes packaged into a Motif for team distribution via spec.imports (03-motifs), and Komposer-level note override (04-komposer). A root simulate.yaml and e2e.yaml run all four in sequence.
Temporal — time-dependent or time-aware operators
ork init --pack use-cases/temporal
Four sub-examples: business-hours provisioning (01-business-hours), weekly maintenance window (02-maintenance-window), per-region peak-hour replica scaling (03-regional-peak), and business-hours autoscaling driven by a user-defined note (04-autoscale). No CronJobs in any example.
Workload Autoscaler — replica control driven by time, external metrics, or cross-operator state
ork init --pack use-cases/workload-autoscaler
Three sub-examples: time-based jump scaling (01-time-based), external API step scaling via external: with the dev server (02-external-api), and cross-operator step scaling via cross: reading a sibling CRD’s queue depth (03-cross-operator).
autoscale: — replica control for Deployments, StatefulSets, and ReplicaSets
Adds an autoscale: block to deployments:, statefulsets:, and replicasets: declarations. On every reconcile, the autoscaler evaluates scale-up and scale-down conditions and patches spec.replicas when they pass. The reconciler’s drift correction is suppressed for any workload that declares autoscale: so the two never fight.
deployments:
- name: "{{ .metadata.name }}"
replicas: 2 # baseline — used as the starting point, not enforced once autoscale owns replicas
autoscale:
min: 2
max: 10
cooldown: 3m # minimum gap between any two scale events
scaleUp:
conditions:
when:
- field: external.queue.queue.pendingJobs
greaterThan: "100"
increment: 2 # step scaling — adds 2 replicas per tick
scaleDown:
conditions:
when:
- field: external.queue.queue.pendingJobs
lessThan: "20"
decrement: 1
Scale modes — each direction supports either step or jump scaling:
increment: N/decrement: N— add or remove N replicas per tick, clamped tomin/maxtarget: N— jump directly to N replicas in one reconcile
Condition sources — any data in the resolver context is a valid scaling signal:
time:/dayOfWeek:— time-window and weekday conditions (built-in notes:weekday,weekend,timeInWindow,nextCron)field:referencingexternal.*— scale on live HTTP metrics fetched viaexternal:each reconcilefield:referencingcross.*— scale on a sibling CRD’s status fields viacross:(informer cache, no HTTP call)
negate: true on any condition — inverts the result, enabling “not in business hours” and similar patterns without writing a separate note:
when:
- dayOfWeek:
weekday: true
negate: true # passes on weekends
External JSON auto-parsing — when an external: call returns a JSON object body, top-level keys are merged into external.<name> so nested fields are navigable with dot-path syntax (external.queue.queue.pendingJobs).
ork validate — validates autoscale: declarations: checks that min ≤ max, that each direction has exactly one of target, increment, or decrement, and that cooldown is a valid duration.
Dev server /workload-metrics — ork run --dev-server exposes a stateful metrics endpoint for testing external API autoscale locally. POST /workload-metrics/flip toggles between low-load (8 pending jobs) and high-load (152 pending jobs) without touching the cluster.
User-defined notes — named template expressions for Katalogs and Motifs
Adds a notes: block to Katalog and Motif. Each entry is a named Go template expression that becomes a callable function in every {{ }} context across the Katalog — status fields, when: conditions, onReconcile templates, normalize rules.
notes:
- name: serviceHost
description: Fully-qualified cluster hostname for this workload
expression: "{{ .metadata.name }}.{{ .metadata.namespace }}.svc.cluster.local"
- name: fullImage
expression: "{{ .spec.image }}:{{ .spec.tag | default \"latest\" }}"
spec:
crds:
workload:
operatorBox:
status:
fields:
- path: host
value: "{{ serviceHost }}" # calls the note
onReconcile:
deployments:
- image: "{{ fullImage }}"
Notes declared in a Motif are distributed the same way as profiles — via spec.imports at the Katalog level, available to every CRD without re-declaring them:
spec:
imports:
- motif: ./org-standards.yaml # contributes notes and profiles Katalog-wide
A Komposer can override any note inline without touching the Katalog:
# komposer.yaml
notes:
- name: serviceHost
expression: "{{ .metadata.name }}.{{ .metadata.namespace }}.svc.prod-cluster.example.com"
CLI: ork validate --notes prints the merged note registry after validation. ork notes -f katalog.yaml appends user-defined notes to the built-in catalog.
Fix: ork patterns surfaces auth errors instead of silently showing 0 patterns
ork patterns without a valid GHCR login previously rendered an empty table (“0 patterns”) with no explanation. The catalog listing API requires auth even on public registries — unlike ork run which uses anonymous artifact pulls — so the two commands appeared to behave differently without any hint as to why.
The error is now written to stderr with a login hint:
warning: could not list patterns from ghcr.io/orkspace/orkestra-registry/patterns/katalogs
→ unauthorized: authentication required
hint: try logging in with: docker login ghcr.io
Same fix applied to the motif URL listing path.
spec.imports — katalog-wide profile scoping for Motifs
Profiles declared in a Motif can now be imported at the Katalog level via spec.imports, making them available to every CRD in the Katalog:
spec:
imports:
- motif: ./org-standards.yaml
crds:
application:
operatorBox:
onCreate:
deployments:
- resources:
profile: org-standard # ✓ available to all CRDs
database:
operatorBox:
onCreate:
deployments:
- resources:
profile: org-standard # ✓ same profile, no import needed here
spec.crds[name].imports still works for resources, status, and admission — profiles declared in those Motifs are ignored at the CRD level. This removes the previous behaviour where profiles from a CRD-level import leaked into the Katalog-wide registry, which caused conflicts when more than one CRD imported the same Motif.