Single-binary CLI that deploys F5 BIG-IP Next for Kubernetes (BNK) 2.3.0 on a two-node kind cluster — one combined control-plane + worker, one worker dedicated to TMM running in demo mode (virtio inside the pod netns; no DPU, no SR-IOV, no Multus).
Aimed at low-spec corporate laptops where dpubnkctl's bare-metal + DPU pipeline is overkill. Same poc.yaml-driven, resume-safe shape; much shorter pipeline.
Drives a BNK deployment in three phases:
- cluster up —
kind create cluster, install Calico (acts as a simulator for larger SR-IOV deployments), create internal + external docker bridge networks and attach both to every kind node container, label the worker for TMM, fetch kubeconfig. - deploy prereqs — namespaces, FAR pull secret, cert-manager.
- deploy flo + cne — FLO from the release-manifest chart at
repo.f5.com, License CR with the operator's JWT, CNEInstance withadvanced.demoMode.enabled: trueand TMM pinned vianodeSelector: app=f5-tmm.
Symmetric destroy unwinds it: bnk-forge unregister → kind delete cluster → docker network rm.
| Component | Version |
|---|---|
| BNK | 2.3.0 |
| CNE release manifest | 2.3.0-3.2598.3-0.0.170 |
| Kubernetes (kind node image) | 1.30.8 (kind v0.26 ships this) |
| Calico | v3.28.2 |
| cert-manager | v1.16.2 |
| FLO chart | resolved at deploy time from the release manifest |
| Cluster floor | With bnk-forge | Free disk | |
|---|---|---|---|
| Linux (host docker) | 12 cores · 24 GB | 14 cores · 26 GB | ~10 GB |
| macOS / Windows Docker Desktop | 12 CPUs · 24 GB allocated to the VM | 14 CPUs · 26 GB | ~10 GB |
(Configured in Docker Desktop → Settings → Resources. Rancher Desktop / Colima use the same numbers — same underlying Linux VM model.)
In the two-node shape every F5 pod lands on demo-worker —
demo-control-plane holds the standard node-role.kubernetes.io/control-plane
taint and the BNK 2.3.0 charts don't tolerate it. The Kubernetes
scheduler admits pods against their requests, not their actual
RSS, and the chart reserves heavily:
| Pod (on the worker) | Memory request | CPU request |
|---|---|---|
| f5-tmm | 9204 Mi | 4100m |
| f5-cne-controller (4 containers) | 1600 Mi | 1080m |
| f5-downloader | 1000 Mi | 500m |
| f5-spk-csrc | 1024 Mi | 500m |
| f5-crdconversion | 1024 Mi | 500m |
| f5-dssm-db / -sentinel | 1152 Mi each | 600m each |
| f5-observer / -receiver | 500 Mi each | 512m / 1c |
| f5-observer-operator | 256 Mi | 250m |
| f5-spk-cwc | 640 Mi | 556m |
| f5-afm | 512 Mi | 500m |
| f5-ipam-ctlr / f5-rabbit | 512 Mi each | 100m / 300m |
| otel-collector / flo | 256 Mi each | 500m / 250m |
| Sum on the worker | ~20 Gi | ~12 cores |
Each kind node container reports the docker daemon's full memory and CPU as its allocatable — kind does not partition. So the worker won't schedule the full stack until the daemon (or Docker Desktop VM) is sized above the request total, plus ~4 Gi headroom for the control-plane pods sharing the same VM and kernel overhead in both node containers.
Steady-state, after CNEInstance.Available=True, the cluster's real
RSS is much smaller than the reservation:
| Component | Working set | CPU |
|---|---|---|
| TMM pod (worker) | ~1.2 GB | ~100m |
| kube-apiserver | ~900 MB | ~150m |
| All other F5 pods (~20) | ~1.0 GB | ~470m |
| Calico + coredns + etcd + kube-* | ~700 MB | ~150m |
| Kernel / runtime per node | ~500 MB × 2 | — |
| Total cluster RSS | ~4.5 GB pod + ~1 GB overhead | ~900m steady, ~1.2c during TMM init |
So the cluster lives inside ~6 GB of real memory once it's up — it just won't get there without first satisfying the K8s scheduler's ~20 Gi worker reservation.
kindbnkctl e2e reaches [5/5] deploy-cne and stalls. Six pods
(f5-dssm-db-0, f5-dssm-sentinel-0, f5-spk-cwc-*,
f5-observer-{0,receiver-0,operator-*}) sit Pending with
FailedScheduling: Insufficient memory on the worker, and
CNEInstance.Available never goes true. Quick check:
kubectl --kubeconfig <poc>/artifacts/kubeconfig describe node demo-worker \
| grep -E "Allocatable:|Allocated resources:" -A6If memory Requests is ≥99% of Allocatable, raise the docker
daemon allocation and re-run from the failed phase (kindbnkctl deploy cne …) — it's idempotent.
1.4 GB (kindest/node image) + ~2.4 GB (F5 container images pulled to the worker) + ~0.5 GB (cert-manager, alpine/k8s tooling, manifests) + ~5 GB headroom for kind cluster state and logs.
kindbnkctl doctor reports the host's actual CPU count and fails
when it falls below MinBaseline. Override the constants in
internal/version/version.go if you've tuned chart values to reduce
requests.
If a local bnk-forge
clone exists at ~/git/bnk-forge (or $KINDBNKCTL_BNK_FORGE_PATH)
when kindbnkctl init runs, the new PoC's bnk_forge: block is
pre-filled and enabled. On cluster up, kindbnkctl best-effort
registers the kind cluster with bnk-forge — if the local bnk-forge
stack isn't running, the auto-hook logs a clean skip and continues.
kindbnkctl never installs or starts bnk-forge for you. If it's
configured but not running, bring it up manually (cd ~/git/bnk-forge && make deploy) then kindbnkctl bnk-forge launch to register
after the fact.
Prebuilt binaries for each tagged release are on the
GitHub Releases page —
three archives per release plus a checksums.txt:
| Platform | Archive |
|---|---|
| Linux (Intel/AMD) | kindbnkctl_<version>_linux_amd64.tar.gz |
| Linux (ARM64) | kindbnkctl_<version>_linux_arm64.tar.gz |
| macOS (Apple Silicon) | kindbnkctl_<version>_darwin_arm64.tar.gz |
One-liner install (Linux amd64; swap the suffix for your platform):
VERSION=$(curl -fsSL https://api.github.com/repos/mwiget/kindbnkctl/releases/latest | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
curl -fsSL "https://github.com/mwiget/kindbnkctl/releases/download/${VERSION}/kindbnkctl_${VERSION#v}_linux_amd64.tar.gz" \
| tar -xz -C /tmp kindbnkctl
sudo install -m 0755 /tmp/kindbnkctl /usr/local/bin/kindbnkctl
kindbnkctl versionReleases follow v<bnk-version>-<n> — e.g. v2.3.0-1, v2.3.0-2.
The 2.3.0 prefix tracks the pinned BNK release; the -n suffix
increments per kindbnkctl-only iteration.
Or build from source — see Repo layout below.
| Tool | Why |
|---|---|
| Docker or Podman | kind runs Kubernetes nodes as containers; FLO + cert-gen also shell into an alpine/k8s:1.31.5 container at deploy time |
| kind | cluster bring-up (default backend) |
| k3d (optional) | alternative backend, selected via the k3dbnkctl symlink (see Cluster backend) |
| kubectl | cluster reads/writes (apply, wait, label) |
| helm | cert-manager + FLO install, release-manifest pull |
| git (optional) | init git-inits the PoC repo (skippable with --no-git) |
Verify after install:
kindbnkctl doctorWhat customers supply themselves, dropped into keys/ of the PoC repo
(delivered through F5's normal channels):
- FAR tarball — image-pull credentials for
repo.f5.com - JWT — TEEM activation token
kind is the default backend. k3d (k3s-in-docker) is available as an
option, selected by the binary's own name: symlink k3dbnkctl to the
installed kindbnkctl and invoke the symlink to drive k3d instead.
ln -sf kindbnkctl ~/.local/bin/k3dbnkctl
k3dbnkctl cluster up --poc demo --yolo --confirm-cluster demo # uses k3d
kindbnkctl cluster up --poc demo --yolo --confirm-cluster demo # uses kindBoth backends build the same two-node, Calico-CNI, k8s-v1.30.8 shape and
share the entire BNK deploy pipeline. Warm, k3d brings the cluster up
~18 % faster and idles ~200 MiB lighter (5 pods vs 12). Full
measurements and the trade-offs are in
docs/kind-vs-k3d.md. (Requires the k3d CLI on
PATH; k3dbnkctl doctor checks for it.)
# 1. Create a fresh PoC repo. Auto-detects ~/git/bnk-forge.
kindbnkctl init demo --customer "Acme"
cd demo
# 2. Drop the operator-supplied files into keys/.
cp /path/to/f5-far-auth-key.tgz keys/
cp /path/to/license.jwt keys/.jwt
# 3. Confirm poc.yaml is clean.
kindbnkctl validate
# 4. Run the pipeline (~10–20 min with a warm docker cache).
kindbnkctl e2e --yolo --confirm-cluster demo
# 5. Tear down (symmetric):
kindbnkctl destroy --yolo --confirm-cluster demoIf you'd rather drive the phases one at a time for diagnostics:
kindbnkctl cluster up --yolo --confirm-cluster demo
kindbnkctl deploy prereqs --yolo --confirm-deploy demo
kindbnkctl deploy flo --yolo --confirm-deploy demo
kindbnkctl deploy cne --yolo --confirm-deploy demoEvery phase is idempotent and gated by --yolo plus a typo-guard.
cmd/kindbnkctl/ main entrypoint
internal/cli/ cobra commands (init, validate, doctor, cluster,
deploy, destroy, e2e, bnk-forge, version)
internal/poc/ poc.yaml schema + I/O
internal/cluster/ kind + docker wrappers
internal/deploy/ cert-manager, FLO, License CR, CWC cert-gen
internal/bnkforge/ bnk-forge HTTP client (copy-fork of dpubnkctl)
internal/embedded/ go:embed AGENTS.md, CLAUDE.md, templates/
internal/version/ build-stamped + BNK 2.3.0 pins + min-spec floor
poc.yaml declarative state — source of truth
AGENTS.md operator + agent guide
CLAUDE.md @AGENTS.md include
journal/ append-only markdown log
artifacts/ rendered kind.yaml, kubeconfig, helm values, CWC certs
keys/ gitignored — FAR tgz + JWT live here
.gitignore excludes all secret material
The shape after a full e2e plus bgp-peer-frr (everything the
other scenarios build on). One docker bridge on the host (the
kind cluster's own); two kind node containers; a Multus-managed
Linux bridge inside the worker carries BGP traffic between TMM
and the FRR helper pod. Scenario backends are plain Calico pods
— the Gateway IPs they serve get plumbed via BGP, so the
backends don't need to be on the NAD themselves.
+----------------------------------------------------------------------------+
| HOST (Linux or macOS Docker Desktop) |
| |
| docker bridge: kind 172.18.0.0/16 |
| | |
+-------|--------------------------------------------------------------------+
|
+-------+--------------+ +-------------------------------------------------+
| smoke-control-plane | | smoke-worker (kind node container) |
| (kind node container)| | label: app=f5-tmm |
| eth0 172.18.0.2 | | eth0 172.18.0.3 |
| | | |
| pods: | | +-------------------------------------------+ |
| Calico Multus | | | TMM pod ns=default app=f5-tmm | |
| FLO CWC | | | 6 containers: | |
| cert-manager | | | f5-tmm | |
| ... | | | f5-tmm-routing (= ZeBOS) | |
+----------------------+ | | debug blobd toda-observer ipsec | |
| | Interfaces: | |
| | net1 192.168.99.X/24 Multus NAD | |
| | (BGP source, no eth0 hook) | |
| | eth0 10.244.x.x/32 Calico (kube-API | |
| | + ZeBOS bgpd kernel listener) | |
| | xeth0 no IP Calico veth #2, TMM | |
| | userspace raw frames | |
| | tmm 169.254.0.253/24 virtio, pod | |
| | default route to TMM DP | |
| | tunl0 DOWN Calico IPIP placeholder | |
| +-------------------------------------------+ |
| +-------------------------------------------+ |
| | FRR pod ns=scn-bgp app=scn-frr | |
| | 1 container: frr (zebra + bgpd) | |
| | net1 192.168.99.Y/24 Multus NAD | |
| | (BGP peer + curl source) | |
| | eth0 10.244.x.x/32 Calico | |
| +-------------------------------------------+ |
| ^ |
| | BGP TCP/179 + scenario curls |
| v over br-bnk-bgp, L2 |
| +========================================+ |
| || br-bnk-bgp Linux bridge in node || |
| || netns, created by the bridge-CNI || |
| || plugin via NAD name=bnk-bgp ; || |
| || host-local IPAM 192.168.99.20-250 || |
| || on /24 || |
| +========================================+ |
| |
| +-------------------------------------------+ |
| | scenario backends (plain Calico pods — | |
| | no NAD attachment, no node pinning) | |
| | nginx ns=scn-httproute-e2e | |
| | pp-backend ns=scn-proxy | |
| | ext-backend ns=scn-extres (Pool | |
| | member references its Calico podIP) | |
| +-------------------------------------------+ |
| |
| DaemonSets in node netns: |
| Calico-node Multus thick |
| f5-coremond (if how-to #4 ran) |
+-------------------------------------------------+
BGP session:
TMM/ZeBOS AS 65000 ======= net1 <-> net1, L2 over br-bnk-bgp =======> FRR AS 65001
listen-range
192.168.99.0/24
peer-group
from-tmm
TMM ZeBOS advertises (redistribute kernel, at router-bgp scope —
silently dropped if placed inside address-family ipv4):
192.168.99.0/24 (net1 connected)
203.0.113.100/32 Gateway scn-gateway (http-routing-e2e)
203.0.113.101/32 Gateway scn-extres-gw (external-resource-pool)
203.0.113.102/32 Gateway scn-proxy-gw (proxy-protocol-l4)
FRR installs each /32 as a kernel route:
203.0.113.100/32 via 192.168.99.X dev net1 proto bgp
so any client in the FRR pod can curl the Gateway addresses
end-to-end via the NAD bridge, completely bypassing TMM's eth0
TCP hook. This is what http-routing-e2e and external-resource-pool
rely on for their data-plane assertions.
Key knob: CNEInstance.spec.advanced.tmm.env TMM_MAPRES_ADDL_VETHS_ON_DP=FALSE
is set by bgp-peer-frr. With this TRUE (TMM's default for
demoMode), mapres grabs net1 for the userspace data plane and
flushes its kernel IP — ZeBOS then has nothing to source-bind
to. Flipping it FALSE lets net1 stay a normal Linux interface
with its NAD-assigned IP so the kernel TCP stack handles BGP
traffic ordinarily.
After e2e brings the cluster up, drive named test scenarios against
it. Each scenario maps to one F5 how-to article (or sub-article) and
exercises a slice of BNK functionality end-to-end: render manifests
into artifacts/scenarios/<name>/, apply them, assert reconciled
state, write a JSON+md report under reports/<timestamp>/scenarios/.
kindbnkctl scenario list # all known scenarios + rating
kindbnkctl scenario run http-routing --poc ./demo # apply + verify + report
kindbnkctl scenario run http-routing --dry-run # render manifests only
kindbnkctl scenario clean http-routing # delete what was appliedRating is a stable hint about what's testable in the 2-node / demo-TMM shape:
| Rating | Meaning |
|---|---|
| green | fully testable here |
| amber | partially testable — control-plane verifies, data-plane plumbing missing |
| red | requires real DPUs / BGP peers / etc.; listed for discoverability, never executed |
Scoring of the F5 BNK how-tos index:
| # | How-to | Rating | Scenario | Wall time |
|---|---|---|---|---|
| 1 | Restrict access to sensitive data | 🟢 | cwc-admin-access |
9s |
| 2 | Components needing cluster-wide access | 🟢 | cluster-wide-watch |
4s |
| 3 | Set up dynamic routing with BGP | 🟢 | bgp-peer-frr |
3m19s |
| 4 | Set up core file collection | 🟢 | core-file-collection |
3m01s |
| 6 | Configure Token Counting and Enforcement | 🟢 | ai-token-counting |
25s |
| 7 | Semantic AI Model Caching (sub-article of AI Traffic Optimization) | 🟢 | ai-semantic-cache |
22s |
| 8 | HTTP traffic steering with Gateway API HTTPRoute | 🟢 | http-routing-e2e |
21s |
| 9 | Proxy Protocol iRule support for L4 routes | 🟢 | proxy-protocol-l4 |
24s |
| 10 | Load Balance Traffic to External Resources | 🟢 | external-resource-pool |
14s |
Plus four scenarios drawn from the BNK Use-Cases / CRD pages rather than the how-tos index:
| Use-case | Rating | Scenario |
|---|---|---|
| Dynamic IP address allocation (FIC for Gateway API) | 🟡 | fic-dynamic-ip |
TCP load balancer (L4Route protocol=TCP, weighted backends) |
🟢 | tcp-l4-loadbalance |
UDP load balancer (L4Route protocol=UDP, socat echo) |
🟢 | udp-l4-loadbalance |
gRPC routing (GRPCRoute, grpcbin backend, grpcurl client) |
🟡 | grpc-loadbalance |
fic-dynamic-ip (🟡): manifest-side configuration applies cleanly
(F5BnkGateway, Gateway w/ infrastructure.parametersRef, HTTPRoute)
but Gateway never reaches Programmed=True. f5-cne-controller logs
"No IPAM found for Gateway" — the F5BnkGateway pool isn't auto-
converted into IPAM/IPAMRange CRs in this BNK 2.3.0 demo
deployment. The scenario asserts the control-plane state and
surfaces the AddressNotAssigned condition as informational.
grpc-loadbalance (🟡): GRPCRoute reconciles, Gateway reaches
Programmed=True, BGP route propagates, grpcurl installs SHA-
verified, and a direct grpcurl-to-backend Service call lists
all gRPC services successfully. But cleartext gRPC traffic
through the Gateway returns RST_STREAM(INTERNAL_ERROR) — TMM's
standard HTTP/json/httprouter profile chain (visible in audit
logs) breaks gRPC framing. Investigation confirmed TMM
unconditionally applies profile-http + profile-json +
profile-httprouter to all listener types (HTTP and HTTPS),
corrupting HTTP/2 binary frames regardless of TLS termination.
Setting appProtocol: kubernetes.io/h2c on the Service, switching
to an HTTPS listener on port 443 with TLS, and adding profile-sbi
(all verified via TMM audit logs) did not change the outcome. This
is a BNK 2.3.0 FLO limitation. Fix needs either a "raw HTTP/2
passthrough" mode for GRPCRoute listeners, or a BNK profile
override path not yet exposed through the Gateway API CRDs.
Wall times measured on a fresh e2e (cluster destroy + redeploy)
running 2026-05-21 on a Linux laptop. The two TMM-restarting
scenarios (bgp-peer-frr + core-file-collection) dominate at
~3 minutes each; the others are tens of seconds because they
either don't touch TMM or piggyback on the bridge already up.
kindbnkctl scenario run --all runs every green-rated scenario
in topo-sorted dependency order, writing an aggregate
reports/<stamp>/run.{json,md} summary alongside the per-scenario
JSONs.
Cluster bring-up itself (kindbnkctl e2e) is 5m44s:
validate 0s · cluster-up 48s · deploy-prereqs 20s · deploy-flo
24s · deploy-cne 4m12s (includes waiting on bnk-gatewayclass
to reach Accepted=True — required to keep first-run scenario
Gateways from being marked-for-deletion by the controller).
How-tos #5 (DOCA Offloads on DPU), #11 (Static Active-Standby Interface Bonding), and #12 (TMOS DNS Service Integration with CIS) are omitted from the table because they require resources kind can't provide: DPU silicon (#5), bondable physical NICs (#11), and a real upstream BIG-IP GTM box (#12). They remain valid BNK features outside the kindbnkctl shape.
Ratings are assigned only after a scenario is built and run.
Implemented scenarios that pan out land as 🟢; ones that
hit a real architectural barrier on kind+demoMode get 🟡
with the gap documented in the scenario's Description().
Empty cell = scenario not yet built.
bgp-peer-frr (green) deploys a real BGP session between an FRR
pod and TMM's ZeBOS daemon, peered over a Multus
NetworkAttachmentDefinition (bridge CNI) on a per-node Linux
bridge. The NAD path bypasses TMM's eth0 TCP hook entirely —
BGP rides net1 in both pods, exchanging prefixes via the bridge.
Six assertions pass: Multus DaemonSet Ready, both pods have net1
in the 192.168.99.0/24 NAD range, ZeBOS sees the neighbor, BGP
session Established, and FRR's BGP table has at least one prefix
learned from TMM (via redistribute kernel).
http-routing-e2e (green) — depends on bgp-peer-frr for the
NAD plumbing. Applies a GatewayClass + Gateway (static
spec.addresses=203.0.113.100) + HTTPRoute + nginx backend.
TMM's ZeBOS (via redistribute kernel) advertises 203.0.113.100/32
into BGP; FRR installs the kernel route via net1; the verify
step execs 5 curls from inside the FRR pod, which already has
the route. All 6 assertions pass including the 5×curl. Path:
FRR socket → FRR kernel route → net1 → bnk-bgp bridge → TMM net1
→ Gateway listener → nginx. TMM's eth0 TCP hook is completely
bypassed.
Reproduce manually:
kubectl -n scn-bgp exec deploy/scn-frr -c frr -- \
curl -sS -H 'Host: kindbnkctl.local' http://203.0.113.100/
# → kindbnkctl-scenario-httproute-e2e-OKexternal-resource-pool (green) — demonstrates how-to #10 (load
balance to non-Service backends) via the BNK Pool CR. HTTPRoute
backendRefs points at a Pool {group:k8s.f5net.com, kind:Pool}
instead of a Service; Pool.spec.members lists endpoints by
IP+port. On kind, the "external" backend is an nginx pod attached
to the bnk-bgp NAD (same bridge TMM uses), with its NAD IP
auto-discovered and rendered into the Pool CR. Gateway address
is 203.0.113.101 to avoid collision with http-routing-e2e.
cwc-admin-access (green) — implements how-to #1 (restrict access
to sensitive data). Demonstrates BNK's dual-gate access control
on the CWC admin API: mTLS at the TLS layer + bearer token at
the HTTP layer. Both materials are produced by the deploy-flo
phase already (cwc-license-client-certs Secret + cwc-auth-token
Secret in f5-cne-core); the scenario just replicates them into
its own namespace, spawns a curl probe pod, and runs three
requests against https://f5-spk-cwc.f5-cne-core.svc:38081/status:
authenticated (expect 200 + license JSON), no Authorization
header (expect 401 "invalid token format"), bogus token
(expect 401 "invalid token"). Independent of bgp-peer-frr —
this is a pure runtime-access check.
proxy-protocol-l4 (green) — implements how-to #9 (PROXY-protocol
iRule on an L4 route). The new BNK CRs reconcile (F5BigCneIrule
Programmed, L4Route Accepted, BNKNetPolicy ResolvedRefs True),
TMM proxies the TCP traffic, FRR learns the Gateway IP via BGP,
and 5/5 curls from FRR through the Gateway return the marker body
with the parsed proxy_addr set to FRR's NAD IP — proving the
iRule's TCP::respond prepended the PROXY v1 line before nginx
saw the request. Load-bearing knob: L4Route.spec.pvaAccelerationMode: disabled, which keeps the data path in TMM's TCL slow path. With
the default full/assisted PVA mode, TMM hardware-offloads the
connection after handshake and TCP::respond fires in the VM but
can't reach the offloaded wire — symptoms are 200 OK from nginx
turning into "broken header" errors and curl (52) Empty reply.
core-file-collection (green) — implements how-to #4 (set up
core file collection). One-line CNEInstance.spec.coreCollection.
enabled=true flip plus advanced.coremon.hostPath=true so the
CoreMond DaemonSet survives kind's single-node-RWO storage class.
FLO auto-creates a CoreMond CR + DaemonSet in f5-cne-core and
adds kernel-cores / f5-core-store / tmm-core volumes to the TMM
Deployment template. The scenario asserts the CR exists, the
DaemonSet is Running, and the CNEInstance condition
CoremondAvailable=True. The how-to's "kill -11 to force a
crash" verification step is intentionally NOT automated —
crashing TMM mid-scenario destabilises the cluster, and the
follow-up "did a core file land in /var/crash" check needs a
privileged node-level read we'd rather not bake in. Operators
can run the kill manually after the scenario and inspect the
kind worker container's filesystem to confirm capture.
A complete e2e --with-scenarios report from a clean cluster
is checked in at
examples/reports/run-demo-2026-06-03T06-47-53Z.md
so a reader can see the full report shape (versions, host
resources, cluster topology, F5 control-plane pods, every deploy
phase, and every scenario row) without running anything locally.
Reproduce on your own host with:
kindbnkctl e2e \
--poc <pocdir> \
--yolo --confirm-cluster <pocname> \
--with-scenarios \
--no-resumeOutput lands at <pocdir>/reports/<stamp>/run-<pocname>-<stamp>.md
(plus the JSON twin, per-phase logs under logs/, and
per-scenario JSONs under scenarios/).
The checked-in report ran 16m42s end-to-end: ~6m deploy
(validate → cluster-up → deploy-prereqs/flo/cne) plus ~11m
running 12 green scenarios topo-sorted by dependency order
(the one amber scenario — fic-dynamic-ip — is skipped by
--all and must be run explicitly).
make test # Go unit tests (poc, deploy, cluster, scenarios)
make smoke # unit tests + Layer A CLI smoke (no cluster required, ~5s)make smoke is the gate to run before pushing — it covers the
non-cluster-dependent surface area in one shot.
- dpubnkctl — the
bare-metal + DPU sister tool. kindbnkctl is a copy-fork:
internal/poc,internal/cluster,internal/cliare rewritten for the kind path;internal/bnkforge,internal/deployare forked verbatim with minor adjustments (local kubectl/helm instead of containerized). - f5-bnk-udf
(branch
v2.2.0) — the inspiration for the BNK-on-host shape:advanced.demoMode.enabled: true+ node label + nodeSelector, ZeBOS dynamic-routing ConfigMap pattern, multi-worker topology. Same CNEInstance recipe family; kindbnkctl adapts it to a two-node kind cluster with Multus NADs replacing the macvlan-on-bare-metal approach used in f5-bnk-udf.