open-nomad/client/allocrunner/network_manager_linux.go
James Rasell 0e926ef3fd
allow configuration of Docker hostnames in bridge mode (#11173)
Add a new hostname string parameter to the network block which
allows operators to specify the hostname of the network namespace.
Changing this causes a destructive update to the allocation and it
is omitted if empty from API responses. This parameter also supports
interpolation.

In order to have a hostname passed as a configuration param when
creating an allocation network, the CreateNetwork func of the
DriverNetworkManager interface needs to be updated. In order to
minimize the disruption of future changes, rather than add another
string func arg, the function now accepts a request struct along with
the allocID param. The struct has the hostname as a field.

The in-tree implementations of DriverNetworkManager.CreateNetwork
have been modified to account for the function signature change.
In updating for the change, the enhancement of adding hostnames to
network namespaces has also been added to the Docker driver, whilst
the default Linux manager does not current implement it.
2021-09-16 08:13:09 +02:00

191 lines
6.4 KiB
Go

package allocrunner
import (
"fmt"
"os"
"path"
"strings"
"syscall"
hclog "github.com/hashicorp/go-hclog"
clientconfig "github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/client/lib/nsutil"
"github.com/hashicorp/nomad/client/pluginmanager/drivermanager"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/plugins/drivers"
)
func newNetworkManager(alloc *structs.Allocation, driverManager drivermanager.Manager) (nm drivers.DriverNetworkManager, err error) {
// The defaultNetworkManager is used if a driver doesn't need to create the network
nm = &defaultNetworkManager{}
tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
// default netmode to host, this can be overridden by the task or task group
tgNetMode := "host"
if len(tg.Networks) > 0 && tg.Networks[0].Mode != "" {
tgNetMode = tg.Networks[0].Mode
}
groupIsolationMode := netModeToIsolationMode(tgNetMode)
// Setting the hostname is only possible where the task groups networking
// mode is group; meaning bridge or none.
if len(tg.Networks) > 0 &&
(groupIsolationMode != drivers.NetIsolationModeGroup && tg.Networks[0].Hostname != "") {
return nil, fmt.Errorf("hostname cannot be set on task group using %q networking mode",
groupIsolationMode)
}
// networkInitiator tracks the task driver which needs to create the network
// to check for multiple drivers needing to create the network.
var networkInitiator string
// driverCaps tracks which drivers we've checked capabilities for so as not
// to do extra work
driverCaps := make(map[string]struct{})
for _, task := range tg.Tasks {
// the task's netmode defaults to the the task group but can be overridden
taskNetMode := tgNetMode
if len(task.Resources.Networks) > 0 && task.Resources.Networks[0].Mode != "" {
taskNetMode = task.Resources.Networks[0].Mode
}
// netmode host should always work to support backwards compat
if taskNetMode == "host" {
continue
}
// check to see if capabilities of this task's driver have already been checked
if _, ok := driverCaps[task.Driver]; ok {
continue
}
driver, err := driverManager.Dispense(task.Driver)
if err != nil {
return nil, fmt.Errorf("failed to dispense driver %s: %v", task.Driver, err)
}
caps, err := driver.Capabilities()
if err != nil {
return nil, fmt.Errorf("failed to retrieve capabilities for driver %s: %v",
task.Driver, err)
}
// check that the driver supports the requested network isolation mode
netIsolationMode := netModeToIsolationMode(taskNetMode)
if !caps.HasNetIsolationMode(netIsolationMode) {
return nil, fmt.Errorf("task %s does not support %q networking mode", task.Name, taskNetMode)
}
// check if the driver needs to create the network and if a different
// driver has already claimed it needs to initiate the network
if caps.MustInitiateNetwork {
if networkInitiator != "" {
return nil, fmt.Errorf("tasks %s and %s want to initiate networking but only one driver can do so", networkInitiator, task.Name)
}
netManager, ok := driver.(drivers.DriverNetworkManager)
if !ok {
return nil, fmt.Errorf("driver %s does not implement network management RPCs", task.Driver)
}
nm = netManager
networkInitiator = task.Name
} else if tg.Networks[0].Hostname != "" {
// TODO jrasell: remove once the default linux network manager
// supports setting the hostname in bridged mode. This currently
// indicates only Docker supports this, which is true unless a
// custom driver can which means this check still holds as true as
// we can tell.
// Please see: https://github.com/hashicorp/nomad/issues/11180
return nil, fmt.Errorf("hostname is not currently supported on driver %s", task.Driver)
}
// mark this driver's capabilities as checked
driverCaps[task.Driver] = struct{}{}
}
return nm, nil
}
// defaultNetworkManager creates a network namespace for the alloc
type defaultNetworkManager struct{}
// CreateNetwork is the CreateNetwork implementation of the
// drivers.DriverNetworkManager interface function. It does not currently
// support setting the hostname of the network namespace.
func (*defaultNetworkManager) CreateNetwork(allocID string, _ *drivers.NetworkCreateRequest) (*drivers.NetworkIsolationSpec, bool, error) {
netns, err := nsutil.NewNS(allocID)
if err != nil {
// when a client restarts, the namespace will already exist and
// there will be a namespace file in use by the task process
if e, ok := err.(*os.PathError); ok && e.Err == syscall.EPERM {
nsPath := path.Join(nsutil.NetNSRunDir, allocID)
_, err := os.Stat(nsPath)
if err == nil {
return nil, false, nil
}
}
return nil, false, err
}
spec := &drivers.NetworkIsolationSpec{
Mode: drivers.NetIsolationModeGroup,
Path: netns.Path(),
Labels: make(map[string]string),
}
return spec, true, nil
}
func (*defaultNetworkManager) DestroyNetwork(allocID string, spec *drivers.NetworkIsolationSpec) error {
return nsutil.UnmountNS(spec.Path)
}
func netModeToIsolationMode(netMode string) drivers.NetIsolationMode {
switch strings.ToLower(netMode) {
case "host":
return drivers.NetIsolationModeHost
case "bridge", "none":
return drivers.NetIsolationModeGroup
case "driver":
return drivers.NetIsolationModeTask
default:
if strings.HasPrefix(strings.ToLower(netMode), "cni/") {
return drivers.NetIsolationModeGroup
}
return drivers.NetIsolationModeHost
}
}
func newNetworkConfigurator(log hclog.Logger, alloc *structs.Allocation, config *clientconfig.Config) (NetworkConfigurator, error) {
tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
// Check if network stanza is given
if len(tg.Networks) == 0 {
return &hostNetworkConfigurator{}, nil
}
netMode := strings.ToLower(tg.Networks[0].Mode)
ignorePortMappingHostIP := config.BindWildcardDefaultHostNetwork
if len(config.HostNetworks) > 0 {
ignorePortMappingHostIP = false
}
switch {
case netMode == "bridge":
c, err := newBridgeNetworkConfigurator(log, config.BridgeNetworkName, config.BridgeNetworkAllocSubnet, config.CNIPath, ignorePortMappingHostIP)
if err != nil {
return nil, err
}
return &synchronizedNetworkConfigurator{c}, nil
case strings.HasPrefix(netMode, "cni/"):
c, err := newCNINetworkConfigurator(log, config.CNIPath, config.CNIInterfacePrefix, config.CNIConfigDir, netMode[4:], ignorePortMappingHostIP)
if err != nil {
return nil, err
}
return &synchronizedNetworkConfigurator{c}, nil
default:
return &hostNetworkConfigurator{}, nil
}
}