github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/allocrunner/network_hook.go (about)

     1  package allocrunner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	hclog "github.com/hashicorp/go-hclog"
     8  	"github.com/hashicorp/nomad/client/taskenv"
     9  	"github.com/hashicorp/nomad/nomad/structs"
    10  	"github.com/hashicorp/nomad/plugins/drivers"
    11  	"github.com/miekg/dns"
    12  )
    13  
    14  const (
    15  	// dockerNetSpecLabelKey is the label added when we create a pause
    16  	// container to own the network namespace, and the NetworkIsolationSpec we
    17  	// get back from CreateNetwork has this label set as the container ID.
    18  	// We'll use this to generate a hostname for the task in the event the user
    19  	// did not specify a custom one. Please see dockerNetSpecHostnameKey.
    20  	dockerNetSpecLabelKey = "docker_sandbox_container_id"
    21  
    22  	// dockerNetSpecHostnameKey is the label added when we create a pause
    23  	// container and the task group network include a user supplied hostname
    24  	// parameter.
    25  	dockerNetSpecHostnameKey = "docker_sandbox_hostname"
    26  )
    27  
    28  type networkIsolationSetter interface {
    29  	SetNetworkIsolation(*drivers.NetworkIsolationSpec)
    30  }
    31  
    32  // allocNetworkIsolationSetter is a shim to allow the alloc network hook to
    33  // set the alloc network isolation configuration without full access
    34  // to the alloc runner
    35  type allocNetworkIsolationSetter struct {
    36  	ar *allocRunner
    37  }
    38  
    39  func (a *allocNetworkIsolationSetter) SetNetworkIsolation(n *drivers.NetworkIsolationSpec) {
    40  	for _, tr := range a.ar.tasks {
    41  		tr.SetNetworkIsolation(n)
    42  	}
    43  }
    44  
    45  type networkStatusSetter interface {
    46  	SetNetworkStatus(*structs.AllocNetworkStatus)
    47  }
    48  
    49  // networkHook is an alloc lifecycle hook that manages the network namespace
    50  // for an alloc
    51  type networkHook struct {
    52  	// isolationSetter is a callback to set the network isolation spec when after the
    53  	// network is created
    54  	isolationSetter networkIsolationSetter
    55  
    56  	// statusSetter is a callback to the alloc runner to set the network status once
    57  	// network setup is complete
    58  	networkStatusSetter networkStatusSetter
    59  
    60  	// manager is used when creating the network namespace. This defaults to
    61  	// bind mounting a network namespace descritor under /var/run/netns but
    62  	// can be created by a driver if nessicary
    63  	manager drivers.DriverNetworkManager
    64  
    65  	// alloc should only be read from
    66  	alloc *structs.Allocation
    67  
    68  	// spec described the network namespace and is syncronized by specLock
    69  	spec *drivers.NetworkIsolationSpec
    70  
    71  	// networkConfigurator configures the network interfaces, routes, etc once
    72  	// the alloc network has been created
    73  	networkConfigurator NetworkConfigurator
    74  
    75  	// taskEnv is used to perform interpolation within the network blocks.
    76  	taskEnv *taskenv.TaskEnv
    77  
    78  	logger hclog.Logger
    79  }
    80  
    81  func newNetworkHook(logger hclog.Logger,
    82  	ns networkIsolationSetter,
    83  	alloc *structs.Allocation,
    84  	netManager drivers.DriverNetworkManager,
    85  	netConfigurator NetworkConfigurator,
    86  	networkStatusSetter networkStatusSetter,
    87  	taskEnv *taskenv.TaskEnv,
    88  ) *networkHook {
    89  	return &networkHook{
    90  		isolationSetter:     ns,
    91  		networkStatusSetter: networkStatusSetter,
    92  		alloc:               alloc,
    93  		manager:             netManager,
    94  		networkConfigurator: netConfigurator,
    95  		taskEnv:             taskEnv,
    96  		logger:              logger,
    97  	}
    98  }
    99  
   100  func (h *networkHook) Name() string {
   101  	return "network"
   102  }
   103  
   104  func (h *networkHook) Prerun() error {
   105  	tg := h.alloc.Job.LookupTaskGroup(h.alloc.TaskGroup)
   106  	if len(tg.Networks) == 0 || tg.Networks[0].Mode == "host" || tg.Networks[0].Mode == "" {
   107  		return nil
   108  	}
   109  
   110  	if h.manager == nil || h.networkConfigurator == nil {
   111  		h.logger.Trace("shared network namespaces are not supported on this platform, skipping network hook")
   112  		return nil
   113  	}
   114  
   115  	// Perform our networks block interpolation.
   116  	interpolatedNetworks := taskenv.InterpolateNetworks(h.taskEnv, tg.Networks)
   117  
   118  	// Interpolated values need to be validated. It is also possible a user
   119  	// supplied hostname avoids the validation on job registrations because it
   120  	// looks like it includes interpolation, when it doesn't.
   121  	if interpolatedNetworks[0].Hostname != "" {
   122  		if _, ok := dns.IsDomainName(interpolatedNetworks[0].Hostname); !ok {
   123  			return fmt.Errorf("network hostname %q is not a valid DNS name", interpolatedNetworks[0].Hostname)
   124  		}
   125  	}
   126  
   127  	// Our network create request.
   128  	networkCreateReq := drivers.NetworkCreateRequest{
   129  		Hostname: interpolatedNetworks[0].Hostname,
   130  	}
   131  
   132  	spec, created, err := h.manager.CreateNetwork(h.alloc.ID, &networkCreateReq)
   133  	if err != nil {
   134  		return fmt.Errorf("failed to create network for alloc: %v", err)
   135  	}
   136  
   137  	if spec != nil {
   138  		h.spec = spec
   139  		h.isolationSetter.SetNetworkIsolation(spec)
   140  	}
   141  
   142  	if created {
   143  		status, err := h.networkConfigurator.Setup(context.TODO(), h.alloc, spec)
   144  		if err != nil {
   145  			return fmt.Errorf("failed to configure networking for alloc: %v", err)
   146  		}
   147  
   148  		// If the driver set the sandbox hostname label, then we will use that
   149  		// to set the HostsConfig.Hostname. Otherwise, identify the sandbox
   150  		// container ID which will have been used to set the network namespace
   151  		// hostname.
   152  		if hostname, ok := spec.Labels[dockerNetSpecHostnameKey]; ok {
   153  			h.spec.HostsConfig = &drivers.HostsConfig{
   154  				Address:  status.Address,
   155  				Hostname: hostname,
   156  			}
   157  		} else if hostname, ok := spec.Labels[dockerNetSpecLabelKey]; ok {
   158  
   159  			// the docker_sandbox_container_id is the full ID of the pause
   160  			// container, whereas we want the shortened name that dockerd sets
   161  			// as the pause container's hostname.
   162  			if len(hostname) > 12 {
   163  				hostname = hostname[:12]
   164  			}
   165  
   166  			h.spec.HostsConfig = &drivers.HostsConfig{
   167  				Address:  status.Address,
   168  				Hostname: hostname,
   169  			}
   170  		}
   171  
   172  		h.networkStatusSetter.SetNetworkStatus(status)
   173  	}
   174  	return nil
   175  }
   176  
   177  func (h *networkHook) Postrun() error {
   178  	if h.spec == nil {
   179  		return nil
   180  	}
   181  
   182  	if err := h.networkConfigurator.Teardown(context.TODO(), h.alloc, h.spec); err != nil {
   183  		h.logger.Error("failed to cleanup network for allocation, resources may have leaked", "alloc", h.alloc.ID, "error", err)
   184  	}
   185  	return h.manager.DestroyNetwork(h.alloc.ID, h.spec)
   186  }