usage: vexa-preflight [-h] --namespace NAMESPACE [--manifests MANIFESTS] [--snapshot SNAPSHOT]
[--dump-snapshot DUMP_SNAPSHOT] [--kubeconfig KUBECONFIG]
[--context CONTEXT] [--bot-profile {default,none}] [--live-probes]
[--registry-host REGISTRY_HOST] [--pull-test-image PULL_TEST_IMAGE]
[--pull-timeout PULL_TIMEOUT] [--probe-image PROBE_IMAGE] [--json]
vexa-preflight — cluster conformance preflight for a private channel.
Answers, BEFORE first sync and on every upgrade, in plain language: will this
cluster run what the channel delivers? Every check is anchored to a failure
observed in the field — clusters carry taints, LimitRanges and admission
policies we do not control, and the first sync is the wrong moment to learn
that.
Checks
P1 taints-tolerations every delivered workload can schedule on ≥1 node
(anchor: addon pods stranded 6h by a cluster-wide
custom taint; Argo's own pods Pending on tainted
nodes)
P2 limitranges every container declares requests+limits, and the
declared values fit the namespace LimitRange
(anchor: a LimitRange squeezed undeclared bot pods to
64Mi; sizing env vars were dead code for a month)
P3 resourcequota quota headroom covers the declared sums
P4 pod-security OpenShift SCC restricted-v2: explicit UIDs/fsGroups
outside the namespace range are REJECTED (mutation
fixes the rest); PSA restricted: runAsNonRoot,
seccomp, drop-ALL required (anchor: an OpenShift
readiness review — the hardened workloads are exactly
the SCC-rejected ones)
P5 networkpolicy default-deny detection, DNS egress, registry
reachability (static + optional live probe)
P6 shm the bot's 2Gi Memory-medium /dev/shm counts against
its memory limit; LimitRange max and node allocatable
must fit it (anchor: 2Gi shm against a 2560Mi limit is
production's actual configuration)
P7 image-pull the cluster can actually pull a release image by
digest with the namespace's pull secrets (anchor:
images "pulled" only because nodes had them cached;
a real install fails here)
P8 storage a default StorageClass exists if the manifests carry
PVCs
P9 kubernetes version sanity floor
EIGHT of those nine run by default: P1-P6, P8, P9. The two LIVE probes are
opt-in because they create a pod in your cluster — the live half of P5 needs
--live-probes --registry-host, and P7 needs --live-probes --pull-test-image.
P7 also needs the namespace's image-pull Secret to exist already, which an
install writes; run it AFTER the install, not as the before-anything check.
Modes
live (default) reads the cluster via kubectl; --live-probes adds
the in-cluster probe pods (P5 live, P7)
--snapshot FILE air-gapped: read a snapshot produced by
--dump-snapshot on a connected workstation
Workloads under test come from --manifests (rendered YAML; converted via
`kubectl create --dry-run=client -o json`, so kubectl is the only dependency)
plus the built-in dynamic-bot profile (bots are spawned per meeting and never
appear in a chart render; sizes are the measured production values,
overridable).
Exit codes
0 every check that ran passed (warnings included), and every check ran
1 at least one FAIL — this is the fails-closed case
2 usage error
4 nothing failed AND at least one check was NOT EVALUATED (see below)
A READ THIS CREDENTIAL IS REFUSED IS NOT A FINDING. Every cluster-scoped read
is wrapped, and a check whose input could not be read reports UNKNOWN, naming
the read and the refusal — never a traceback, never a PASS, never a FAIL. A
namespace-scoped tenant cannot list nodes; saying so is the honest answer, and
`--dump-snapshot` as an admin then `--snapshot` as the tenant is the way to
turn an UNKNOWN into an answer.
WHICH IS WHY UNKNOWN HAS ITS OWN EXIT CODE. It used to share 0 with a clean
run, so a caller — a script, a CI job, `install.sh` — could not tell "your
cluster is fine" from "four of these were never looked at", and the second one
read as the first. 4 is not a failure and does not mean stop; it means the
answer is incomplete and says which parts. Callers that must not proceed on an
incomplete answer now have something to test.
options:
-h, --help show this help message and exit
--namespace NAMESPACE
target namespace for the delivered workloads
--manifests MANIFESTS
rendered manifests (file or dir) of the delivered set
--snapshot SNAPSHOT read cluster state from a snapshot file (air-gapped mode)
--dump-snapshot DUMP_SNAPSHOT
write the live cluster snapshot to a file and exit
--kubeconfig KUBECONFIG
--context CONTEXT
--bot-profile {default,none}
include the dynamic bot pod profile (absent from renders) in the checks
--live-probes run in-cluster probe pods (DNS/registry, image pull)
--registry-host REGISTRY_HOST
channel registry host[:port] for the reachability probe
--pull-test-image PULL_TEST_IMAGE
image ref (by digest) for the pull probe
--pull-timeout PULL_TIMEOUT
--probe-image PROBE_IMAGE
--json