Skip to content

Commit

Permalink
cni: use check command when restoring from restart
Browse files Browse the repository at this point in the history
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, 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: #24292
Ref: #24650
  • Loading branch information
tgross committed Dec 12, 2024
1 parent dcb0259 commit 9daf267
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .changelog/24658.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
networking: check network namespaces on Linux during client restarts and fail the allocation if an existing namespace is invalid
```
7 changes: 5 additions & 2 deletions client/allocrunner/network_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,14 @@ func (h *networkHook) Prerun() error {
h.isolationSetter.SetNetworkIsolation(spec)
}

if created {
status, err := h.networkConfigurator.Setup(context.TODO(), h.alloc, spec)
if spec != nil {
status, err := h.networkConfigurator.Setup(context.TODO(), h.alloc, spec, created)
if err != nil {
return fmt.Errorf("failed to configure networking for alloc: %v", err)
}
if status == nil {
return nil // netns already existed and was correctly configured
}

// If the driver set the sandbox hostname label, then we will use that
// to set the HostsConfig.Hostname. Otherwise, identify the sandbox
Expand Down
8 changes: 4 additions & 4 deletions client/allocrunner/networking.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// NetworkConfigurator sets up and tears down the interfaces, routes, firewall
// rules, etc for the configured networking mode of the allocation.
type NetworkConfigurator interface {
Setup(context.Context, *structs.Allocation, *drivers.NetworkIsolationSpec) (*structs.AllocNetworkStatus, error)
Setup(context.Context, *structs.Allocation, *drivers.NetworkIsolationSpec, bool) (*structs.AllocNetworkStatus, error)
Teardown(context.Context, *structs.Allocation, *drivers.NetworkIsolationSpec) error
}

Expand All @@ -23,7 +23,7 @@ type NetworkConfigurator interface {
// require further configuration
type hostNetworkConfigurator struct{}

func (h *hostNetworkConfigurator) Setup(context.Context, *structs.Allocation, *drivers.NetworkIsolationSpec) (*structs.AllocNetworkStatus, error) {
func (h *hostNetworkConfigurator) Setup(context.Context, *structs.Allocation, *drivers.NetworkIsolationSpec, bool) (*structs.AllocNetworkStatus, error) {
return nil, nil
}
func (h *hostNetworkConfigurator) Teardown(context.Context, *structs.Allocation, *drivers.NetworkIsolationSpec) error {
Expand All @@ -41,10 +41,10 @@ type synchronizedNetworkConfigurator struct {
nc NetworkConfigurator
}

func (s *synchronizedNetworkConfigurator) Setup(ctx context.Context, allocation *structs.Allocation, spec *drivers.NetworkIsolationSpec) (*structs.AllocNetworkStatus, error) {
func (s *synchronizedNetworkConfigurator) Setup(ctx context.Context, allocation *structs.Allocation, spec *drivers.NetworkIsolationSpec, created bool) (*structs.AllocNetworkStatus, error) {
networkingGlobalMutex.Lock()
defer networkingGlobalMutex.Unlock()
return s.nc.Setup(ctx, allocation, spec)
return s.nc.Setup(ctx, allocation, spec, created)
}

func (s *synchronizedNetworkConfigurator) Teardown(ctx context.Context, allocation *structs.Allocation, spec *drivers.NetworkIsolationSpec) error {
Expand Down
4 changes: 2 additions & 2 deletions client/allocrunner/networking_bridge_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,12 @@ func (b *bridgeNetworkConfigurator) ensureForwardingRules() error {
}

// Setup calls the CNI plugins with the add action
func (b *bridgeNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) (*structs.AllocNetworkStatus, error) {
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)
return b.cni.Setup(ctx, alloc, spec, created)
}

// Teardown calls the CNI plugins with the delete action
Expand Down
13 changes: 12 additions & 1 deletion client/allocrunner/networking_cni.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ func addNomadWorkloadCNIArgs(logger log.Logger, alloc *structs.Allocation, cniAr
}

// Setup calls the CNI plugins with the add action
func (c *cniNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) (*structs.AllocNetworkStatus, error) {
func (c *cniNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec, created bool) (*structs.AllocNetworkStatus, error) {

if err := c.ensureCNIInitialized(); err != nil {
return nil, fmt.Errorf("cni not initialized: %w", err)
}
Expand Down Expand Up @@ -171,6 +172,16 @@ func (c *cniNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Alloc
cniArgs[ConsulIPTablesConfigEnvVar] = string(iptablesCfg)
}

if !created {
err := c.cni.Check(ctx, alloc.ID, spec.Path,
c.nsOpts.withCapabilityPortMap(portMaps.ports),
c.nsOpts.withArgs(cniArgs),
)
// return early: either the netns is ok or we need to start the alloc
// over anyways
return nil, err
}

// Depending on the version of bridge cni plugin used, a known race could occure
// where two alloc attempt to create the nomad bridge at the same time, resulting
// in one of them to fail. This rety attempts to overcome those erroneous failures.
Expand Down
2 changes: 1 addition & 1 deletion client/allocrunner/networking_cni_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ func TestSetup(t *testing.T) {
}

// method under test
result, err := c.Setup(context.Background(), alloc, spec)
result, err := c.Setup(context.Background(), alloc, spec, true)
if tc.expectErr == "" {
must.NoError(t, err)
must.Eq(t, tc.expectResult, result)
Expand Down

0 comments on commit 9daf267

Please sign in to comment.