In case you've got cool friends who share their nodes with you
Find a file
2026-02-25 14:10:54 +04:00
app feat: initial commit 2026-02-25 14:10:54 +04:00
manifests feat: initial commit 2026-02-25 14:10:54 +04:00
tests feat: initial commit 2026-02-25 14:10:54 +04:00
.gitignore feat: initial commit 2026-02-25 14:10:54 +04:00
kustomization.yaml feat: initial commit 2026-02-25 14:10:54 +04:00
pyproject.toml feat: initial commit 2026-02-25 14:10:54 +04:00
README.md feat: initial commit 2026-02-25 14:10:54 +04:00
uv.lock feat: initial commit 2026-02-25 14:10:54 +04:00

DNS Healthcheck + Cloudflare

This setup runs a Kubernetes deployment that:

  • checks DDNS nodes for health on ports 80/443,
  • collects managed hostnames from Ingress/Gateway resources,
  • keeps Cloudflare A records in sync with healthy node IPs,
  • stores cooldown state in a PVC.

Files

  • app/main.py: bootstrap + runtime loop
  • app/reconcilers/domain.py: per-domain reconcile logic
  • app/reconcilers/once.py: one reconcile cycle across domains
  • app/services/*.py: Cloudflare/Kubernetes/nodes/state implementations
  • app/facades.py: Laravel-style facades (Cloudflare, Kubernetes, Nodes)
  • app/di.py: dependency-injector container and bindings
  • pyproject.toml: Python dependency definitions for uv
  • uv.lock: locked dependency graph used by uv (--frozen)
  • manifests/*.yaml: Kubernetes resources
  • kustomization.yaml: assembles resources and script ConfigMap

Configure

  1. Edit node list and timings in manifests/20-config.yaml.
  2. Create the Cloudflare token secret:
kubectl apply -f manifests/00-namespace.yaml
kubectl -n dns-healthcheck create secret generic cloudflare --from-literal=token='your-cloudflare-api-token'

The token needs Zone:DNS:Edit for your zones.

Local dev with uv

Prerequisite: install uv (https://docs.astral.sh/uv/getting-started/installation/).

Install and sync local environment:

uv sync --dev

Run tests:

uv run pytest

Facade fakes are available for tests and local experimentation:

  • Cloudflare.fake(records={...})
  • Kubernetes.fake(hosts={...})
  • Nodes.fake(healthy_ips={...})

Run the checker locally (with env values):

CF_TOKEN=... NODES='friend1: friend1.ddns.net' INTERVAL=30 COOLDOWN=60 uv run python -m app.main

Upgrade dependencies and refresh lock file:

uv lock --upgrade
uv sync --dev --upgrade

Deploy

The deployment runs with uv and installs from pyproject.toml + uv.lock using --frozen, so cluster installs are lockfile-based and reproducible.

kubectl apply -k .

Tail logs:

kubectl -n dns-healthcheck logs -f deploy/dns-healthcheck

Troubleshooting

  • No domains detected: ensure resources have dns-healthcheck/managed: "true".
  • Gateway API route without hostnames (TCP/UDP): set dns-healthcheck/hosts annotation.
  • Cloudflare auth errors: verify the token secret exists in dns-healthcheck namespace and has Zone:DNS:Edit permission.
  • No healthy IPs: verify DDNS names in manifests/20-config.yaml resolve publicly and ports 80 or 443 are reachable.
  • Runtime dependency mismatch in cluster: make sure uv.lock is committed and kubectl apply -k . is re-run after lock changes.

Managed hosts annotations

Mark resources that should be managed by the checker:

  • dns-healthcheck/managed: "true"

Optional explicit host list annotation:

  • dns-healthcheck/hosts: "example.com,api.example.com"

If dns-healthcheck/hosts is not set:

  • Ingress hosts are read from spec.rules[].host
  • HTTPRoute/GRPCRoute/TLSRoute hosts are read from spec.hostnames