Skip to content

Add DNS-seeder prober with its own deploy pipeline - #18

Draft
andres-pcg wants to merge 4 commits into
mainfrom
feat/dns-seeder-prober
Draft

Add DNS-seeder prober with its own deploy pipeline#18
andres-pcg wants to merge 4 commits into
mainfrom
feat/dns-seeder-prober

Conversation

@andres-pcg

Copy link
Copy Markdown
Contributor

Summary

The DNS-seeder prober — the detection layer for the 2026-05-28 incident class
(seeders handing out dead peer IPs while DNS resolves fine) — plus its own
dedicated deploy pipeline.

Why

A recursive DNS check only sees "did it resolve?", which is exactly why the old
monitoring stayed green during the near-stall. The prober instead:

  • enumerates each seeder's authoritative nameservers (ns1..nsN) by iterative
    resolution from the root,
  • queries each nameserver directly for the seeder's A records,
  • handshakes the returned IPs (Zcash version/verack) to confirm they're live
    nodes, and
  • flags a nameserver whose live ratio diverges from its siblings (robust to
    the node-side rate-limiting that depresses everything uniformly).

It hardcodes no IPs — it checks the live seeder output, so it can't drift the
way the Corefile bootstrap list does.

What's here

  • prober/ — Go tool. One-shot CLI and --serve mode exposing /status
    (200 / 503), /results (JSON), / (table). Reuses dnsseeder's own handshake
    (pinned, with the btcd Zcash-fork replace). Unit-tested. Bruno collection for
    local poking under prober/bruno/.
  • prober/Dockerfile — distroless, non-root.
  • .github/workflows/cd-deploy-prober-dev.ymldedicated pipeline (build on
    the runner via buildx → deploy to Cloud Run). Kept separate from the Kuma
    deploy because the prober has no DB / secrets / Cloud SQL / VPC.
  • docs/dns-seeder-resilience.md — root-cause analysis + upstream hardening recs.

Deployment shape

Always-on Cloud Run: --min-instances=1 --no-cpu-throttling (background probe
loop), --execution-environment=gen2 (UDP egress for the iterative DNS),
outbound-only. Uptime Kuma watches /status (/healthz is reserved by Google
Front End on *.run.app).

Prerequisites for the pipeline (TF, separate)

The pipeline reuses the existing WIF + SAs: build impersonates artifact-publisher
(has artifactregistry.writer), deploy impersonates instance-deployer (has
run.admin + serviceAccountUser). No Cloud Build needed. Nothing new required
beyond what's already granted.

Status

Already deployed to dev manually and verified (Kuma polls /status every 60s,
200). This PR adds the source of truth + automation so it deploys on change.

The 2026-05-28 near-stall came from seeders handing out dead peer IPs while DNS
resolved fine -- invisible to a recursive DNS check. The prober enumerates each
seeder's authoritative nameservers, queries each one directly, and handshakes the
returned IPs (Zcash version/verack) to confirm they are live nodes, flagging a
nameserver that diverges from its siblings. It hardcodes no IPs (it checks the
live seeder output, so it can't drift), runs as a small always-on Cloud Run
service exposing /status for Uptime Kuma, and ships a dedicated pipeline kept
separate from Kuma (no DB/secrets/VPC). Includes the root-cause + hardening doc.
Prod counterpart of cd-deploy-prober-dev.yml: builds and deploys the
always-on prober to Cloud Run on a published release, using the prod
environment vars.
- Dockerfile: declare USER nonroot:nonroot explicitly (checkov CKV_DOCKER_3) and
  skip CKV_DOCKER_2 — Cloud Run ignores Docker HEALTHCHECK and the service is
  health-checked via /livez + Kuma polling /status (distroless has no shell).
- ci-lint-codebase: disable super-linter's Go validators (it runs golangci-lint
  from the repo root, but the module is in prober/, so it can't resolve imports
  and reports false positives) and add a dedicated Go job that runs gofmt, vet
  and tests with the module context.
- README: 'repo' -> 'repository' (terminology lint).
…ndshakes

Pinned to v0.4.0 the prober advertised 170140 (NU6.1) while the fleet serves at
170150 (NU6.2), so every peer refused it: 0 live on all six nameservers with TCP
to :8233 fine. It reported that as INCONCLUSIVE and /healthz stayed 200, which
is the blind spot the tool exists to remove.

Adds DOWN(probe) for records resolving run-wide with no peer handshaking
anywhere. Judged run-wide because probing one target faster than its cooldown
can zero that target on its own, which is expected and stays soft.
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.

2 participants