Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cf55fc1
Add LocalDNS Corefile hotfix DaemonSet
kwaksaewon Aug 7, 2026
b389e4e
Keep LocalDNS hotfix DaemonSet reconciling
kwaksaewon Aug 7, 2026
7d4d54c
Avoid restarting localdns from hotfix DaemonSet
kwaksaewon Aug 7, 2026
3ff8143
Add CoreDNS reload plugin to LocalDNS hotfix
kwaksaewon Aug 7, 2026
e061478
Add VnetDNS forward health check to LocalDNS hotfix
kwaksaewon Aug 7, 2026
8d877e8
Document LocalDNS hotfix patching layers
kwaksaewon Aug 7, 2026
4a9fbec
Clarify LocalDNS CoreDNS systemd wording
kwaksaewon Aug 7, 2026
d99fb9b
Clarify LocalDNS hotfix README caveats
kwaksaewon Aug 7, 2026
2e317b4
Limit failfast to external LocalDNS forward blocks
kwaksaewon Aug 7, 2026
a1e93ee
Preserve original LocalDNS hotfix backups
kwaksaewon Aug 7, 2026
2b7111e
Clarify LocalDNS patch README wording
kwaksaewon Aug 8, 2026
d3db58e
Explain LocalDNS environment Corefile flow
kwaksaewon Aug 8, 2026
ae4e3ce
Use patch terminology in LocalDNS DaemonSet comments
kwaksaewon Aug 8, 2026
534c7df
Rename LocalDNS patch DaemonSet artifacts
kwaksaewon Aug 8, 2026
26285b4
Remove node-local template patching
kwaksaewon Aug 8, 2026
7d3b058
Add health check to KubeDNS external forward blocks
kwaksaewon Aug 8, 2026
6892db3
Target LocalDNS patch directives to default server blocks
kwaksaewon Aug 8, 2026
48ed35e
Preserve existing LocalDNS failfast directives
kwaksaewon Aug 8, 2026
cf8a2c0
Simplify LocalDNS default server directive patching
kwaksaewon Aug 8, 2026
708011d
Comment LocalDNS default server patch logic
kwaksaewon Aug 8, 2026
b1223d6
Restart LocalDNS after patching Corefile
kwaksaewon Aug 8, 2026
db0f61b
Restart LocalDNS once after patching
kwaksaewon Aug 8, 2026
14d3315
Clarify LocalDNS patch DaemonSet comments
kwaksaewon Aug 8, 2026
1009099
Add LocalDNS Corefile rollback DaemonSet
kwaksaewon Aug 10, 2026
0c817a6
Verify LocalDNS rollback removes patch directives
kwaksaewon Aug 10, 2026
11bc771
Comment LocalDNS rollback DaemonSet flow
kwaksaewon Aug 10, 2026
a561262
Harden LocalDNS rollback verification
kwaksaewon Aug 10, 2026
925c66c
Prevent LocalDNS patch and rollback DaemonSet conflicts
kwaksaewon Aug 10, 2026
22f60f3
Clarify LocalDNS DaemonSet guard comments
kwaksaewon Aug 10, 2026
293008d
Reset LocalDNS patch restart marker on rollback
kwaksaewon Aug 10, 2026
8425f85
Use live process PIDs for LocalDNS active markers
kwaksaewon Aug 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions examples/localdns/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# DaemonSet to deliver private patches to the LocalDNS configuration

This directory contains a DaemonSet that applies temporary LocalDNS Corefile
patches on existing AKS Linux nodes.

It also contains a separate rollback DaemonSet that restores the pre-patch
backups created by the patch DaemonSet.

## Why this is needed

The LocalDNS Corefile is originally generated by AgentBaker during node
bootstrap. For normal bootstrap config generation, the template comes from the
AgentBaker service, not from a template file on the already-created node.

The simplified flow is:

```text
AgentBaker service template
-> bootstrap/CSE payload
-> node writes /etc/localdns/environment
-> localdns.sh decodes /etc/localdns/environment into localdns.corefile when localdns starts
-> localdns.sh copies/updates it into updated.localdns.corefile
-> LocalDNS (the CoreDNS binary run by the node's localdns systemd unit) starts with updated.localdns.corefile
```

After a node is created, restarting the node's `localdns` systemd unit does not
call AgentBaker again. Instead, `localdns.sh` reads the Corefile payload already
stored on that node. Because of that, this DaemonSet patches the node-local files
that `localdns.sh` actually uses at runtime.

More specifically, `/etc/localdns/environment` contains environment variables
whose values are base64-encoded Corefile text. On `localdns` service start,
`localdns.sh` selects one of those payloads, decodes it, and writes:

```text
/opt/azure/containers/localdns/localdns.corefile
```

Then `localdns.sh` copies/processes that file into:

```text
/opt/azure/containers/localdns/updated.localdns.corefile
```

Finally, the node's `localdns` systemd unit starts the CoreDNS binary with:

```text
coredns -conf /opt/azure/containers/localdns/updated.localdns.corefile
```

This is why patching `/etc/localdns/environment` is required for restart
persistence. If only `updated.localdns.corefile` is patched, a later
`systemctl restart localdns` can recreate it from an older base64 value in the
environment file.

## Files patched by the DaemonSet

### 1. `/etc/localdns/environment`

This is the most important persistence point for an existing node.

`localdns.sh` regenerates `/opt/azure/containers/localdns/localdns.corefile`
from base64 values stored in this file whenever `localdns` starts. The DaemonSet
patches these variables when present:

```text
LOCALDNS_COREFILE_BASE
LOCALDNS_COREFILE_WITH_HOSTS
LOCALDNS_BASE64_ENCODED_COREFILE
```

If this file is not patched, a later `systemctl restart localdns` can decode the
previous base64 payload and recreate a Corefile without the intended patch.

### 2. `localdns.corefile` and `updated.localdns.corefile`

These are the materialized Corefile files on the node:

```text
/opt/azure/containers/localdns/localdns.corefile
/opt/azure/containers/localdns/updated.localdns.corefile
```

`localdns.corefile` is the decoded base Corefile.

`updated.localdns.corefile` is the file passed to LocalDNS, which is the
CoreDNS binary run by the node's `localdns` systemd unit:

```text
coredns -conf /opt/azure/containers/localdns/updated.localdns.corefile
```

The DaemonSet patches these files so the node-local files on disk are correct
immediately and so the next LocalDNS/CoreDNS start uses the fixed content. This
does not by itself guarantee that an already-running CoreDNS process has loaded
the new content; see [Restart behavior](#restart-behavior).

## Directives added

The DaemonSet adds the following CoreDNS directives where needed:

```text
reload 10s
prefer_udp
health_check 5s
failfast_all_unhealthy_upstreams
```

`reload 10s` is added to each default `.:53` server block so CoreDNS can
automatically reload future Corefile changes.

`prefer_udp` is added inside the `forward` plugin for each default `.:53` server
block when `ADD_DEFAULT_SERVER_PREFER_UDP` is set to `"true"`. Set that variable
to `"true"` only when the customer requested `Protocol: PreferUDP` for the
default LocalDNS overrides. The DaemonSet does not add it to blocks that already
use `force_tcp`.

`health_check 5s` is added inside the `forward` plugin for each default `.:53`
server block. The DaemonSet does not add it to `cluster.local` /
ClusterCoreDNS-forwarded blocks, custom suffix blocks such as `wmt:53`, or
blocks that already use `force_tcp`.

`failfast_all_unhealthy_upstreams` is added inside the `forward` plugin for each
default `.:53` server block. It is intentionally not added to `cluster.local` /
CoreDNS-forwarded blocks or custom suffix blocks such as `wmt:53`.

## Restart behavior

The DaemonSet runs:

```text
systemctl restart localdns
```

only once on a node after the first reconcile pass that actually changes
LocalDNS files.

Patching `updated.localdns.corefile` alone is not enough for an already-running
CoreDNS process to pick up changes unless that process was already started with
the CoreDNS `reload` plugin present in its Corefile.

Therefore:

- The first successful patch pass restarts `localdns` once so the running
CoreDNS process immediately loads the patched Corefile.
- The DaemonSet writes a node-local restart marker after that successful restart.
- Later reconcile loops do not restart `localdns` again on that node, even if
they repair regenerated files.
- After the restart, `reload 10s` can pick up future Corefile edits without
another restart.

The DaemonSet keeps reconciling files every 60 seconds.

## Idempotency

Each reconcile pass writes patched output to a temporary file and compares it
with the existing file. It only writes when content differs.

When a file is changed, the previous content is backed up with a
`.pre-localdns-patch` suffix.

## Rollback

Use `localdns-corefile-rollback-ds.yaml` to undo the patch on nodes where the
patch DaemonSet created `.pre-localdns-patch` backups.

Delete the patch DaemonSet before applying the rollback DaemonSet:

```bash
kubectl -n kube-system delete ds localdns-corefile-patch --ignore-not-found=true
kubectl apply -f localdns-corefile-rollback-ds.yaml
```

Do not run the patch DaemonSet and rollback DaemonSet at the same time. If both
are running, they can fight over the same LocalDNS files.

Both DaemonSets also write node-local active markers and refuse to reconcile if
the opposite DaemonSet is active on the same node:

```text
/opt/azure/containers/localdns/.localdns-corefile-patch-active
/opt/azure/containers/localdns/.localdns-corefile-rollback-active
```

The markers contain the host PID of the active DaemonSet shell. If a marker is
stale because that process exited, the next reconcile pass removes it.

The rollback DaemonSet requires all three `.pre-localdns-patch` backups before
it restores anything, so it does not partially roll back a node:

```text
/etc/localdns/environment
/opt/azure/containers/localdns/localdns.corefile
/opt/azure/containers/localdns/updated.localdns.corefile
```

This restores the original pre-patch content exactly, including removing
`reload 10s` if it was introduced by the patch DaemonSet.

If it restores any file, it restarts `localdns` so the running CoreDNS process
loads the restored Corefile. Rollback does not use a one-time restart marker:
because rollback restores the original Corefile and may remove `reload 10s`,
each later repair must restart `localdns` again to ensure the running process
loads the restored file.

Rollback also removes the patch DaemonSet's one-time restart marker:

```text
/opt/azure/containers/localdns/.localdns-corefile-patch-restarted
```

This ensures a later patch reapply on the same node performs its required
one-time `localdns` restart.

Each reconcile pass also verifies rollback by checking that all three files
match their backups exactly and that the restored `updated.localdns.corefile`
does not contain patch directives:

```text
reload 10s
prefer_udp
health_check 5s
failfast_all_unhealthy_upstreams
```

After rollback is verified, delete the rollback DaemonSet:

```bash
kubectl -n kube-system delete ds localdns-corefile-rollback --ignore-not-found=true
```
Loading
Loading