Browse Source
With Network=host, Podman snapshots /etc/resolv.conf at container creation and bind-mounts the copy, so a later DNS change (DHCP renewal, renumbering, router replacement) never reaches running containers: they keep querying the old, now-dead resolvers until recreated, silently. Add a host-level mechanism in the base cookbook: - a systemd.path unit watching /etc/resolv.conf (catches any writer, including atomic rename-into-place); - a oneshot service running a dedicated script that rewrites each Network=host container's snapshot in place (same inode, so the bind-mount stays valid). Rewriting the content in place is picked up at runtime with no restart by both glibc (reloads on mtime change) and musl (stateless, re-reads every lookup), verified empirically. Scoped to NetworkMode=host only: bridged containers legitimately point at aardvark-dns and must not be touched. Fixes #1 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>fix/network-host-resolv-conf
5 changed files with 74 additions and 0 deletions
@ -0,0 +1,43 @@ |
|||
#!/bin/bash |
|||
# |
|||
# Resync the host's /etc/resolv.conf into the frozen snapshot that Podman |
|||
# bind-mounts into every Network=host container. |
|||
# |
|||
# Why: with Network=host, Podman copies /etc/resolv.conf at container *creation* |
|||
# time into .../userdata/resolv.conf and bind-mounts that copy. It is a snapshot, |
|||
# not a view: when the host's DNS servers change (DHCP renewal, renumbering, |
|||
# router replacement, ...), every running container keeps querying the old, |
|||
# now-dead resolvers — silently, until it is recreated. |
|||
# |
|||
# Fix: overwrite the snapshot's *content* in place (same inode, so the bind-mount |
|||
# stays valid). Both glibc (reloads when the file's mtime changes) and musl |
|||
# (stateless, re-reads on every lookup) then pick up the new resolvers at runtime |
|||
# with no restart and no downtime. |
|||
# |
|||
# Scope: Network=host containers ONLY. Bridged containers legitimately point at |
|||
# aardvark-dns (e.g. nameserver 10.89.0.1), which already follows the host's DNS |
|||
# live; overwriting their resolv.conf would break inter-container name resolution. |
|||
|
|||
set -Eeuo pipefail |
|||
|
|||
host_resolv=/etc/resolv.conf |
|||
|
|||
for id in $(podman ps -q); do |
|||
# Only touch containers that share the host network stack. |
|||
mode="$(podman inspect --format '{{.HostConfig.NetworkMode}}' "$id" 2>/dev/null)" || continue |
|||
[ "$mode" = host ] || continue |
|||
|
|||
snapshot="$(podman inspect --format '{{.ResolvConfPath}}' "$id" 2>/dev/null)" || continue |
|||
[ -n "$snapshot" ] && [ -f "$snapshot" ] || continue |
|||
|
|||
if ! cmp -s "$host_resolv" "$snapshot"; then |
|||
name="$(podman inspect --format '{{.Name}}' "$id" 2>/dev/null || echo "$id")" |
|||
echo "Refreshing resolv.conf for container ${name} (${snapshot})" |
|||
# In-place truncation: preserves the inode the container has bind-mounted. |
|||
if ! cat "$host_resolv" > "$snapshot"; then |
|||
echo "Failed to refresh resolv.conf for container ${name}!" >&2 |
|||
fi |
|||
fi |
|||
done |
|||
|
|||
exit 0 |
|||
@ -0,0 +1,12 @@ |
|||
[Unit] |
|||
Description=Watch /etc/resolv.conf and resync it into Network=host containers |
|||
Documentation=https://github.com/nmasse-itix/podman-quadlet-cookbook/issues/1 |
|||
|
|||
[Path] |
|||
# Fires on any writer (NetworkManager, manual edit, ...), including atomic |
|||
# rename-into-place, since systemd also watches the parent directory. |
|||
PathChanged=/etc/resolv.conf |
|||
Unit=podman-resolv-conf-sync.service |
|||
|
|||
[Install] |
|||
WantedBy=multi-user.target |
|||
@ -0,0 +1,8 @@ |
|||
[Unit] |
|||
Description=Resync /etc/resolv.conf into running Network=host Podman containers |
|||
Documentation=https://github.com/nmasse-itix/podman-quadlet-cookbook/issues/1 |
|||
# Triggered by podman-resolv-conf-sync.path whenever /etc/resolv.conf changes. |
|||
|
|||
[Service] |
|||
Type=oneshot |
|||
ExecStart=/etc/quadlets/base/podman-resolv-conf-sync.sh |
|||
Loading…
Reference in new issue