mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
When the Nomad client restarts and restores allocations, the network namespace for an allocation may exist but no longer be correctly configured. For example, if the host is rebooted and the task was a Docker task using a pause container, the network namespace may be recreated by the docker daemon. When we restore an allocation, use the CNI "check" command to verify that any existing network namespace matches the expected configuration. This requires CNI plugins of at least version 1.2.0 to avoid a bug in older plugin versions that would cause the check to fail. If the check fails, destroy the network namespace and try to recreate it from scratch once. If that fails in the second pass, fail the restore so that the allocation can be recreated (rather than silently having networking fail). This should fix the gap left #24650 for Docker task drivers and any other drivers with the `MustInitiateNetwork` capability. Fixes: https://github.com/hashicorp/nomad/issues/24292 Ref: https://github.com/hashicorp/nomad/pull/24650
147 lines
4.1 KiB
Go
147 lines
4.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package allocrunner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
hclog "github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/nomad/client/allocrunner/cni"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/plugins/drivers"
|
|
)
|
|
|
|
const (
|
|
// defaultNomadBridgeName is the name of the bridge to use when not set by
|
|
// the client
|
|
defaultNomadBridgeName = "nomad"
|
|
|
|
// bridgeNetworkAllocIfPrefix is the prefix that is used for the interface
|
|
// name created inside of the alloc network which is connected to the bridge
|
|
bridgeNetworkAllocIfPrefix = "eth"
|
|
|
|
// defaultNomadAllocSubnet is the subnet to use for host local ip address
|
|
// allocation when not specified by the client
|
|
defaultNomadAllocSubnet = "172.26.64.0/20" // end 172.26.79.255
|
|
)
|
|
|
|
// bridgeNetworkConfigurator is a NetworkConfigurator which adds the alloc to a
|
|
// shared bridge, configures masquerading for egress traffic and port mapping
|
|
// for ingress
|
|
type bridgeNetworkConfigurator struct {
|
|
cni *cniNetworkConfigurator
|
|
allocSubnetIPv6 string
|
|
allocSubnetIPv4 string
|
|
bridgeName string
|
|
hairpinMode bool
|
|
|
|
newIPTables func(structs.NodeNetworkAF) (IPTablesChain, error)
|
|
|
|
logger hclog.Logger
|
|
}
|
|
|
|
func newBridgeNetworkConfigurator(log hclog.Logger, alloc *structs.Allocation, bridgeName, ipv4Range, ipv6Range, cniPath string, hairpinMode, ignorePortMappingHostIP bool, node *structs.Node) (*bridgeNetworkConfigurator, error) {
|
|
b := &bridgeNetworkConfigurator{
|
|
bridgeName: bridgeName,
|
|
hairpinMode: hairpinMode,
|
|
allocSubnetIPv4: ipv4Range,
|
|
allocSubnetIPv6: ipv6Range,
|
|
newIPTables: newIPTablesChain,
|
|
logger: log,
|
|
}
|
|
|
|
if b.bridgeName == "" {
|
|
b.bridgeName = defaultNomadBridgeName
|
|
}
|
|
|
|
if b.allocSubnetIPv4 == "" {
|
|
b.allocSubnetIPv4 = defaultNomadAllocSubnet
|
|
}
|
|
|
|
var netCfg []byte
|
|
var err error
|
|
|
|
tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
|
|
for _, svc := range tg.Services {
|
|
if svc.Connect.HasTransparentProxy() {
|
|
netCfg, err = buildNomadBridgeNetConfig(*b, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
if netCfg == nil {
|
|
netCfg, err = buildNomadBridgeNetConfig(*b, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
parser := &cniConfParser{
|
|
listBytes: netCfg,
|
|
}
|
|
|
|
c, err := newCNINetworkConfiguratorWithConf(log, cniPath, bridgeNetworkAllocIfPrefix, ignorePortMappingHostIP, parser, node)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.cni = c
|
|
|
|
return b, nil
|
|
}
|
|
|
|
// ensureForwardingRules ensures that a forwarding rule is added to iptables
|
|
// to allow traffic inbound to the bridge network
|
|
func (b *bridgeNetworkConfigurator) ensureForwardingRules() error {
|
|
if b.allocSubnetIPv6 != "" {
|
|
ip6t, err := b.newIPTables(structs.NodeNetworkAF_IPv6)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = ensureChainRule(ip6t, b.bridgeName, b.allocSubnetIPv6); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
ipt, err := b.newIPTables(structs.NodeNetworkAF_IPv4)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = ensureChainRule(ipt, b.bridgeName, b.allocSubnetIPv4); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Setup calls the CNI plugins with the add action
|
|
func (b *bridgeNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec, created bool) (*structs.AllocNetworkStatus, error) {
|
|
if err := b.ensureForwardingRules(); err != nil {
|
|
return nil, fmt.Errorf("failed to initialize table forwarding rules: %v", err)
|
|
}
|
|
|
|
return b.cni.Setup(ctx, alloc, spec, created)
|
|
}
|
|
|
|
// Teardown calls the CNI plugins with the delete action
|
|
func (b *bridgeNetworkConfigurator) Teardown(ctx context.Context, alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) error {
|
|
return b.cni.Teardown(ctx, alloc, spec)
|
|
}
|
|
|
|
func buildNomadBridgeNetConfig(b bridgeNetworkConfigurator, withConsulCNI bool) ([]byte, error) {
|
|
conf := cni.NewNomadBridgeConflist(cni.NomadBridgeConfig{
|
|
BridgeName: b.bridgeName,
|
|
AdminChainName: cniAdminChainName,
|
|
IPv4Subnet: b.allocSubnetIPv4,
|
|
IPv6Subnet: b.allocSubnetIPv6,
|
|
HairpinMode: b.hairpinMode,
|
|
ConsulCNI: withConsulCNI,
|
|
})
|
|
return conf.Json()
|
|
}
|