v0.7.9 — Reconciler configuration, user-defined profiles, kubectl DSL, and resilience examples

7 min read

Resilience pack — new sub-examples

Three new examples under examples/resilience/, each with ork validate, ork simulate, ork run walkthrough, and a fully passing ork e2e:

ExampleWhat it teaches
admission-protectionRuntime validation as a resilience layer. Bad CR → failureThreshold exceeded → operator degrades. Patch to valid image → automatic recovery, no restart.
crd-missing-recoveryRuntime CRD watch. Delete the CRD at runtime without deletion protection — Orkestra detects the disappearance, degrades, and retries. Re-apply the CRD and CR → full recovery. lastError preserved as audit trail.
leader-failoverLeader election resilience. Helm-deployed runtime with replicaCount: 2. Kill the konductor pod — a follower is elected within leaseDuration and reconciliation continues. Covers lease inspection and leader election configuration.

Live API documentation

New concept section: Every CRD is a Live API (documentation/concepts/live-api/). Covers the runtime HTTP API on port 8080 — /katalog, /katalog/{crd}, /katalog/{crd}/health, /katalog/{crd}/cr — with real response shapes, isKonductor signal, hasUnhealthyDependencies, and the gateway API on port 8443. Includes a complete endpoint reference.

E2E concept: testing leader-led deployments

New concept page (documentation/concepts/e2e/06-leader-led-deployments.md) explaining the follower routing problem and how leaderElection solves it. Covers isKonductor, lease namespace defaulting, when to use service vs. leader port-forward, and kubectl.logs with leaderElection. General-purpose — applies to any operator using Kubernetes leader election, not just Orkestra.

Source caching for ork pull -f

ork pull -f komposer.yaml now pre-warms all three local cache namespaces so that subsequent ork template, ork validate, and ork generate calls are served from disk rather than making network requests:

CacheLocationKey
Git Helm sources~/.orkestra/helm/git/<sha256>/SHA256(repo + ref + subpath)
Remote Helm repository charts~/.orkestra/helm/repo/<sha256>/SHA256(repo + chart + version)
Remote HTTPS files~/.orkestra/files/<sha256>/SHA256(url)

Caches have no TTL — entries persist until ork pull -f komposer.yaml --refresh is run, which bypasses all three and overwrites the stored copies. --refresh on the OCI registry pull was already supported; it now extends to Helm and file sources.

Bug fixes

  • Merger log noise: git clone and Helm pull progress messages in resolveGitChart and resolveRemoteChart downgraded from Info to Debug. Only shown with --debug.
  • charts/examples gitlink: dangling gitlink (mode 160000) in the repository index with no .gitmodules caused ork template to fail on machines that cloned the repo via the merger. Re-added as regular tracked files.
  • .gitignore bare ork pattern: the entry ork matched pkg/ork/ and blocked pkg/ork/metrics.go from being tracked. Changed to /ork (root-only).
  • govulncheck in CI: go run golang.org/x/vuln/cmd/govulncheck@latest ./... added to validate-pr workflow. go install + call-by-name was tried first but GOPATH/bin is not on PATH in CI runners.
  • CVE dependency updates: x/cryptov0.52.0, x/netv0.55.0, containerdv1.7.33. Three remaining containerd advisories have Fixed in: N/A upstream and are not actionable.

Breaking: reconciler config moves inside operatorBox

workers, resync, and queue (maxDepth, failureThreshold) move from the CRD root into operatorBox.reconciler. The operatorBox is the complete definition of what makes a CRD an operator — the reconciler runtime config belongs there alongside onCreate, onReconcile, status, and admission.

# before
spec:
  crds:
    service:
      workers: 4
      resync: 30s
      queue:
        maxDepth: 200
        failureThreshold: 5
      operatorBox:
        onCreate: ...

# after
spec:
  crds:
    service:
      operatorBox:
        reconciler:
          workers: 4
          resync: 30s
          queue:
            maxDepth: 200
            failureThreshold: 5
        onCreate: ...

ork validate rejects the old layout with a clear error pointing to operatorBox.reconciler.

New profile class: reconciler

Named presets for concurrency and queue tuning. Reference with operatorBox.reconciler.profile:

Profileworkersresyncqueue.maxDepth
high-throughput105m1000
conservative21m100
development130s50

Inline fields override the profile when both are declared. Retry is always exponential backoff — no maxRetries.

User-defined profiles for all profile classes

All profile classes now support user-defined named presets declared in the profiles: block at the root of a Katalog or Motif. Previously only networkPolicies, resourceQuotas, limitRanges, hpa, pdb, and rollingUpdate supported this. Four additional classes are added:

ClassYAML keyWhat it tunes
Resourcesprofiles.resourcesContainer CPU and memory requests/limits
Probesprofiles.probesProbe timing — initialDelaySeconds, periodSeconds, failureThreshold, successThreshold, timeoutSeconds
Container Securityprofiles.containerSecurityPer-container securityContext — allowPrivilegeEscalation, readOnlyRootFilesystem, runAsNonRoot, capabilities
Pod Securityprofiles.podSecurityPod-level securityContext — runAsNonRoot, runAsUser, runAsGroup, fsGroup
profiles:
  resources:
    - name: api-worker
      requests: { cpu: "500m", memory: "256Mi" }
      limits: { cpu: "2", memory: "1Gi" }

  probes:
    - name: slow-boot
      initialDelaySeconds: 60
      periodSeconds: 20
      failureThreshold: 5
      timeoutSeconds: 10

  containerSecurity:
    - name: strict-readonly
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      runAsNonRoot: true
      capabilities:
        drop: [ALL]

  podSecurity:
    - name: ci-runner
      runAsNonRoot: true
      runAsUser: 2000
      runAsGroup: 2000
      fsGroup: 2000

User-defined profiles are resolved before Orkestra built-ins. Declaring a profile with the same name as a built-in overrides it for that Katalog. ork validate prints a warning when a built-in is shadowed.

operatorBox.autoscaler does not yet support user-defined profiles — configure autoscaler behavior inline.

kubectl: DSL block for e2e assertions

A kubectl: block can now sit alongside resources: and commands: in any expect: entry. Eleven subcommands:

SubcommandGenerates
getkubectl get <kind> <name> -o jsonpath='{<field>}' or --output json/yaml
logskubectl logs -n <ns> [-l <selector> | <name> | leaderElection] [--since=<since>]
describekubectl describe <kind> <name/selector> -n <ns>
execkubectl exec -n <ns> <pod> -- <command>
port-forwardPort-forward + curl as one operation; lifecycle managed by the runner
applykubectl apply -f <file> or inline manifest via stdin
patchkubectl patch <kind> <name> --type=<merge|strategic|json> -p '<patch>'
deletekubectl delete -f <file> or by resource identity; ignoreNotFound: true available
eventskubectl events --for=<kind>/<name> -n <ns>
authkubectl auth can-i <verb> <resource> [--as <as>]
cpkubectl cp <ns>/<pod>:<src> — copies to temp file, asserts content, cleans up
topkubectl top <pod|node> — requires metrics-server; installed automatically via Helm

All subcommands share six assertion fields: equals, notEquals, outputContains, outputNotContains, greaterThan, lessThan. greaterThan and lessThan parse the output as float64 — the check fails if the output is not numeric. jq and yq extraction is supported where applicable. apply and patch run before read subcommands each iteration so mutations take effect before assertions check them.

commands[].run now also supports outputNotContains.

leaderElection on port-forward and logs

Both port-forward and logs entries accept a leaderElection block that resolves the target pod from a Kubernetes coordination.k8s.io/v1 Lease. In multi-replica deployments only the elected leader runs reconcilers — without this, port-forward may land on a follower returning stale state and kubectl logs may target the wrong pod. leaderElection guarantees assertions always run against the pod with authoritative state.

kubectl:
  port-forward:
    - leaderElection:
        lease: orkestra-konductor
      namespace: orkestra-system
      port: 8080
      path: /katalog/myapp/health
      jq: state
      equals: "healthy"
  logs:
    - leaderElection:
        lease: orkestra-konductor
        namespace: orkestra-system
      outputContains: "became konductor"

service and pod are optional when leaderElection is set. For logs, name, labelSelector, and leaderElection are mutually exclusive. lease.namespace defaults to the entry’s namespace.

kubectl.delete

Supports deletion by file or by resource identity. ignoreNotFound: true silences errors when the resource is already gone — useful in cleanup steps or after cascade deletes.

kubectl:
  delete:
    - file: ./crd.yaml
      ignoreNotFound: true
    - kind: Pod
      name: my-pod
      namespace: default
      ignoreNotFound: true

wait: on checkpoints and port-forward entries

Each expect: checkpoint accepts a wait: field — a duration to sleep before the polling loop starts. Individual port-forward entries accept their own wait: — a duration to sleep after the connection is established but before the curl request is sent.

Curl fix: HTTP 4xx/5xx responses no longer silently fail

Port-forward curl calls previously used curl -sf, which exits non-zero on any HTTP 4xx/5xx response. Health endpoints for degraded operators return HTTP 503, causing assertions against degraded state to time out rather than report the actual body. Changed to curl -s — the body is always returned and assertions check content directly.

Port-forward progress spinner

During the polling loop, a spinner shows the URL being curled and the resolved target (curl http://localhost:PORT/path (→ pod/holder-name)).

Tool pre-flight: ork e2e scans the spec and installs missing tools before assertions run — curl (port-forward), jq, yq (apt-get / apk / brew), and metrics-server (Helm, with --kubelet-insecure-tls on kind clusters).

include: for expect: composition

Large test suites can be split across files. An include: entry is replaced in place by the checkpoints in the referenced file — position in the list determines where the expanded checkpoints appear in the run order. The file uses expect: as its root key.

expect:
  - include: ./infra-ready.yaml   # expands here — runs first
  - name: Operator-specific check
    after: cr-applied
    timeout: 30s
    resources:
      - kind: MyOperator
        name: my-resource
  - include: ./cleanup.yaml       # expands here — runs last

ork validate expands all includes before reporting — the checkpoint list and count reflect the fully-expanded run order. Paths are resolved relative to the e2e.yaml that contains the include: entry.

Validator: ork validate checks all kubectl: blocks — required fields, mutual exclusion, at least one assertion per entry, jq/yq format consistency, top kind must be pod or node.

Fixture: pkg/e2e/fixture/ is a living integration test with one checkpoint per subcommand. Rule: add a checkpoint when you add a subcommand.