Repo: github.com/hrodrig/kzero · Releases: Releases · DeepWiki: hrodrig/kzero
Badges: Version is a static badge aligned with the repo VERSION file (next release target). GitHub release shows the latest published tag on GitHub; it can lag the VERSION file until a release is cut. Go matches go.mod. License points at this repository’s license file. Ask DeepWiki links to DeepWiki AI-generated docs for this repository (see also badge maker). CI, Security, and CodeQL reflect GitHub Actions workflows. codecov tracks coverage uploaded from CI. pkg.go.dev, Go Report Card, and deps.dev summarize the Go module and dependencies. gghstats clones shows Git clone traffic for this repo (see gghstats).
Terminal demo (recorded with VHS; source docs/demo.tape, config docs/demo-kzero.yaml):
Regenerate from the repo root: docs/README.md — Terminal demo.
Declarative Kubernetes workload orchestration: ordered down / up (and reset) pipelines from YAML, with phase hooks, optional per-step pre / post scripts, workload scale steps, Helm releases (shell scripts or Helm SDK chart manifests under helm.workspace), pvc / exec API steps, custom: scripts, API watchdog with throttled progress logs and pipeline.stalled alerts.
Operator deployment (bastion-first, out-of-band): see docs/deployment-models.md. Scope vs alternatives: docs/scope-and-alternatives.md. Playbooks and annotated profiles: kzero-selfhosted — this repo ships the CLI binary, packages, container image, and Homebrew cask only (same split as pgwd / pgwd-selfhosted).
Releases (GitHub Releases) ship binaries, .deb / .rpm, ghcr.io/hrodrig/kzero, and Homebrew. Supply chain (v0.7.0+): SPDX / CycloneDX SBOMs + Cosign on checksums.txt and GHCR — see verify Cosign. No Helm charts as release artifacts.
Behavior and acceptance: SPECIFICATIONS.md. Shipped: v1.0.0 — stable contract: default run.execution: native (#32), process exit codes 0–4 (#42), product kind CI (#34), PVC/StatefulSet cookbook (#33). Prior: v0.9.2 (doctor, completion, kubectl-kzero, retry jitter, JSON Schema); v0.9.0 (graceful shutdown, require_delivery, E2E smoke) — CHANGELOG.md. Mitigations: pipeline-network-loss.md. Diagrams: docs/diagrams.md.
- Terminal demo
- Features
- Requirements
- Install or update
- Operator deployment
- Quick start
- First run
- Usage examples
- Configuration reference
- Environment and precedence
- Troubleshooting
- Security note
- Releases and CI
- Verify Cosign (v0.7.0+)
- Development
- Per-step
pre/post(example) - Pipeline order and integrity on
down - Get involved
- License
- Configuration-first (
schema_version: "1.0"): pipelines and hooks live in config, not hardcoded playbooks. - Commands:
analyze,doctor,target,notify test,verify,probe,down,up,reset,completion,version— global--log-format text|jsonand--log-levelon pipeline output (see SPEC). (reset= fulldownthenup; ifdownfails,updoes not run). Every pipeline command prints aKubernetes target:block (started_at, optionalclient_id, context, cluster, API server, kubeconfig path) before work starts.kzero notify testverifies outboundnotify.*channels without running a pipeline.analyzeoptionally checks the API that eachdeployment/statefulset/pvc/execin the plan exists when kubeconfig loads. - Exit codes (stable for wrappers): 0 success; 1 config; 2 Kubernetes; 3 executor / pipeline abort; 4 notify delivery (
require_deliveryornotify testPOST fail) — SPEC §5. - Phase hooks:
pre-down,post-down,pre-up,post-up,on-error(shell script paths). - Per-step hooks (
pre/post): optional shell scripts for a single pipeline step—run immediately before and after that step’s main action;postruns only if the main action succeeds. - Step types: compact refs (
deployment.ns/name,statefulset.ns/name,pvc.ns/claim,exec.ns/pod),release.ns/name(Helm SDK chart manifest<release>.yamlor legacy<release>.shunderhelm.workspace), andcustom: ./script.sh(with optional siblingpre/postkeys on the same YAML mapping). DaemonSet is not supported as a built-in kind becausekubectl scalerejects it (no/scalesubresource); use acustom:step withkubectl patchto set anodeSelectorthat drains the pods. run.execution:native(default when omitted — client-go scale + Helm SDK + APIpvc/exec),shell(opt-in —kubectl/helmsubprocesses), orauto(native with shell fallback). On a bastion,nativeavoids hostkubectl/helmwhile staying out-of-band; in-cluster Jobs may use the distroless image — see deployment-models.md and SPEC —run.execution.- Run modes:
dry-run(plan only, no cluster mutations) andlive.
Libraries (see go.mod): Cobra v1.10.2, Viper v1.21.0.
Host tooling depends on run.execution and your pipeline step types (see SPECIFICATIONS.md):
| Path | Host tools |
|---|---|
run.execution: native (default) / auto |
Valid kubeconfig (or in-cluster SA); no host kubectl for scale/wait/pvc/exec/Helm SDK release.* |
run.execution: shell (opt-in) |
kubectl on PATH (or command.kubectl); helm when using release.* shell scripts |
Phase hooks, custom:, per-step pre/post |
Always /bin/sh <script> (shebang ignored). Scripts must be POSIX//bin/sh-safe — on Ubuntu /bin/sh is often dash (pipefail / [[ fail). See SPEC — Hook and script interpreter. |
- RBAC sufficient for the operations in your pipelines (for example
get/patch/scale, PVC delete, Helm releases, pod exec) - Go 1.26.5+ if you build from source (
make build) or usego install
Pre-built .deb, .rpm, .tar.gz (and .zip on Windows), plus multi-arch container images on ghcr.io/hrodrig/kzero, are on GitHub Releases and latest release. The release badge at the top of this README shows the current tag at a glance.
Why not a single latest URL for every file? GitHub’s …/releases/latest/download/<file> only works if the asset filename is identical on every release. GoReleaser here uses the git tag (with v) in Linux package and archive basenames (for example kzero_v1.0.0_linux_amd64.deb), while the download path is still …/download/v1.0.0/…. Pick names from the release page, use the snippet below, or use the badge.
# Latest published release tag (python3 or jq). Linux .deb basename includes the tag WITH "v".
TAG="$(curl -fsSL https://api.github.com/repos/hrodrig/kzero/releases/latest | python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])')"
# Alternative: TAG="$(curl -fsSL https://api.github.com/repos/hrodrig/kzero/releases/latest | jq -r .tag_name)"
[ -n "$TAG" ] || { echo "Could not resolve tag (empty). Install python3 or jq, or set TAG manually from the Releases page." >&2; exit 1; }
DEB="kzero_${TAG}_linux_amd64.deb"
URL="https://github.com/hrodrig/kzero/releases/download/${TAG}/${DEB}"
TMP="/tmp/${DEB}"
# Download to /tmp so user _apt can read the file (apt often cannot read ~/.deb when $HOME is mode 700).
if ! curl -fsSL "$URL" -o "$TMP"; then
echo "Download failed (curl exit $?). Check URL: $URL" >&2
exit 1
fi
if [ ! -f "$TMP" ]; then
echo "Expected $TMP after download — not found." >&2
exit 1
fi
sudo apt install "$TMP"Paste the block as a whole, or chain with &&, so apt does not run after a failed curl. curl -f (via -fsSL) exits non‑zero on HTTP errors (404, etc.).
apt + _apt / “Permission denied” under $HOME: if you curl the .deb into ~ and run sudo apt install ./kzero_….deb, Debian/Ubuntu may warn that _apt cannot read the file (home directory not world-executable). Use /tmp as above, or sudo cp "$DEB" /tmp/ then sudo apt install "/tmp/$DEB".
Empty TAG: if jq/python3 failed, you get .../download//kzero__linux_amd64.deb and a broken filename.
.deb / .rpm install kzero to /usr/bin and place /etc/kzero/kzero.yaml (from the sample; config|noreplace on upgrades). The CLI defaults to ./kzero.yaml when --config is omitted—use kzero --config /etc/kzero/kzero.yaml … after install, or copy that file to ./kzero.yaml. Use linux_arm64 in the download filename on ARM64 Linux.
| Format | Example (tag v1.0.0 in the URL path; artifact basename includes the same v1.0.0) |
|---|---|
.deb |
curl -fsSL -o /tmp/kzero_v1.0.0_linux_amd64.deb https://github.com/hrodrig/kzero/releases/download/v1.0.0/kzero_v1.0.0_linux_amd64.deb then sudo apt install /tmp/kzero_v1.0.0_linux_amd64.deb |
.rpm |
curl -fsSLO https://github.com/hrodrig/kzero/releases/download/v1.0.0/kzero_v1.0.0_linux_amd64.rpm then sudo rpm -Uvh kzero_v1.0.0_linux_amd64.rpm or sudo dnf install ./kzero_v1.0.0_linux_amd64.rpm |
.tar.gz (Linux) |
curl -fsSLO https://github.com/hrodrig/kzero/releases/download/v1.0.0/kzero_v1.0.0_linux_amd64.tar.gz then tar xzf kzero_v1.0.0_linux_amd64.tar.gz and run ./kzero from the extracted tree (see share/examples/kzero/kzero.sample.yml) |
.tar.gz (macOS) |
curl -fsSLO https://github.com/hrodrig/kzero/releases/download/v1.0.0/kzero_v1.0.0_darwin_amd64.tar.gz (or …_darwin_arm64.tar.gz on Apple silicon) |
Update: download a newer release and run the same install command again (rpm -Uvh, apt install over the .deb, or replace the tarball tree).
Windows: use the .zip asset for your arch (for example kzero_v1.0.0_windows_amd64.zip), unpack, run kzero.exe where kubectl is available.
Docker: docker pull ghcr.io/hrodrig/kzero:v1.0.0 (match the image tag to the release you want). Published images use gcr.io/distroless/static-debian13:nonroot (static kzero binary only: no shell, no BusyBox/Alpine runtime). Dockerfile in this repo uses the same final stage. Package: ghcr.io/hrodrig/kzero.
Homebrew and BSD packaging helpers: see Install or update and contrib/README.md.
brew install hrodrig/kzero/kzeroTap: hrodrig/homebrew-kzero (cask updated automatically on each release).
Then run kzero --config /etc/kzero/kzero.yaml analyze (or follow Quick start to build from a clone).
# Bash / Zsh (current session)
source <(kzero completion bash)
source <(kzero completion zsh)
# Fish (persist)
kzero completion fish > ~/.config/fish/completions/kzero.fishkzero completion powershell | Out-String | Invoke-ExpressionRelease archives ship two binaries from the same cmd/kzero: kzero and kubectl-kzero. With kubectl-kzero on $PATH, kubectl plugin discovery enables kubectl kzero ….
# Homebrew / release tarball already installs both
kubectl plugin list | grep kzero
# Or from a local build
PREFIX=/usr/local make install-kubectl-plugin
kubectl kzero analyze --config ./kzero.yamlkzero version / kubectl kzero version print the binary label (kzero vs kubectl-kzero) so logs show which entry point fired.
kzero doctor --config ./kzero.yaml
kzero doctor --config ./kzero.yaml --output jsonChecks config load, kubectl/helm on PATH (when run.execution is shell or auto; skipped under default native), API reachability, pipeline workload refs, and SelfSubjectAccessReview RBAC hints. Config fail → exit 1; check errors → exit 2; warnings allowed with exit 0.
make build
./bin/kzero --help
./bin/kzero analyze --config configs/kzero.sample.ymlOn FreeBSD, use gmake (or plain make, which forwards to GNU Make via the stub Makefile).
Copy and edit configs/kzero.sample.yml (or use --config / ./kzero.yaml). Override values via environment variables with the KZERO_ prefix (see Viper / sample file comments).
If you installed from Releases or go install (below), use kzero on your PATH instead of ./bin/kzero.
From any machine with Go 1.26.5+ (installs to $(go env GOPATH)/bin; ensure that directory is on your PATH):
go install github.com/hrodrig/kzero/cmd/kzero@latestUse a release tag instead of @latest if you want a pinned version (for example @v1.0.0). Module reference: pkg.go.dev/github.com/hrodrig/kzero.
End-to-end operator profile (maintenance reset: truncate, Helm infra, PVC wipe, infra_probe, notify): kzero-selfhosted — full-reset-example with validation runbook.
kzero reads one YAML file per invocation (default ./kzero.yaml; after a .deb/.rpm install use kzero --config /etc/kzero/kzero.yaml).
- Start from
configs/kzero.sample.yml(clone, release tarball,/etc/kzero/kzero.yaml, orkzero --print-sample-config > kzero.yaml). - Keep
run.mode: dry-rununtilkzero analyzematches expectations; see SPECIFICATIONS.md.
# From clone or tarball:
cp configs/kzero.sample.yml kzero.yaml
# Homebrew / binary-only install (no configs/ in PATH):
kzero --print-sample-config > kzero.yaml
kzero analyze
kzero down # dry-run when run.mode: dry-runFor bastion, cron, CI, and live patterns: kzero-selfhosted → run/README.md. For a full platform reset playbook (hooks, Helm SDK manifests, transcripts): full-reset-example.
Where to run kzero: docs/deployment-models.md — bastion / management host (recommended) for production reset; in-cluster Job is optional, not the default recovery model.
| Goal | Start here |
|---|---|
| Full platform reset (truncate, PVC, Helm SDK, probe) | kzero-selfhosted/run/examples/full-reset-example/ · validation runbook |
| PVC / StatefulSet data patterns | pvc-statefulset-data-strategy.md — scale→wait→pvc.*, wipe, snapshot/custom:, init |
| Bastion / cron / systemd | kzero-selfhosted/run/ — standalone, automation & CI |
| Network loss during live reset | pipeline-network-loss.md — run.api_watchdog, notify [ERR] / require_delivery, phase-boundary preflight; supplemental mitigations |
docker run (analyze / version; live limits) |
run/docker/README.md |
| kind e2e (product CI) | testing/kind/ — GHA integration-kind; full lab: kzero-selfhosted/testing/kind |
| Reference hooks & probe assets | run/examples/ |
Install the CLI here (Install or update); run it from a host with kubeconfig and the tools your YAML requires (kubectl / helm on the shell path; native reduces host dependencies — see Requirements).
Paths use ./bin/kzero after make build. If you installed from Releases or go install, use kzero on your PATH the same way.
kzero analyze --config ./kzero.yaml
# With run.mode: dry-run in YAML:
kzero down --config ./kzero.yaml**analyze** validates the profile and prints the normalized plan on stdout: run metadata, phase hooks, indexed [down] / [up] steps (including release script paths and per-step options), and a Deferred block when unimplemented schema fields are set. See SPECIFICATIONS.md → kzero analyze.
kzero notify test --config ./kzero.yaml
kzero notify test -c ./kzero.yaml --event pipeline.errorVerifies notify.* channels without contacting the API or running down / up. Full cookbook: docs/examples/notifications.md.
kzero verify --config ./kzero.yaml
kzero verify -c ./kzero.yaml --log-format jsonSet run.verify: true to run verify automatically after a successful up or reset in live mode.
kzero probe --config ./kzero.yamlOptional infra_probe runs a throwaway mini-pipeline before down / reset in live mode. Cookbook: docs/examples/infra-probe.md. Reference Redis assets: kzero-selfhosted/run/examples/infra-probe/.
kzero analyze --config /path/to/prod/kzero.yaml
kzero up --config /path/to/prod/kzero.yamlkzero reset --config ./kzero.yamlIf down fails, up is not executed (see RunReset).
Cron, GitHub Actions, and YES-gated wrappers: kzero-selfhosted/run/docs/automation-and-pipelines.md.
kzero versionFull schema, validation, and acceptance criteria: SPECIFICATIONS.md. Annotated sample: configs/kzero.sample.yml. Editor autocomplete: configs/kzero.schema.json (YAML Language Server $schema — see sample header). Runtime validation remains the Go loader.
| Block / key | Purpose |
|---|---|
schema_version |
Must be 1.0 today. |
cluster |
Metadata (name, environment, …) for labels and notifications. |
helm |
workspace: directory of <release>.yaml Helm SDK chart manifests (recommended with run.execution: native) and/or legacy <release>.sh scripts; optional registries for OCI login. |
command |
Optional paths for kubectl and helm. |
hooks |
Optional global scripts: pre-down, post-down, pre-up, post-up, on-error. |
notify |
Optional outbound alerts: slack, discord, teams, pagerduty, webhook; live events pipeline.start, success, error, stalled. Optional require_delivery (fail run with exit 4 if error/stalled POST fails). Test with kzero notify test — notifications.md. |
pipelines |
See pipelines below. |
retry |
See retry below. |
run |
See run below. |
| Key | Purpose |
|---|---|
mode |
dry-run (log plan only) or live (execute steps). Required. |
execution |
native (default when omitted), shell (opt-in), or auto — see Features and SPEC. |
timeout |
Wall-clock budget for a full down, up, or reset (Go duration, e.g. 25m). |
kubeconfig |
Path passed to kubectl / helm; empty uses the process environment / default kubeconfig search. |
operation_timeout |
Per-operation ceiling (e.g. 45s) for individual kubectl/helm calls inside a step. |
Pipeline steps always run sequentially in YAML order (no parallel execution). On down, workload steps scale to 0 without waiting for pods to exit unless you add per-step post (or pre on the next step). See Pipeline order and integrity on down and SPEC — Current engine.
| Key | Purpose |
|---|---|
attempts |
Total tries per pipeline step in live mode (integer, minimum effective 1). |
delay |
Base wait before the first retry (Go duration, e.g. 8s); wait is full jitter in [0, delay × 2^(n−1)] (capped at 2m; default base 5s if delay is zero). |
Retries rerun the whole step (per-step pre, main, post). Only transient failures retry (API timeouts, conflicts, 429/503, connection errors). NotFound / Forbidden fail immediately. dry-run never retries. See SPEC — Current engine.
downandupare ordered lists. Each item is either:- a string step reference:
deployment.ns/name,statefulset.ns/name,pvc.ns/claim,exec.ns/pod,release.ns/name(DaemonSet is not a built-in kind — see Features); or - a single-key map whose key is one of the above or
custom, with optional fields beside that key (see below).
- a string step reference:
release.*steps requirehelm.workspace. Withrun.execution: native/auto, use<release>.yamlchart manifests (Helm SDK); withshell, use<release>.shscripts (see SPEC and full-reset-example).
Optional fields on a map step (same YAML mapping as the step ref, alongside pre / post):
| Field | Applies to | Purpose |
|---|---|---|
pre / post |
workload, release, custom |
Shell scripts run immediately before / after the main action; post only if the main action succeeds. |
replicas |
mainly up / scale targets |
Target replica count (integer). |
wait_for_ready |
workloads on up |
If true, wait for rollout / ready after scale-up. Not used to wait for pod termination on down. |
timeout |
step-level override | Go duration string (e.g. 10m) for that step’s bounded wait / operations. |
KZERO_environment variables override values from the loaded YAML (ViperAutomaticEnv). Nested keys use underscores (for examplerun.mode→KZERO_RUN_MODE,client.id→KZERO_CLIENT_ID).--config /path/to/kzero.yamlselects that file explicitly. When omitted, the default file is./kzero.yaml(must exist; there is no built-in fallback path).
kzero always loads a real file path. With no --config, that path is ./kzero.yaml relative to the current working directory. Fix: cd to the directory that contains kzero.yaml, pass --config /absolute/path/kzero.yaml, or copy configs/kzero.sample.yml / /etc/kzero/kzero.yaml to ./kzero.yaml.
Only those two literals are accepted (see validation in internal/config/load.go). Check for typos, quotes, or trailing spaces in YAML.
Any release.ns/name entry requires helm.workspace in the root config. Set it to the directory that contains <release>.yaml (Helm SDK) and/or <release>.sh (shell path), or remove release.* steps if you are not using Helm-driven releases.
reset runs down then up under one run.timeout. If down returns an error, up is skipped (RunReset). Inspect the down failure (hooks, RBAC, or a failing step) before re-running.
run.mode: liveperforms real cluster mutations (kubectl/helmas configured). Stay ondry-rununtil reviews pass.- Hooks (
hooks.*, per-steppre/post,custom:) run as the same OS user that invokeskzero. Only reference scripts you trust; treat changes like production code. - Use least-privilege RBAC for the kube identity in use; pipeline steps may scale, delete, or upgrade workloads depending on your YAML.
See SECURITY.md for reporting vulnerabilities.
- Work on
develop; merge tomainwhen ready. - Before tagging:
make release-check(Docker): lint, test, cover ≥80%, govulncheck, Grype. - On
main: annotated tag + push → Release workflow (GoReleaser, GHCR, Cosign, SBOMs, homebrew-kzero). - Local:
make release(main only). Snapshot:make snapshot→dist/.
# checksums.txt (download assets + .sig / .pem from the GitHub Release)
cosign verify-blob --certificate-identity-regexp 'https://github.com/hrodrig/kzero/.+' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate checksums.txt.pem --signature checksums.txt.sig checksums.txt
# GHCR image (replace TAG)
cosign verify --certificate-identity-regexp 'https://github.com/hrodrig/kzero/.+' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/hrodrig/kzero:TAGmake test
make lint
make buildSee CONTRIBUTING.md and CHANGELOG.md. Security reporting: SECURITY.md.
Use this when something must happen between ordered steps—for example, run a script while a StatefulSet is still running, then scale that StatefulSet to zero in the same step:
pipelines:
down:
- deployment.app/worker
- statefulset.data/job-queue:
pre: ./hooks/pre-scale-purge.shHere pre-scale-purge.sh runs before the scale action for job-queue (native API or shell kubectl scale — so the pod can still accept exec). Optional post runs after a successful scale (or other main action for that step).
List order defines when each step runs, not automatic “wait until pods are gone.” For down, use a post hook after the upstream workload if the next step must not start until termination finishes:
pipelines:
down:
- deployment.app/consumer:
post: ./hooks/wait-deployment-scale-down.sh
- deployment.app/producerReference hook script: kzero-selfhosted/run/examples/hooks/wait-deployment-scale-down.sh. Full walkthrough: docs/examples/pipeline-order-and-integrity.md.
Waiting between steps on up (Helm --wait in scripts, post on release.*, wait_for_ready on workloads): docs/examples/waiting-between-pipeline-steps.md.
Custom step with hooks (same list item, multiple keys):
- custom: ./hooks/maintenance.sh
pre: ./hooks/before-maintenance.sh
post: ./hooks/after-maintenance.shFound kzero useful? We'd love your help to make it better. You can:
- Report bugs or suggest features — open an issue
- Contribute code — see CONTRIBUTING.md for how to submit a pull request
- Star the repo — it helps others discover kzero
Thanks for using kzero.
See LICENSE.