diff --git a/cookbooks/base/Makefile b/cookbooks/base/Makefile index ce05908..7fb20ef 100644 --- a/cookbooks/base/Makefile +++ b/cookbooks/base/Makefile @@ -2,8 +2,13 @@ SYSTEMD_MAIN_UNIT_NAMES := var-lib-virtiofs-data.mount SYSTEMD_MAIN_UNIT_NAMES += rpm-ostree-install-qemu-guest-agent.service SYSTEMD_MAIN_UNIT_NAMES += install-tools.service +# .path units are not picked up by common.mk's wildcard (only .service/.target/ +# .timer/.mount), so install and enable/start it explicitly. It triggers the +# path-scoped podman-resolv-conf-sync.service oneshot. +SYSTEMD_MAIN_UNIT_NAMES += podman-resolv-conf-sync.path TARGET_FILES += $(TARGET_CHROOT)/etc/quadlets/base/install-tools.d +TARGET_FILES += $(TARGET_CHROOT)/etc/systemd/system/podman-resolv-conf-sync.path # Include common Makefile include ../../scripts/common.mk diff --git a/cookbooks/base/README.md b/cookbooks/base/README.md index 14979b4..11b49aa 100644 --- a/cookbooks/base/README.md +++ b/cookbooks/base/README.md @@ -8,6 +8,12 @@ The base cookbook provides foundational configuration for Fedora CoreOS systems. - **tmpfiles configuration**: Sets up required directories such as `/var/lib/virtiofs/data`. - **QEMU guest agent**: Optional installation for better VM integration. - **SSH key persistence**: Backs up and restores SSH host keys across reboots. +- **resolv.conf resync**: Keeps `Network=host` containers in sync with the host's DNS. + With `Network=host`, Podman snapshots `/etc/resolv.conf` at container creation and bind-mounts + the copy, so a later DNS change (e.g. a DHCP renewal) never reaches running containers. A + `systemd.path` unit watches `/etc/resolv.conf` and a oneshot service rewrites each host-network + container's snapshot **in place** (same inode, no restart); bridged containers are left untouched. + See [issue #1](https://github.com/nmasse-itix/podman-quadlet-cookbook/issues/1). This cookbook is used as an implicit dependency for other cookbooks that run on Fedora CoreOS. diff --git a/cookbooks/base/config/podman-resolv-conf-sync.sh b/cookbooks/base/config/podman-resolv-conf-sync.sh new file mode 100755 index 0000000..a9d2237 --- /dev/null +++ b/cookbooks/base/config/podman-resolv-conf-sync.sh @@ -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 diff --git a/cookbooks/base/podman-resolv-conf-sync.path b/cookbooks/base/podman-resolv-conf-sync.path new file mode 100644 index 0000000..12442ed --- /dev/null +++ b/cookbooks/base/podman-resolv-conf-sync.path @@ -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 diff --git a/cookbooks/base/podman-resolv-conf-sync.service b/cookbooks/base/podman-resolv-conf-sync.service new file mode 100644 index 0000000..9539023 --- /dev/null +++ b/cookbooks/base/podman-resolv-conf-sync.service @@ -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