0e926ef3fd
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.
191 lines
6.4 KiB
Go
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
|
|
}
|
|
}
|