Skip to content

feat(deploy): kind validation of the three-tier topology with NetworkPolicy - #465

Merged
Minipada merged 10 commits into
jazzyfrom
feature/452-kind-validation-of-the-three-tier-topolo
Sep 2, 2026
Merged

feat(deploy): kind validation of the three-tier topology with NetworkPolicy#465
Minipada merged 10 commits into
jazzyfrom
feature/452-kind-validation-of-the-three-tier-topolo

Conversation

@Minipada

@Minipada Minipada commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds tools/kind/: a kubeadm cluster (kind) with Calico — a real, policy-enforcing CNI, unlike k3d's default Flannel (k3d cluster for local development iteration #451) — that proves the fleet's security claims by attempted connection rather than by rendering.
  • Stands up a robot tier (site A: dc-ros + its local Vector Shipper), an edge aggregator (site A), a stand-in second site (dc-edge-b), and a hub (Postgres), and asserts via scripts/verify_network_policy.sh and scripts/run.sh:
    • the robot tier has no internet route (a real external address, denied);
    • sites cannot reach each other (both directions, against dc-edge-b);
    • robot -> edge and edge -> hub are permitted (positive controls) and carry real Records collected by dc-ros, arriving at the hub through the edge aggregator;
    • the robot tier keeps collecting and buffering while the edge tier is unreachable — a real NetworkPolicy swap, not a stopped container — and loses nothing once it's restored.
  • Wires it into .github/workflows/ci.yaml as verify-kind-networkpolicy, a pre-merge check separate from the fast colcon test/prek loop, needing only build-dc-ros-image's published image.
  • Docker dependency is scoped to this harness and documented in tools/kind/README.md; building and shipping DC stays on Podman (scripts/load_images.sh routes the dc-ros image through podman save / kind load image-archive, never kind load docker-image).
  • Scopes a check-yaml exclude for tools/kind/kubernetes/ (.pre-commit-config.yaml) since those manifests are intentionally multi-document and already schema-validated by the existing kubeconform hook.

Verification

  • prek run --all-files --skip build-doc: all hooks pass, including kubeconform against every new manifest.
  • reuse lint: compliant.
  • shellcheck / bash -n clean on every new script.
  • Live-verified against a local kind + Calico cluster on this machine (not just rendered): cluster bring-up (up.sh), all manifests applied and became Ready, and every verify_network_policy.sh check passed for real — no-internet, both site-to-site directions, and both positive controls. The edge aggregator's Vector config was confirmed valid and its healthcheck against a real hub Postgres passed; tools/e2e/sql/init.sql's dc_records table was confirmed present.
  • The dc-ros/Records-flow and induced-outage portions of run.sh need a real dc-ros image, which is out of scope to build in this environment (a full ROS workspace build) — CI's build-dc-ros-image job supplies that image the same way verify-robot-manifests already consumes it.

Closes #452

…Policy

Adds tools/kind/, a kind cluster with Calico (a real, policy-enforcing CNI,
unlike k3d's default Flannel) that proves the fleet's security claims by
attempted connection rather than by rendering: the robot tier has no
internet route, sites cannot reach each other, and robot -> edge -> hub
carries real Records collected by dc-ros, including through an induced
edge-tier outage the robot buffers through and recovers from without loss.
Wired into ci.yaml as a pre-merge check (verify-kind-networkpolicy),
separate from the fast colcon test/prek loop.

Live-verified against a local kind+Calico cluster: cluster bring-up, all
NetworkPolicy enforcement checks, and the edge aggregator's Vector config
against a real hub Postgres all pass. The dc-ros/Records-flow path is
exercised the same way verify-robot-manifests already does (CI's
build-dc-ros-image image), not re-built locally.

Closes #452

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDTVLJ2NZ2fa3EKgdRCFDj
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.44%. Comparing base (d9340bb) to head (b77bc4b).
⚠️ Report is 3 commits behind head on jazzy.

Additional details and impacted files
@@           Coverage Diff           @@
##            jazzy     #465   +/-   ##
=======================================
  Coverage   71.44%   71.44%           
=======================================
  Files         124      124           
  Lines        7563     7563           
=======================================
  Hits         5403     5403           
  Misses       2160     2160           
Flag Coverage Δ
cpp-jazzy 71.44% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Minipada and others added 9 commits September 1, 2026 15:35
…hem inline

verify-kind-networkpolicy previously curl-downloaded kind and kubectl on
every run. Adds containers/kind-tools/Containerfile (pinned versions) and a
build-kind-tools-image job; verify-kind-networkpolicy now pulls that image
and podman-cp's the binaries out instead of reinstalling them itself, with
--cache-from/--cache-to making an unchanged pin a cache hit rather than a
rebuild. Records this as a general convention in CLAUDE.md ("Podman-built
images for CI tools, not inline installs") for future CI tooling.

Verified locally: podman build + hadolint clean, and both binaries
extracted from the built image run and report the pinned versions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDTVLJ2NZ2fa3EKgdRCFDj
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
…ookworm

bookworm is the previous stable; trixie is current. Rebuilt and re-verified
locally (podman build, hadolint, and both extracted binaries reporting the
pinned versions) before pushing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDTVLJ2NZ2fa3EKgdRCFDj
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
…cument in mdbook

Per feedback: CI, local dev, and any future reproduction of this validation
should all run the exact same script, never a dev-only convenience path that
can drift from what CI actually asserts. Removes
tools/kind/scripts/{up,down,load_images}.sh and inlines their podman
build/save, kind create/delete, and kubectl commands directly into run.sh —
one script, top to bottom (bring up, verify, tear down), same as
tools/e2e/scripts/run.sh already does for the E2E harness.

Adds doc/src/dc/deploy_kind_networkpolicy.md (mdbook), walking through the
exact same commands run.sh runs, for reading or running by hand without the
script — verified by building the docs site locally (mdbook + linkcheck,
tools/ci/pre-commit/build_doc.sh) and confirming the page renders with no
broken links.

Records the "one script for CI and dev, no up/down pairs" rule in CLAUDE.md
for future harnesses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDTVLJ2NZ2fa3EKgdRCFDj
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
Per feedback: run.sh had become a second place (alongside the mdbook page)
maintaining the same command sequence. Deletes it and inlines every step
directly into ci.yaml's verify-kind-networkpolicy job as its own named
step — cluster create, Calico install, image load, manifest applies,
readiness waits, the NetworkPolicy check, and the induced-outage/record
count check are now literal podman/kind/kubectl commands, not a script
CI happens to call.

verify_network_policy.sh stays a script: it has real control flow (retry
loops, pass/fail assertions across six checks) that duplicating across
CI and docs would actually risk drifting, unlike a fixed one-shot command
sequence with no branching.

doc/src/dc/deploy_kind_networkpolicy.md is updated to match exactly —
every command on that page is now the literal ci.yaml step, no more
references to a script that no longer exists.

Verified: extracted every ci.yaml step's bash into temp files and ran
bash -n + shellcheck on each (clean); rebuilt the mdbook site locally
(no broken links); and re-ran the full command sequence (cluster create
through NetworkPolicy verification) against a live local kind+Calico
cluster — all six checks pass again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDTVLJ2NZ2fa3EKgdRCFDj
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
…harness-only tag

kubernetes/robot-a.yaml and edge-a.yaml previously required ${DC_ROS_IMAGE}/
${VECTOR_IMAGE} to always be substituted, and the docs illustrated that
with an invented local tag (dc-ros:kind) built on the spot — a second,
harness-only way to get dc-ros that diverged from how every other
rendering in this repo gets it.

Both manifests now commit real, working defaults instead, same convention
deploy/robot/scripts/verify_kube_play.sh already uses: dc-ros defaults to
ghcr.io/minipada/ros2_data_collection/dc-ros:jazzy (the floating ref
build-dc-ros-image actually pushes and a real deployment actually pins to
— not :latest, which this repo doesn't push), and vector defaults to the
same literal version robot-pod.yaml pins. CI substitutes DC_ROS_IMAGE only
to test the PR's own just-built image; a plain local run needs no
substitution at all and pulls the exact image a real robot would run.

Verified: podman pull of the real ghcr.io/.../dc-ros:jazzy ref succeeds
(public, no auth needed); kubeconform still validates all manifests; every
ci.yaml step's bash re-checked with bash -n + shellcheck; mdbook rebuilt
locally with no broken links.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDTVLJ2NZ2fa3EKgdRCFDj
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
This repo doesn't push a :latest tag for dc-ros/dc-uploader — :jazzy (the
branch ref build-dc-ros-image/build-dc-uploader-image push on every merge)
is the one a real deployment pins to. Several files still assumed :latest
existed:

- ci.yaml's build-dc-ros-image/build-dc-uploader-image jobs were pushing a
  :latest tag alongside :<sha>/:jazzy on every push to jazzy — removed, so
  CI stops producing a tag this repo doesn't want.
- deploy/robot/{compose.yaml,kubernetes/robot-pod.yaml,quadlet/*.container}
  defaulted to :latest — now default to :jazzy, matching what CI actually
  publishes.
- deploy/robot/scripts/verify_kube_play.sh's sed substitution and docs
  updated to match.
- .github/actions/{image-ref,podman-push}/action.yaml's own descriptions
  corrected — they described the now-removed :latest push.

Verified: podman pull of ghcr.io/.../dc-ros:jazzy succeeds; kubeconform
still validates robot-pod.yaml and every tools/kind manifest; ci.yaml's
every job (not just verify-kind-networkpolicy) re-checked with bash -n;
full prek suite and reuse lint pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDTVLJ2NZ2fa3EKgdRCFDj
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
…s image

Replaces the hand-rolled containers/kind-tools/Containerfile +
build-kind-tools-image job + podman-cp extraction with one step: the
Helm-maintained, widely-used helm/kind-action (pinned to commit
ef37e7f390d99f746eb8b610417061a60e82a6cc / v1.14.0), install_only: true.
It puts pinned kind/kubectl on PATH with sha256 checksum verification
against their own published sums — something the old Containerfile never
did — for two binaries a maintained action already installs correctly, no
custom image to build/push/maintain.

install_only is required, not optional: the action otherwise always runs
`kind create cluster --wait=<duration>`, which blocks for node Readiness.
That can't succeed here before Calico is installed, since
kind-config.yaml's disableDefaultCNI leaves every node NotReady until
then. Cluster creation stays exactly as it was: a plain `kind create
cluster` with no --wait, immediately followed by the Calico apply.

Updates CLAUDE.md's CI-tools rule to prefer a maintained, SHA-pinned
installer action first, falling back to a Podman-built image only when
no such installer exists for the tool.

Verified before landing: read helm/kind-action's actual kind.sh/main.sh
source (not just its README) to confirm the --wait behavior and that
install_only skips cluster creation; resolved the v1 tag to v1.14.0's
commit SHA via the GitHub API; bash -n on every ci.yaml job's embedded
script (not just this one); kubeconform on every touched manifest;
mdbook rebuilt locally with no broken links; full prek + reuse lint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDTVLJ2NZ2fa3EKgdRCFDj
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
…omize

Replaces 7 separate kubectl apply/create-configmap steps (namespaces,
3 imperative ConfigMaps, networkpolicies, hub, edge, probes, robot) with
tools/kind/kustomization.yaml + one `kubectl apply -k tools/kind/` —
kustomize ships in kubectl, no new binary. Its configMapGenerator
replaces two of the three imperative `kubectl create configmap
--dry-run=client | apply` commands; the third (hub-init-sql, sourced from
tools/e2e/sql/init.sql, genuinely outside this tree) stays as-is, since
kustomize refuses by design to read a file outside its own root.

Considered Helm too (raised directly): not a fit here — this harness
deploys one fixed topology every run, nothing to parameterize across
environments, which is Helm's actual value proposition. Documented as
tools/kind/README.md's new "Why not Helm" section, and noted that the
real fleet deployment tooling (deploy/robot/ and beyond) is a different
problem where that combination genuinely fits — tracked separately as
its own architecture question (#466), not folded into this harness.

Verified against a live local kind+Calico cluster: `kubectl kustomize`
renders 19 valid resources (kubeconform-clean), `kubectl apply -k`
creates everything, namespace-first ordering happens automatically, and
critically the ConfigMap hash-suffix reference rewrite actually took
effect at runtime — dc-robot's mounted robot-a-params ConfigMap volume
resolved to the generated robot-a-params-<hash> name, not the plain one.
(An unrelated environmental issue — this long-lived sandbox's
accumulated fs.inotify.max_user_instances exhaustion — kept dc-ros's
vector container from starting cleanly on this run; that is unchanged
pre-existing behavior, not something this refactor touches, and CI's
GitHub-hosted runners start fresh with no such history.)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDTVLJ2NZ2fa3EKgdRCFDj
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
… too

Raises the kustomization root from tools/kind/ to tools/ (the actual
common ancestor of tools/kind/ and tools/e2e/, whose init.sql the
hub-init-sql ConfigMap needs) so kustomize's own load-restriction — it
refuses to read a file outside its root — no longer forces one ConfigMap
to be created imperatively while the other two are generated. All three
are now configMapGenerator entries, and the CI job collapses from three
steps (apply namespaces, create hub-init-sql, apply everything else) to
one: `kubectl apply -k tools/`.

Namespace objects apply before namespaced resources that need them
(kubectl's own apply-time Kind ordering) even when the Namespace and the
ConfigMap targeting it are both generated in the same kustomize build —
verified live, not assumed. The induced-outage restore step now reuses
that same `apply -k tools/` command too, instead of a narrower
`apply -f networkpolicies.yaml` — the command that brings the topology
up is also the one that puts it back.

Verified against a live local kind+Calico cluster: a single
`kubectl apply -k tools/` with no separate namespace step created all 20
resources (kubeconform-clean) with zero ordering errors, and
hub-postgres/edge-vector came up functional (dc_records table present,
reachable).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDTVLJ2NZ2fa3EKgdRCFDj
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
@Minipada
Minipada merged commit 764c65a into jazzy Sep 2, 2026
16 checks passed
@github-actions
github-actions Bot deleted the feature/452-kind-validation-of-the-three-tier-topolo branch September 2, 2026 01:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant