Pin resolvers on the two services that reach the internet - #33
Merged
Merged
Conversation
Docker copies the host's /etc/resolv.conf into a container once, at creation,
and never refreshes it: not on restart, not on reboot. There is no loopback
stripping for network_mode: host, correctly, because the container shares the
host's network namespace. So a container created while that file was still
wrong keeps a dead resolver for its entire life.
That stranded five boards. Their telemetry containers were created in the
seconds before NetworkManager wrote a usable resolv.conf, so they pointed at
127.0.0.53, where nothing listens on these boards. The hosts resolved fine
throughout. The boards checked in to Mender, ran blah2, collected detections
and reported status active while sending nothing at all, and every check that
ran outside the container reported them healthy. Sacremento sat like that for
five days. See ClickUp 86cba42d8.
Setting dns here makes Docker write the file from these values and ignore the
host's copy, so creation timing stops mattering. Docker records it in the file
it writes, as "Overrides: [nameservers]".
Only two services resolve anything off-box: retina-telemetry for api.retina.fm
and retina-spectrum for tower-finder.retina.fm. Everything else talks over
127.0.0.1, and tar1090 is on the bridge network where Docker's embedded
resolver tracks the host file dynamically and never had this problem.
Verified on ret824685c9 with Compose v5.1.3, against the real deployed
manifest:
- renders as [1.1.1.1, 8.8.8.8, 2606:4700:4700::1111], and under the
spectrum profile for retina-spectrum
- NODE_DNS_* overrides render correctly and mix with the defaults
- with the host's resolv.conf set to the exact broken content from the
incident, a host-network container created without dns: inherits
127.0.0.53 and cannot resolve, while retina-telemetry recreated with this
block resolves normally
NODE_DNS_* is overridable in .env for standalone deployments only. On a Mender
node config-merger regenerates the manifests .env wholesale on every run, so a
value added there by hand does not survive. A site that blocks outbound DNS to
public resolvers needs config-merger taught to emit these first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the stale-container-resolver fault behind ClickUp 86cba42d8.
The fault
Docker copies the host's
/etc/resolv.confinto a container once, at creation, and never refreshes it: not on restart, not on reboot. There is no loopback stripping undernetwork_mode: host, correctly, because the container shares the host's network namespace. A container created while that file was still wrong therefore keeps a dead resolver for its entire life.Five boards were in that state. Their telemetry containers had been created in the seconds before NetworkManager wrote a usable
resolv.conf, so they pointed at127.0.0.53, where nothing listens on these boards. The hosts resolved fine throughout. The boards checked in to Mender, ran blah2, collected detections and reportedstatus: activewhile sending nothing, and every check running outside the container reported them healthy. Sacremento sat like that for five days.A single board showed it cleanly. Same host, same daemon, containers created at different moments:
retina-telemetry127.0.0.53adsb2dd127.0.0.53tar1090127.0.0.11blah2-api192.168.1.1Boot was 11:32:29. NetworkManager wrote the correct file at 11:32:53. Only the clock differed.
The change
dns:on the two services that resolve anything off-box:retina-telemetryforapi.retina.fm,retina-spectrumfortower-finder.retina.fm. Those are the only external URLs in the compose. Everything else talks over127.0.0.1, andtar1090is on the bridge network where Docker's embedded resolver tracks the host file dynamically and never had this problem.Docker then writes the container's
resolv.conffrom these values and ignores the host's copy, so creation timing stops mattering. It records this in the file itself, asOverrides: [nameservers].Three entries, because glibc reads at most three nameservers and ignores the rest. Two providers rather than two addresses from one, so a single provider outage doesn't take fleet telemetry with it.
Verification
On
ret824685c9with Compose v5.1.3, against the real deployed manifest:[1.1.1.1, 8.8.8.8, 2606:4700:4700::1111], and the same forretina-spectrumunder its profile.NODE_DNS_*overrides render correctly and mix with the remaining defaults.resolv.confset to the exact broken content from the incident, a host-network container created withoutdns:inherits127.0.0.53and cannot resolve, whileretina-telemetryrecreated with this block resolves normally. The fault reproduces without the change and does not occur with it.The board's telemetry error count dropped from 4 to 1, the survivor being a server-side
400: tx_lat, so it is reaching the API.Note that CI does not validate the compose file (ruff, vulture and the config-merger suite only), so the hardware check above is the evidence for this one.
Known limitation
NODE_DNS_*is overridable in.envfor standalone deployments only. On a Mender node, config-merger regenerates the manifests.envwholesale on every run, so a value added there by hand does not survive. A site that blocks outbound DNS to public resolvers needs config-merger taught to emit these first. Documented in both files rather than left to be discovered.🤖 Generated with Claude Code