Commit 9b084c3
authored
Add NodeLocal DNSCache as a base component (#548)
**Impact:** All clusters — every node gets a per-node DNS cache
DaemonSet
**Risk:** medium
Part of the migration: #544
## What
Deploys [NodeLocal
DNSCache](https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/)
(NLD) as a base Kubernetes component on every OSDC cluster. Each node
runs a local CoreDNS instance that intercepts pod DNS queries via
iptables NOTRACK rules, caching responses and forwarding cache misses to
cluster CoreDNS over persistent TCP.
## Why
High-concurrency CI runners generate significant DNS traffic (pip
installs, container pulls, API calls). All of it currently funnels
through the cluster-wide kube-dns CoreDNS pods, creating a centralized
bottleneck. NLD absorbs the bulk of repeated lookups at the node level,
reducing cluster CoreDNS QPS, eliminating conntrack overhead for DNS,
and lowering per-pod DNS latency.
## How
- **iptables-mode (not `--skip-teardown`)**: NLD binds the kube-dns
ClusterIP on a dummy `nodelocaldns` interface and installs NOTRACK
iptables rules so pods' DNS traffic (destined for the kube-dns
ClusterIP) is silently intercepted by the local cache. No pod or kubelet
config changes needed.
- **`deploy.sh` with runtime substitution**: The kube-dns ClusterIP
varies per cluster (dynamic service CIDR). `deploy.sh` resolves it at
apply time and sed-substitutes the `__KUBE_DNS_CLUSTER_IP__` placeholder
in rendered manifests. A Python splitter applies Services before the
DaemonSet to ensure kubelet injects `KUBE_DNS_UPSTREAM_SERVICE_HOST` env
into node-cache pods.
- **`kube-dns-upstream` Service**: A second Service fronting the same
kube-dns pods, giving NLD a separate ClusterIP to forward cache misses
to without creating an iptables loop through itself.
- **No memory/CPU limits**: OOMKill orphans iptables rules (cluster-wide
DNS degradation); CPU throttling adds DNS latency.
`system-node-critical` priorityClass prevents eviction.
- **`automountServiceAccountToken: false`**: The binary makes no API
calls; disabling the token reduces credential-leak surface on a
privileged container.
- **Two metrics ports**: `:9253` (CoreDNS plugin metrics — cache hits,
forward latency) and `:9353` (binary-emitted `coredns_nodecache_*`
counters including `setup_errors_total`). Both scraped via a single
PodMonitor with cardinality-reducing `metricRelabelings` (drop `go_*`,
`process_*`, histogram buckets).
- **First-deploy idempotency**: `deploy.sh` detects whether
`kube-dns-upstream` already exists. On first deploy it triggers a
rollout restart so pods pick up the upstream Service env var; on re-runs
it skips the restart.
## Changes
**Base component (`base/kubernetes/nodelocaldns/`)**
- `configmap.yaml` — Corefile with four server blocks (cluster.local,
in-addr.arpa, ip6.arpa, catch-all), `force_tcp` for cluster zones,
external queries forwarded to `/etc/resolv.conf`
- `daemonset.yaml` — DaemonSet with hostNetwork, privileged,
system-node-critical, tolerations for all OSDC node taints (GPU,
node-fleet, instance-type, git-cache-not-ready), image
`k8s-dns-node-cache:1.26.8`
- `upstream-service.yaml` — `kube-dns-upstream` Service selecting
kube-dns pods (NLD forward target)
- `metrics-service.yaml` — headless `node-local-dns-metrics` Service for
PodMonitor discovery (ports 9253 + 9353)
- `serviceaccount.yaml` — dedicated SA with
`automountServiceAccountToken: false`
- `kustomization.yaml` — Kustomize overlay for the component
- `deploy.sh` — orchestrates: precondition check → ClusterIP resolution
→ kustomize render → sed substitution → Services-first apply → DaemonSet
apply → conditional rollout restart
**Deployment integration**
- `justfile` — `deploy-base` recipe calls `nodelocaldns/deploy.sh` after
image-cache-janitor
- `base/kubernetes/kustomization.yaml` — comment noting nodelocaldns
uses its own deploy.sh (not inline kustomize)
**Monitoring**
- `podmonitors/nodelocaldns.yaml` — PodMonitor scraping both metrics
ports at 60s, dropping `go_*`, `process_*`, and histogram buckets
- `nodelocaldns-alerts.yaml` — three PrometheusRule alerts:
`NodeLocalDNSSetupErrors` (critical, iptables failures),
`NodeLocalDNSPodRestarting` (warning), `NodeLocalDNSDaemonSetDegraded`
(warning, unavailable pods >15m)
- Alert and monitor kustomizations updated
**Tests**
- `test_base_kubernetes.py` — three new smoke tests: DaemonSet healthy
on all nodes, metrics service is headless, upstream service selects
kube-dns pods
**Documentation**
- `architecture.md` — base resources list and deploy sequence updated
- `observability.md` — PodMonitor table and alert table updated
- `CLAUDE.md` — `osdc-nodelocaldns` skill reference added
## Notes
- **Soak gate**: After initial deploy, monitor
`coredns_nodecache_setup_errors_total` and cache hit rate for 24-48h
before considering NLD stable. The `NodeLocalDNSSetupErrors` alert
(critical) will fire immediately if iptables rule installation fails on
any node.
- **Rolling update at 1% maxUnavailable**: Conservative to avoid many
nodes simultaneously losing DNS interception during upgrades.
- **External DNS bypass**: The catch-all `.:53` block forwards to
`/etc/resolv.conf`, so external lookups (e.g., `download.pytorch.org`)
bypass cluster CoreDNS entirely — they go through the node cache but
miss → VPC resolver, not kube-dns.
## Testing
```
============================================================
OSDC Integration Test Results
============================================================
Cluster: arc-staging (pytorch-arc-staging)
Date: 2026-05-09 01:27 UTC
PR Workflow Jobs:
✓ test-pypi-cache-defaults success
✓ test-cpu-arm64 success
✓ test-pypi-cache-action-cuda success
✓ test-cpu-x86-amx success
✓ test-pypi-cache-action-cpu success
✓ test-gpu-t4-multi success
✓ test-cpu-x86-avx512 success
✓ test-cache-enforcer success
✓ test-release-arm64 success
✓ test-gpu-t4 success
✓ test-harbor success
✓ test-git-cache success
✓ build-amd64 / build success
✓ build-arm64 / build success
Smoke ⊘ SKIPPED
Compactor ⊘ SKIPPED
Overall: PASSED
============================================================
```
```
============================================================================================================================ test session starts ============================================================================================================================
platform darwin -- Python 3.13.12, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/jschmidt/meta/ci-infra-code-review/osdc
configfile: pyproject.toml
plugins: anyio-4.12.1, xdist-3.8.0, cov-7.0.0
16 workers [235 items]
......................................................s.................................................................................................s............................................s..................................... [100%]
========================================================================================================================== short test summary info ==========================================================================================================================
SKIPPED [1] modules/arc-runners/tests/smoke/test_capacity_parity.py:300: no scale sets with proactive_capacity > 0
SKIPPED [1] modules/cache-enforcer/tests/smoke/test_cache_enforcer.py:107: No cache-enforcer pods desired (no runner nodes in cluster)
SKIPPED [1] modules/monitoring/tests/smoke/test_monitoring.py:173: No dcgm-exporter pods found (no GPU nodes)
================================================================================================================= 232 passed, 3 skipped in 92.41s (0:01:32) =================================================================================================================
Smoke tests completed in 1m34s
```
```
18:19 aws:308535385114 12 ~ meta ci-infra-code-review osdc jeanschmidt/dnscache_daemonset_2 ? ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ kubectl get ds node-local-dns -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
node-local-dns 2 2 2 2 2 <none> 13m
18:19 aws:308535385114 12 ~ meta ci-infra-code-review osdc jeanschmidt/dnscache_daemonset_2 ? ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ kubectl get pods -n kube-system -l k8s-app=node-local-dns -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
node-local-dns-8g5km 1/1 Running 0 13m 10.0.60.247 ip-10-0-60-247.us-west-1.compute.internal <none> <none>
node-local-dns-r8pbv 1/1 Running 0 13m 10.0.89.138 ip-10-0-89-138.us-west-1.compute.internal <none> <none>
```
---------
Signed-off-by: Jean Schmidt <contato@jschmidt.me>1 parent 5cadb9c commit 9b084c3
22 files changed
Lines changed: 614 additions & 14 deletions
File tree
- osdc
- base/kubernetes
- nodelocaldns
- tests/smoke
- docs
- modules/monitoring/kubernetes
- alerts
- monitors
- podmonitors
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| 58 | + | |
58 | 59 | | |
59 | 60 | | |
60 | 61 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
0 commit comments