A container can be told where to resolve names

A container does not inherit the machine's names. It gets its own /etc/hosts
holding its own hostname, and a runtime rewrites resolv.conf — so every
internal name the mesh wrote for that machine is invisible to what the machine
is running.

That was hit for real, in the lab: a database client on one node could not
resolve another node, on a mesh where both names were correct and present on
both machines. It was worked around by resolving on the host and passing an
address, which is the kind of workaround that should not be needed twice.

A field on an existing shape, not a ninth shape — the vocabulary is still the
eight the count asserts.

Per container rather than by editing the machine's resolver configuration: that
file belongs to something else on most machines, and a host that edited it
would be fighting whatever owns it on every boot — the fault this host exists
to avoid, in the place it would be hardest to see.

A container told nothing is run exactly as before. Most containers should
resolve whatever the machine resolves, and passing an empty flag would be a
change of behaviour dressed up as a default.
This commit is contained in:
2026-08-31 11:09:49 +02:00
parent 8fcfa88fe0
commit 0e2b288bb6
3 changed files with 92 additions and 0 deletions
+72
View File
@@ -1224,3 +1224,75 @@ func TestAFailedActionStopsWhatFollows(t *testing.T) {
t.Errorf("the message does not say the rest was not tried: %v", err)
}
}
// A container is told which resolver to use, because it does not inherit the machine's names.
//
// A container gets its own `/etc/hosts` holding its own hostname, and a runtime rewrites
// `resolv.conf` — so every internal name the mesh wrote for the machine is invisible to what the
// machine is running. That was hit for real: a database client on one node could not resolve
// another node, on a mesh where both names were correct and present on both machines.
func TestAContainerIsToldWhichResolverToUse(t *testing.T) {
var ran []string
run := func(_ context.Context, name string, args ...string) (string, error) {
if name != "docker" {
return "", errors.New("not installed")
}
switch args[0] {
case "info":
return "29.0.0\n", nil
case "inspect":
return "false\t\n", errors.New("no such container")
case "run":
ran = args
return "deadbeef\n", nil
}
return "", nil
}
d := parseTrusted(t, `{"declaration":1,"resources":[
{"id":"app","type":"container","name":"app","image":"`+pinned+`",
"nameservers":["10.42.0.1"]}
]}`)
_, _, _ = Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
var told bool
for i, a := range ran {
if a == "--dns" && i+1 < len(ran) && ran[i+1] == "10.42.0.1" {
told = true
}
}
if !told {
t.Fatalf("the container was not told where to resolve names: %v", ran)
}
}
// And a container that was told nothing is run exactly as before: most containers resolve
// whatever the machine resolves, and passing an empty flag would be a change of behaviour
// dressed as a default.
func TestAContainerToldNothingIsRunAsBefore(t *testing.T) {
var ran []string
run := func(_ context.Context, name string, args ...string) (string, error) {
if name != "docker" {
return "", errors.New("not installed")
}
switch args[0] {
case "info":
return "29.0.0\n", nil
case "inspect":
return "false\t\n", errors.New("no such container")
case "run":
ran = args
return "deadbeef\n", nil
}
return "", nil
}
d := parseTrusted(t, `{"declaration":1,"resources":[
{"id":"app","type":"container","name":"app","image":"`+pinned+`"}
]}`)
_, _, _ = Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
for _, a := range ran {
if a == "--dns" {
t.Fatalf("a container that was told nothing was given a resolver anyway: %v", ran)
}
}
}