cni: add DNS set by CNI plugins to task configuration (#20007)

CNI plugins may set DNS configuration, but this isn't threaded through to the
task configuration so that we can write it to the `/etc/resolv.conf` file as
needed. Add the `AllocNetworkStatus` to the alloc hook resources so they're
accessible from the taskrunner. Any DNS entries provided by the user will
override these values.

Fixes: https://github.com/hashicorp/nomad/issues/11102
This commit is contained in:
Tim Gross
2024-02-20 10:17:27 -05:00
committed by GitHub
parent 20cfbc82d3
commit 45b2c34532
6 changed files with 163 additions and 6 deletions

3
.changelog/20007.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:bug
cni: Fixed a bug where DNS set by CNI plugins was not provided to task drivers
```

View File

@@ -908,7 +908,9 @@ func (ar *allocRunner) SetClientStatus(clientStatus string) {
func (ar *allocRunner) SetNetworkStatus(s *structs.AllocNetworkStatus) {
ar.stateLock.Lock()
defer ar.stateLock.Unlock()
ar.state.NetworkStatus = s.Copy()
ans := s.Copy()
ar.state.NetworkStatus = ans
ar.hookResources.SetAllocNetworkStatus(ans)
}
func (ar *allocRunner) NetworkStatus() *structs.AllocNetworkStatus {

View File

@@ -1125,6 +1125,18 @@ func (tr *TaskRunner) buildTaskConfig() *drivers.TaskConfig {
defer tr.networkIsolationLock.Unlock()
var dns *drivers.DNSConfig
// set DNS from any CNI plugins
netStatus := tr.allocHookResources.GetAllocNetworkStatus()
if netStatus != nil && netStatus.DNS != nil {
dns = &drivers.DNSConfig{
Servers: netStatus.DNS.Servers,
Searches: netStatus.DNS.Searches,
Options: netStatus.DNS.Options,
}
}
// override DNS if set by job submitter
if alloc.AllocatedResources != nil && len(alloc.AllocatedResources.Shared.Networks) > 0 {
allocDNS := alloc.AllocatedResources.Shared.Networks[0].DNS
if allocDNS != nil {

View File

@@ -2931,3 +2931,121 @@ func TestTaskRunner_IdentityHook_Disabled(t *testing.T) {
taskEnv := tr.envBuilder.Build()
must.MapNotContainsKey(t, taskEnv.EnvMap, "NOMAD_TOKEN")
}
func TestTaskRunner_AllocNetworkStatus(t *testing.T) {
ci.Parallel(t)
// Mock task with group network
alloc1 := mock.Alloc()
task1 := alloc1.Job.TaskGroups[0].Tasks[0]
alloc1.AllocatedResources.Shared.Networks = []*structs.NetworkResource{
{
Device: "eth0",
IP: "192.168.0.100",
DNS: &structs.DNSConfig{
Servers: []string{"1.1.1.1", "8.8.8.8"},
Searches: []string{"test.local"},
Options: []string{"ndots:1"},
},
ReservedPorts: []structs.Port{{Label: "admin", Value: 5000}},
DynamicPorts: []structs.Port{{Label: "http", Value: 9876}},
}}
task1.Driver = "mock_driver"
task1.Config = map[string]interface{}{"run_for": "2s"}
// Mock task with task networking only
alloc2 := mock.Alloc()
task2 := alloc2.Job.TaskGroups[0].Tasks[0]
task2.Driver = "mock_driver"
task2.Config = map[string]interface{}{"run_for": "2s"}
testCases := []struct {
name string
alloc *structs.Allocation
task *structs.Task
fromCNI *structs.DNSConfig
expect *drivers.DNSConfig
}{
{
name: "task with group networking overrides CNI",
alloc: alloc1,
task: task1,
fromCNI: &structs.DNSConfig{
Servers: []string{"10.37.105.17"},
Searches: []string{"node.consul"},
Options: []string{"ndots:2", "edns0"},
},
expect: &drivers.DNSConfig{
Servers: []string{"1.1.1.1", "8.8.8.8"},
Searches: []string{"test.local"},
Options: []string{"ndots:1"},
},
},
{
name: "task with CNI alone",
alloc: alloc2,
task: task1,
fromCNI: &structs.DNSConfig{
Servers: []string{"10.37.105.17"},
Searches: []string{"node.consul"},
Options: []string{"ndots:2", "edns0"},
},
expect: &drivers.DNSConfig{
Servers: []string{"10.37.105.17"},
Searches: []string{"node.consul"},
Options: []string{"ndots:2", "edns0"},
},
},
{
name: "task with group networking alone",
alloc: alloc1,
task: task1,
fromCNI: nil,
expect: &drivers.DNSConfig{
Servers: []string{"1.1.1.1", "8.8.8.8"},
Searches: []string{"test.local"},
Options: []string{"ndots:1"},
},
},
{
name: "task without group networking",
alloc: alloc2,
task: task2,
fromCNI: nil,
expect: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
conf, cleanup := testTaskRunnerConfig(t, tc.alloc, tc.task.Name, nil)
t.Cleanup(cleanup)
// note this will never actually be set if we don't have group/CNI
// networking, but it's a good validation no-group/CNI code path
conf.AllocHookResources.SetAllocNetworkStatus(&structs.AllocNetworkStatus{
InterfaceName: "",
Address: "",
DNS: tc.fromCNI,
})
tr, err := NewTaskRunner(conf)
must.NoError(t, err)
// Run the task runner.
go tr.Run()
t.Cleanup(func() {
tr.Kill(context.Background(), structs.NewTaskEvent("cleanup"))
})
// Wait for task to complete.
testWaitForTaskToStart(t, tr)
tr.stateLock.RLock()
t.Cleanup(tr.stateLock.RUnlock)
must.Eq(t, tc.expect, tr.localState.TaskHandle.Config.DNS)
})
}
}

View File

@@ -9,6 +9,7 @@ import (
consulapi "github.com/hashicorp/consul/api"
"github.com/hashicorp/nomad/client/pluginmanager/csimanager"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/structs"
)
// AllocHookResources contains data that is provided by AllocRunner Hooks for
@@ -16,8 +17,9 @@ import (
// AllocRunner and then only accessed via getters and setters that hold the
// lock.
type AllocHookResources struct {
csiMounts map[string]*csimanager.MountInfo
consulTokens map[string]map[string]*consulapi.ACLToken // Consul cluster -> service identity -> token
csiMounts map[string]*csimanager.MountInfo
consulTokens map[string]map[string]*consulapi.ACLToken // Consul cluster -> service identity -> token
networkStatus *structs.AllocNetworkStatus
mu sync.RWMutex
}
@@ -67,3 +69,21 @@ func (a *AllocHookResources) SetConsulTokens(m map[string]map[string]*consulapi.
a.consulTokens[k] = v
}
}
// GetAllocNetworkStatus returns a copy of the AllocNetworkStatus previously
// written the group's network_hook
func (a *AllocHookResources) GetAllocNetworkStatus() *structs.AllocNetworkStatus {
a.mu.RLock()
defer a.mu.RUnlock()
return a.networkStatus.Copy()
}
// SetAllocNetworkStatus stores the AllocNetworkStatus for later use by the
// taskrunner's buildTaskConfig() method
func (a *AllocHookResources) SetAllocNetworkStatus(ans *structs.AllocNetworkStatus) {
a.mu.Lock()
defer a.mu.Unlock()
a.networkStatus = ans
}

View File

@@ -84,9 +84,11 @@ All other operating systems use the `host` networking mode.
[mode](#mode) is set to [`bridge`](#bridge). This parameter supports
[interpolation](/nomad/docs/runtime/interpolation).
- `dns` <code>([DNSConfig](#dns-parameters): nil)</code> - Sets the DNS configuration
for the allocations. By default all DNS configuration is inherited from the client host.
DNS configuration is only supported on Linux clients at this time.
- `dns` <code>([DNSConfig](#dns-parameters): nil)</code> - Sets the DNS
configuration for the allocations. By default all task drivers will inherit
DNS configuration from the client host. DNS configuration is only supported on
Linux clients at this time. Note that if you are using a `mode="cni/*`, these
values will override any DNS configuration the CNI plugins return.
### `port` Parameters