mirror of
https://github.com/kemko/nomad.git
synced 2026-01-05 18:05:42 +03:00
ar: support opting into binding host ports to default network IP (#8321)
* ar: support opting into binding host ports to default network IP * fix config plumbing * plumb node address into network resource * struct: only handle network resource upgrade path once
This commit is contained in:
@@ -133,12 +133,16 @@ func newNetworkConfigurator(log hclog.Logger, alloc *structs.Allocation, config
|
||||
}
|
||||
|
||||
netMode := strings.ToLower(tg.Networks[0].Mode)
|
||||
ignorePortMappingHostIP := config.BindWildcardDefaultHostNetwork
|
||||
if len(config.HostNetworks) > 0 {
|
||||
ignorePortMappingHostIP = false
|
||||
}
|
||||
|
||||
switch {
|
||||
case netMode == "bridge":
|
||||
return newBridgeNetworkConfigurator(log, config.BridgeNetworkName, config.BridgeNetworkAllocSubnet, config.CNIPath)
|
||||
return newBridgeNetworkConfigurator(log, config.BridgeNetworkName, config.BridgeNetworkAllocSubnet, config.CNIPath, ignorePortMappingHostIP)
|
||||
case strings.HasPrefix(netMode, "cni/"):
|
||||
return newCNINetworkConfigurator(log, config.CNIPath, config.CNIInterfacePrefix, config.CNIConfigDir, netMode[4:])
|
||||
return newCNINetworkConfigurator(log, config.CNIPath, config.CNIInterfacePrefix, config.CNIConfigDir, netMode[4:], ignorePortMappingHostIP)
|
||||
default:
|
||||
return &hostNetworkConfigurator{}, nil
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ type bridgeNetworkConfigurator struct {
|
||||
logger hclog.Logger
|
||||
}
|
||||
|
||||
func newBridgeNetworkConfigurator(log hclog.Logger, bridgeName, ipRange, cniPath string) (*bridgeNetworkConfigurator, error) {
|
||||
func newBridgeNetworkConfigurator(log hclog.Logger, bridgeName, ipRange, cniPath string, ignorePortMappingHostIP bool) (*bridgeNetworkConfigurator, error) {
|
||||
b := &bridgeNetworkConfigurator{
|
||||
bridgeName: bridgeName,
|
||||
allocSubnet: ipRange,
|
||||
@@ -54,7 +54,7 @@ func newBridgeNetworkConfigurator(log hclog.Logger, bridgeName, ipRange, cniPath
|
||||
b.allocSubnet = defaultNomadAllocSubnet
|
||||
}
|
||||
|
||||
c, err := newCNINetworkConfiguratorWithConf(log, cniPath, bridgeNetworkAllocIfPrefix, buildNomadBridgeNetConfig(b.bridgeName, b.allocSubnet))
|
||||
c, err := newCNINetworkConfiguratorWithConf(log, cniPath, bridgeNetworkAllocIfPrefix, ignorePortMappingHostIP, buildNomadBridgeNetConfig(b.bridgeName, b.allocSubnet))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -33,27 +33,29 @@ const (
|
||||
)
|
||||
|
||||
type cniNetworkConfigurator struct {
|
||||
cni cni.CNI
|
||||
cniConf []byte
|
||||
cni cni.CNI
|
||||
cniConf []byte
|
||||
ignorePortMappingHostIP bool
|
||||
|
||||
rand *rand.Rand
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func newCNINetworkConfigurator(logger log.Logger, cniPath, cniInterfacePrefix, cniConfDir, networkName string) (*cniNetworkConfigurator, error) {
|
||||
func newCNINetworkConfigurator(logger log.Logger, cniPath, cniInterfacePrefix, cniConfDir, networkName string, ignorePortMappingHostIP bool) (*cniNetworkConfigurator, error) {
|
||||
cniConf, err := loadCNIConf(cniConfDir, networkName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load CNI config: %v", err)
|
||||
}
|
||||
|
||||
return newCNINetworkConfiguratorWithConf(logger, cniPath, cniInterfacePrefix, cniConf)
|
||||
return newCNINetworkConfiguratorWithConf(logger, cniPath, cniInterfacePrefix, ignorePortMappingHostIP, cniConf)
|
||||
}
|
||||
|
||||
func newCNINetworkConfiguratorWithConf(logger log.Logger, cniPath, cniInterfacePrefix string, cniConf []byte) (*cniNetworkConfigurator, error) {
|
||||
func newCNINetworkConfiguratorWithConf(logger log.Logger, cniPath, cniInterfacePrefix string, ignorePortMappingHostIP bool, cniConf []byte) (*cniNetworkConfigurator, error) {
|
||||
conf := &cniNetworkConfigurator{
|
||||
cniConf: cniConf,
|
||||
rand: rand.New(rand.NewSource(time.Now().Unix())),
|
||||
logger: logger,
|
||||
cniConf: cniConf,
|
||||
rand: rand.New(rand.NewSource(time.Now().Unix())),
|
||||
logger: logger,
|
||||
ignorePortMappingHostIP: ignorePortMappingHostIP,
|
||||
}
|
||||
if cniPath == "" {
|
||||
if cniPath = os.Getenv(envCNIPath); cniPath == "" {
|
||||
@@ -88,7 +90,7 @@ func (c *cniNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Alloc
|
||||
var firstError error
|
||||
for attempt := 1; ; attempt++ {
|
||||
//TODO eventually returning the IP from the result would be nice to have in the alloc
|
||||
if _, err := c.cni.Setup(ctx, alloc.ID, spec.Path, cni.WithCapabilityPortMap(getPortMapping(alloc))); err != nil {
|
||||
if _, err := c.cni.Setup(ctx, alloc.ID, spec.Path, cni.WithCapabilityPortMap(getPortMapping(alloc, c.ignorePortMappingHostIP))); err != nil {
|
||||
c.logger.Warn("failed to configure network", "err", err, "attempt", attempt)
|
||||
switch attempt {
|
||||
case 1:
|
||||
@@ -149,7 +151,7 @@ func (c *cniNetworkConfigurator) Teardown(ctx context.Context, alloc *structs.Al
|
||||
return err
|
||||
}
|
||||
|
||||
return c.cni.Remove(ctx, alloc.ID, spec.Path, cni.WithCapabilityPortMap(getPortMapping(alloc)))
|
||||
return c.cni.Remove(ctx, alloc.ID, spec.Path, cni.WithCapabilityPortMap(getPortMapping(alloc, c.ignorePortMappingHostIP)))
|
||||
}
|
||||
|
||||
func (c *cniNetworkConfigurator) ensureCNIInitialized() error {
|
||||
@@ -162,7 +164,7 @@ func (c *cniNetworkConfigurator) ensureCNIInitialized() error {
|
||||
|
||||
// getPortMapping builds a list of portMapping structs that are used as the
|
||||
// portmapping capability arguments for the portmap CNI plugin
|
||||
func getPortMapping(alloc *structs.Allocation) []cni.PortMapping {
|
||||
func getPortMapping(alloc *structs.Allocation, ignoreHostIP bool) []cni.PortMapping {
|
||||
ports := []cni.PortMapping{}
|
||||
|
||||
if len(alloc.AllocatedResources.Shared.Ports) == 0 && len(alloc.AllocatedResources.Shared.Networks) > 0 {
|
||||
@@ -186,12 +188,15 @@ func getPortMapping(alloc *structs.Allocation) []cni.PortMapping {
|
||||
port.To = port.Value
|
||||
}
|
||||
for _, proto := range []string{"tcp", "udp"} {
|
||||
ports = append(ports, cni.PortMapping{
|
||||
portMapping := cni.PortMapping{
|
||||
HostPort: int32(port.Value),
|
||||
ContainerPort: int32(port.To),
|
||||
Protocol: proto,
|
||||
HostIP: port.HostIP,
|
||||
})
|
||||
}
|
||||
if !ignoreHostIP {
|
||||
portMapping.HostIP = port.HostIP
|
||||
}
|
||||
ports = append(ports, portMapping)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +258,16 @@ type Config struct {
|
||||
|
||||
// HostNetworks is a map of the conigured host networks by name.
|
||||
HostNetworks map[string]*structs.ClientHostNetworkConfig
|
||||
|
||||
// BindWildcardDefaultHostNetwork toggles if the default host network should accept all
|
||||
// destinations (true) or only filter on the IP of the default host network (false) when
|
||||
// port mapping. This allows Nomad clients with no defined host networks to accept and
|
||||
// port forward traffic only matching on the destination port. An example use of this
|
||||
// is when a network loadbalancer is utilizing direct server return and the destination
|
||||
// address of incomming packets does not match the IP address of the host interface.
|
||||
//
|
||||
// This configuration is only considered if no host networks are defined.
|
||||
BindWildcardDefaultHostNetwork bool
|
||||
}
|
||||
|
||||
type ClientTemplateConfig struct {
|
||||
|
||||
@@ -638,6 +638,7 @@ func convertClientConfig(agentConfig *Config) (*clientconfig.Config, error) {
|
||||
for _, hn := range agentConfig.Client.HostNetworks {
|
||||
conf.HostNetworks[hn.Name] = hn
|
||||
}
|
||||
conf.BindWildcardDefaultHostNetwork = agentConfig.Client.BindWildcardDefaultHostNetwork
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
@@ -286,8 +286,15 @@ type ClientConfig struct {
|
||||
// the host
|
||||
BridgeNetworkSubnet string `hcl:"bridge_network_subnet"`
|
||||
|
||||
// HostNetworks describes the different host networks available to the host
|
||||
// if the host uses multiple interfaces
|
||||
HostNetworks []*structs.ClientHostNetworkConfig `hcl:"host_network"`
|
||||
|
||||
// BindWildcardDefaultHostNetwork toggles if when there are no host networks,
|
||||
// should the port mapping rules match the default network address (false) or
|
||||
// matching any destination address (true). Defaults to true
|
||||
BindWildcardDefaultHostNetwork bool `hcl:"bind_wildcard_default_host_network"`
|
||||
|
||||
// ExtraKeysHCL is used by hcl to surface unexpected keys
|
||||
ExtraKeysHCL []string `hcl:",unusedKeys" json:"-"`
|
||||
}
|
||||
@@ -819,6 +826,7 @@ func DevConfig(mode *devModeConfig) *Config {
|
||||
FunctionBlacklist: []string{"plugin"},
|
||||
DisableSandbox: false,
|
||||
}
|
||||
conf.Client.BindWildcardDefaultHostNetwork = true
|
||||
conf.Telemetry.PrometheusMetrics = true
|
||||
conf.Telemetry.PublishAllocationMetrics = true
|
||||
conf.Telemetry.PublishNodeMetrics = true
|
||||
@@ -864,6 +872,7 @@ func DefaultConfig() *Config {
|
||||
FunctionBlacklist: []string{"plugin"},
|
||||
DisableSandbox: false,
|
||||
},
|
||||
BindWildcardDefaultHostNetwork: true,
|
||||
},
|
||||
Server: &ServerConfig{
|
||||
Enabled: false,
|
||||
@@ -1539,6 +1548,9 @@ func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig {
|
||||
result.HostNetworks = a.HostNetworks
|
||||
}
|
||||
|
||||
if b.BindWildcardDefaultHostNetwork {
|
||||
result.BindWildcardDefaultHostNetwork = true
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
|
||||
@@ -291,6 +291,17 @@ func TestAllocsFit(t *testing.T) {
|
||||
MBits: 100,
|
||||
},
|
||||
},
|
||||
NodeNetworks: []*NodeNetworkResource{
|
||||
{
|
||||
Mode: "host",
|
||||
Device: "eth0",
|
||||
Addresses: []NodeNetworkAddress{
|
||||
{
|
||||
Address: "10.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ReservedResources: &NodeReservedResources{
|
||||
Cpu: NodeReservedCpuResources{
|
||||
@@ -318,18 +329,24 @@ func TestAllocsFit(t *testing.T) {
|
||||
Memory: AllocatedMemoryResources{
|
||||
MemoryMB: 1024,
|
||||
},
|
||||
Networks: []*NetworkResource{
|
||||
{
|
||||
Device: "eth0",
|
||||
IP: "10.0.0.1",
|
||||
MBits: 50,
|
||||
ReservedPorts: []Port{{"main", 8000, 0, ""}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Shared: AllocatedSharedResources{
|
||||
DiskMB: 5000,
|
||||
Networks: Networks{
|
||||
{
|
||||
Mode: "host",
|
||||
IP: "10.0.0.1",
|
||||
ReservedPorts: []Port{{"main", 8000, 0, ""}},
|
||||
},
|
||||
},
|
||||
Ports: AllocatedPorts{
|
||||
{
|
||||
Label: "main",
|
||||
Value: 8000,
|
||||
HostIP: "10.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -149,29 +149,32 @@ func (idx *NetworkIndex) AddAllocs(allocs []*Allocation) (collide bool) {
|
||||
}
|
||||
|
||||
if alloc.AllocatedResources != nil {
|
||||
// Add network resources that are at the task group level
|
||||
if len(alloc.AllocatedResources.Shared.Networks) > 0 {
|
||||
for _, network := range alloc.AllocatedResources.Shared.Networks {
|
||||
if idx.AddReserved(network) {
|
||||
// Only look at AllocatedPorts if populated, otherwise use pre 0.12 logic
|
||||
// COMPAT(1.0): Remove when network resources struct is removed.
|
||||
if len(alloc.AllocatedResources.Shared.Ports) > 0 {
|
||||
if idx.AddReservedPorts(alloc.AllocatedResources.Shared.Ports) {
|
||||
collide = true
|
||||
}
|
||||
} else {
|
||||
// Add network resources that are at the task group level
|
||||
if len(alloc.AllocatedResources.Shared.Networks) > 0 {
|
||||
for _, network := range alloc.AllocatedResources.Shared.Networks {
|
||||
if idx.AddReserved(network) {
|
||||
collide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, task := range alloc.AllocatedResources.Tasks {
|
||||
if len(task.Networks) == 0 {
|
||||
continue
|
||||
}
|
||||
n := task.Networks[0]
|
||||
if idx.AddReserved(n) {
|
||||
collide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, task := range alloc.AllocatedResources.Tasks {
|
||||
if len(task.Networks) == 0 {
|
||||
continue
|
||||
}
|
||||
n := task.Networks[0]
|
||||
if idx.AddReserved(n) {
|
||||
collide = true
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-interface TODO: handle upgrade path here?
|
||||
if idx.AddReservedPorts(alloc.AllocatedResources.Shared.Ports) {
|
||||
collide = true
|
||||
}
|
||||
} else {
|
||||
// COMPAT(0.11): Remove in 0.11
|
||||
for _, task := range alloc.TaskResources {
|
||||
@@ -332,8 +335,7 @@ func (idx *NetworkIndex) AssignPorts(ask *NetworkResource) (AllocatedPorts, erro
|
||||
|
||||
// Check if in use
|
||||
if used != nil && used.Check(uint(port.Value)) {
|
||||
addrErr = fmt.Errorf("reserved port collision")
|
||||
continue
|
||||
return nil, fmt.Errorf("reserved port collision %s=%d", port.Label, port.Value)
|
||||
}
|
||||
|
||||
allocPort = &AllocatedPortMapping{
|
||||
@@ -427,7 +429,7 @@ func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResour
|
||||
|
||||
// Check if in use
|
||||
if used != nil && used.Check(uint(port.Value)) {
|
||||
err = fmt.Errorf("reserved port collision")
|
||||
err = fmt.Errorf("reserved port collision %s=%d", port.Label, port.Value)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -565,7 +567,7 @@ func isPortReserved(haystack []int, needle int) bool {
|
||||
}
|
||||
|
||||
// COMPAT(1.0) remove when NetworkResource is no longer used for materialized client view of ports
|
||||
func AllocatedPortsToNetworkResouce(ask *NetworkResource, ports AllocatedPorts) *NetworkResource {
|
||||
func AllocatedPortsToNetworkResouce(ask *NetworkResource, ports AllocatedPorts, node *NodeResources) *NetworkResource {
|
||||
out := ask.Copy()
|
||||
|
||||
for i, port := range ask.DynamicPorts {
|
||||
@@ -574,6 +576,20 @@ func AllocatedPortsToNetworkResouce(ask *NetworkResource, ports AllocatedPorts)
|
||||
out.DynamicPorts[i].To = p.To
|
||||
}
|
||||
}
|
||||
if len(node.NodeNetworks) > 0 {
|
||||
for _, nw := range node.NodeNetworks {
|
||||
if nw.Mode == "host" {
|
||||
out.IP = nw.Addresses[0].Address
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, nw := range node.Networks {
|
||||
if nw.Mode == "host" {
|
||||
out.IP = nw.IP
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -284,7 +284,7 @@ OUTER:
|
||||
netIdx.AddReservedPorts(offer)
|
||||
|
||||
// Update the network ask to the offer
|
||||
nwRes := structs.AllocatedPortsToNetworkResouce(ask, offer)
|
||||
nwRes := structs.AllocatedPortsToNetworkResouce(ask, offer, option.Node.NodeResources)
|
||||
total.Shared.Networks = []*structs.NetworkResource{nwRes}
|
||||
total.Shared.Ports = offer
|
||||
option.AllocResources = &structs.AllocatedSharedResources{
|
||||
|
||||
Reference in New Issue
Block a user