Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Append extra_hosts from all tasks #11074

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/11074.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
docker: Append `extra_hosts` values from all tasks to `/etc/hosts`.
```
2 changes: 1 addition & 1 deletion drivers/docker/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,7 @@ func (d *Driver) createContainerConfig(task *drivers.TaskConfig, driverConfig *T
// that comes from the pause container
if task.NetworkIsolation != nil && driverConfig.NetworkMode == "" {
etcHostMount, err := hostnames.GenerateEtcHostsMount(
task.AllocDir, task.NetworkIsolation, driverConfig.ExtraHosts)
task.Name, task.AllocDir, task.NetworkIsolation, driverConfig.ExtraHosts)
if err != nil {
return c, fmt.Errorf("failed to build mount for /etc/hosts: %v", err)
}
Expand Down
60 changes: 36 additions & 24 deletions drivers/shared/hostnames/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,27 @@ import (
"github.com/hashicorp/nomad/plugins/drivers"
)

const baseHostsFile = `# this file was generated by Nomad
127.0.0.1 localhost
::1 localhost
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

# this entry is the IP address and hostname of the allocation
# shared with tasks in the task group's network
%s %s
`

// GenerateEtcHostsMount writes a /etc/hosts file using the network spec's
// hosts configuration, and returns a mount config so that task drivers can
// bind-mount it into the resulting task's filesystem. The extraHosts
// parameter is expected to be the same format as the extra_hosts field from
// the Docker or containerd drivers: []string{"<hostname>:<ip address>"}
func GenerateEtcHostsMount(taskDir string, conf *drivers.NetworkIsolationSpec, extraHosts []string) (*drivers.MountConfig, error) {
func GenerateEtcHostsMount(taskName string, taskDir string, conf *drivers.NetworkIsolationSpec, extraHosts []string) (*drivers.MountConfig, error) {
if conf == nil || conf.Mode != drivers.NetIsolationModeGroup {
return nil, nil
}
Expand All @@ -26,23 +41,26 @@ func GenerateEtcHostsMount(taskDir string, conf *drivers.NetworkIsolationSpec, e
}

var content strings.Builder
fmt.Fprintf(&content, `# this file was generated by Nomad
127.0.0.1 localhost
::1 localhost
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

# this entry is the IP address and hostname of the allocation
# shared with tasks in the task group's network
%s %s
`, hostsCfg.Address, hostsCfg.Hostname)
path := filepath.Join(taskDir, "hosts")
_, err := os.Stat(path)
if err == nil {
// hosts file already exists so it was probably created by another task.
// Use it as the starting point.
hosts, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read hosts file %s: %v", path, err)
}
content.Write(hosts)
} else if os.IsNotExist(err) {
// hosts file doesn't exist, so create a new one from the base content.
fmt.Fprintf(&content, baseHostsFile, hostsCfg.Address, hostsCfg.Hostname)
} else {
return nil, fmt.Errorf("failed to read hosts file info: %v", err)
}

if len(extraHosts) > 0 {
content.WriteString("\n# these entries are extra hosts added by the task config")
content.WriteString(fmt.Sprintf("\n# these entries are extra hosts added by the task %q", taskName))
for _, hostLine := range extraHosts {
hostsEntry := strings.SplitN(hostLine, ":", 2)
if len(hostsEntry) != 2 {
Expand All @@ -56,15 +74,9 @@ ff02::3 ip6-allhosts
content.WriteString("\n")
}

path := filepath.Join(taskDir, "hosts")

// tasks within an alloc should be able to share and modify the file, so
// only write to it if it doesn't exist
if _, err := os.Stat(path); os.IsNotExist(err) {
err := ioutil.WriteFile(path, []byte(content.String()), 0644)
if err != nil {
return nil, err
}
err = ioutil.WriteFile(path, []byte(content.String()), 0644)
if err != nil {
return nil, err
}

// Note that we're not setting readonly. The file is in the task dir
Expand Down
121 changes: 92 additions & 29 deletions drivers/shared/hostnames/mount_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ import (

func TestGenerateEtcHostsMount(t *testing.T) {

type task struct {
spec *drivers.NetworkIsolationSpec
extraHosts []string
}

testCases := []struct {
name string
spec *drivers.NetworkIsolationSpec
extraHosts []string
tasks map[string]task
expected []string
expectedErr string
}{
Expand All @@ -27,15 +31,23 @@ func TestGenerateEtcHostsMount(t *testing.T) {
},
{
name: "no-hosts-config",
spec: &drivers.NetworkIsolationSpec{Mode: drivers.NetIsolationModeGroup},
tasks: map[string]task{
"test": {
spec: &drivers.NetworkIsolationSpec{Mode: drivers.NetIsolationModeGroup},
},
},
},
{
name: "base-case",
spec: &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
HostsConfig: &drivers.HostsConfig{
Address: "192.168.1.1",
Hostname: "xyzzy",
tasks: map[string]task{
"test": {
spec: &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
HostsConfig: &drivers.HostsConfig{
Address: "192.168.1.1",
Hostname: "xyzzy",
},
},
},
},
expected: []string{
Expand All @@ -44,45 +56,93 @@ func TestGenerateEtcHostsMount(t *testing.T) {
},
{
name: "with-valid-extra-hosts",
spec: &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
HostsConfig: &drivers.HostsConfig{
Address: "192.168.1.1",
Hostname: "xyzzy",
tasks: map[string]task{
"test": {
spec: &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
HostsConfig: &drivers.HostsConfig{
Address: "192.168.1.1",
Hostname: "xyzzy",
},
},
extraHosts: []string{
"apple:192.168.1.2",
"banana:2001:0db8:85a3:0000:0000:8a2e:0370:7334",
},
},
},
extraHosts: []string{
"apple:192.168.1.2",
"banana:2001:0db8:85a3:0000:0000:8a2e:0370:7334",
expected: []string{
"192.168.1.1 xyzzy",
"192.168.1.2 apple",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334 banana",
},
},
{
name: "multiple-tasks-with-valid-extra-hosts",
tasks: map[string]task{
"test-1": {
spec: &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
HostsConfig: &drivers.HostsConfig{
Address: "192.168.1.1",
Hostname: "xyzzy",
},
},
extraHosts: []string{
"apple:192.168.1.2",
"banana:2001:0db8:85a3:0000:0000:8a2e:0370:7334",
},
},
"test-2": {
spec: &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
HostsConfig: &drivers.HostsConfig{
Address: "192.168.1.1",
Hostname: "xyzzy",
},
},
extraHosts: []string{
"cherry:192.168.1.3",
},
},
},
expected: []string{
"192.168.1.1 xyzzy",
"192.168.1.2 apple",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334 banana",
"192.168.1.3 cherry",
},
},
{
name: "invalid-extra-hosts-syntax",
spec: &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
HostsConfig: &drivers.HostsConfig{
Address: "192.168.1.1",
Hostname: "xyzzy",
tasks: map[string]task{
"test": {
spec: &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
HostsConfig: &drivers.HostsConfig{
Address: "192.168.1.1",
Hostname: "xyzzy",
},
},
extraHosts: []string{"apple192.168.1.2"},
},
},
extraHosts: []string{"apple192.168.1.2"},
expectedErr: "invalid hosts entry \"apple192.168.1.2\"",
},
{
name: "invalid-extra-hosts-bad-ip",
spec: &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
HostsConfig: &drivers.HostsConfig{
Address: "192.168.1.1",
Hostname: "xyzzy",
tasks: map[string]task{
"test": {
spec: &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
HostsConfig: &drivers.HostsConfig{
Address: "192.168.1.1",
Hostname: "xyzzy",
},
},
extraHosts: []string{"apple:192.168.1.256"},
},
},
extraHosts: []string{"apple:192.168.1.256"},
expectedErr: "invalid IP address \"apple:192.168.1.256\"",
},
}
Expand All @@ -97,7 +157,10 @@ func TestGenerateEtcHostsMount(t *testing.T) {
require.NoError(err)
dest := filepath.Join(taskDir, "hosts")

got, err := GenerateEtcHostsMount(taskDir, tc.spec, tc.extraHosts)
var got *drivers.MountConfig
for taskName, taskConfig := range tc.tasks {
got, err = GenerateEtcHostsMount(taskName, taskDir, taskConfig.spec, taskConfig.extraHosts)
}

if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr)
Expand Down
6 changes: 6 additions & 0 deletions website/content/docs/upgrade/upgrade-specific.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ from the task directory to `/etc/hosts` within the task. In Nomad 1.1.3 the
source for the bind mount was moved to the allocation directory so that it is
shared between all tasks in an allocation.

Please note that this change may prevent [`extra_hosts`] values to be correctly
propagated to each task when there are multiple tasks within the same group,
such as when using Consul Connect. Use a newer version of Nomad if this
affects you.

## Nomad 1.1.0

#### Enterprise licenses
Expand Down Expand Up @@ -1183,3 +1188,4 @@ deleted and then Nomad 0.3.0 can be launched.
[cap_add_exec]: /docs/drivers/exec#cap_add
[cap_drop_exec]: /docs/drivers/exec#cap_drop
[`log_file`]: /docs/configuration#log_file
[`extra_hosts`]: /docs/drivers/docker#extra_hosts