diff --git a/internal/apply/apply.go b/internal/apply/apply.go index f50c470..5936b66 100644 --- a/internal/apply/apply.go +++ b/internal/apply/apply.go @@ -812,12 +812,12 @@ func applyContainer(ctx context.Context, r *declaration.Container, run Runner) ( for _, v := range r.Volumes { args = append(args, "--volume", v) } - for _, n := range r.Nameservers { - // Per container rather than by changing 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 one place - // it would be hardest to see. - args = append(args, "--dns", n) + for _, h := range r.Hosts { + // Written into the container's own hosts file by the runtime. 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. + args = append(args, "--add-host", h) } args = append(args, r.Image) args = append(args, r.Args...) diff --git a/internal/apply/apply_test.go b/internal/apply/apply_test.go index 88d1bf1..01e587e 100644 --- a/internal/apply/apply_test.go +++ b/internal/apply/apply_test.go @@ -1231,7 +1231,7 @@ func TestAFailedActionStopsWhatFollows(t *testing.T) { // `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) { +func TestAContainerIsGivenTheMeshsNames(t *testing.T) { var ran []string run := func(_ context.Context, name string, args ...string) (string, error) { if name != "docker" { @@ -1250,25 +1250,23 @@ func TestAContainerIsToldWhichResolverToUse(t *testing.T) { } d := parseTrusted(t, `{"declaration":1,"resources":[ {"id":"app","type":"container","name":"app","image":"`+pinned+`", - "nameservers":["10.42.0.1"]} + "hosts":["anchor.internal: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" { + if a == "--add-host" && i+1 < len(ran) && ran[i+1] == "anchor.internal:10.42.0.1" { told = true } } if !told { - t.Fatalf("the container was not told where to resolve names: %v", ran) + t.Fatalf("the container cannot reach another machine by name: %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) { +// And a container given no names is run exactly as before. +func TestAContainerGivenNoNamesIsRunAsBefore(t *testing.T) { var ran []string run := func(_ context.Context, name string, args ...string) (string, error) { if name != "docker" { @@ -1291,8 +1289,8 @@ func TestAContainerToldNothingIsRunAsBefore(t *testing.T) { _, _, _ = 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) + if a == "--add-host" { + t.Fatalf("a container given no names was given some anyway: %v", ran) } } } diff --git a/internal/declaration/declaration.go b/internal/declaration/declaration.go index 3092d17..5b235ad 100644 --- a/internal/declaration/declaration.go +++ b/internal/declaration/declaration.go @@ -328,18 +328,22 @@ type Container struct { Ports []string `json:"ports,omitempty"` Volumes []string `json:"volumes,omitempty"` Args []string `json:"args,omitempty"` - // Nameservers this container resolves through. + // Names this container can reach, as `name:address`. // // **Because 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 this machine is invisible to the thing 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. + // holding only its own hostname, so every internal name the mesh wrote for this machine is + // invisible to the thing 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. // - // Set by the mesh rather than by a module: which resolver a machine has is a fact about the - // machine, and a module that named one would be a module that only runs where somebody put - // that resolver. - Nameservers []string `json:"nameservers,omitempty"` + // **A file rather than a resolver, which is the decision the mesh already made about names** + // and this extends rather than overturns: it works on every runtime, needs no package, and + // has no failure mode of its own. A resolver becomes necessary when names are wanted that are + // not one-per-node — service names, wildcards — and that is still not true. + // + // Set by the mesh, not by a module: which machines exist is a fact about the mesh, and a + // module that listed them would be a module that goes stale when one joins. + Hosts []string `json:"hosts,omitempty"` // Network is the container's network, passed to the runtime unchanged. //