github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/drivers/docker/network.go (about)

     1  package docker
     2  
     3  import (
     4  	"fmt"
     5  
     6  	docker "github.com/fsouza/go-dockerclient"
     7  	"github.com/hashicorp/nomad/plugins/drivers"
     8  )
     9  
    10  const (
    11  	// dockerNetSpecLabelKey is the label added when we create a pause
    12  	// container to own the network namespace, and the NetworkIsolationSpec we
    13  	// get back from CreateNetwork has this label set as the container ID.
    14  	// We'll use this to generate a hostname for the task in the event the user
    15  	// did not specify a custom one. Please see dockerNetSpecHostnameKey.
    16  	dockerNetSpecLabelKey = "docker_sandbox_container_id"
    17  
    18  	// dockerNetSpecHostnameKey is the label added when we create a pause
    19  	// container and the task group network include a user supplied hostname
    20  	// parameter.
    21  	dockerNetSpecHostnameKey = "docker_sandbox_hostname"
    22  )
    23  
    24  func (d *Driver) CreateNetwork(allocID string, createSpec *drivers.NetworkCreateRequest) (*drivers.NetworkIsolationSpec, bool, error) {
    25  	// Initialize docker API clients
    26  	client, _, err := d.dockerClients()
    27  	if err != nil {
    28  		return nil, false, fmt.Errorf("failed to connect to docker daemon: %s", err)
    29  	}
    30  
    31  	if err := d.pullInfraImage(allocID); err != nil {
    32  		return nil, false, err
    33  	}
    34  
    35  	config, err := d.createSandboxContainerConfig(allocID, createSpec)
    36  	if err != nil {
    37  		return nil, false, err
    38  	}
    39  
    40  	specFromContainer := func(c *docker.Container, hostname string) *drivers.NetworkIsolationSpec {
    41  		spec := &drivers.NetworkIsolationSpec{
    42  			Mode: drivers.NetIsolationModeGroup,
    43  			Path: c.NetworkSettings.SandboxKey,
    44  			Labels: map[string]string{
    45  				dockerNetSpecLabelKey: c.ID,
    46  			},
    47  		}
    48  
    49  		// If the user supplied a hostname, set the label.
    50  		if hostname != "" {
    51  			spec.Labels[dockerNetSpecHostnameKey] = hostname
    52  		}
    53  
    54  		return spec
    55  	}
    56  
    57  	// We want to return a flag that tells us if the container already
    58  	// existed so that callers can decide whether or not to recreate
    59  	// the task's network namespace associations.
    60  	container, err := d.containerByName(config.Name)
    61  	if err != nil {
    62  		return nil, false, err
    63  	}
    64  	if container != nil && container.State.Running {
    65  		return specFromContainer(container, createSpec.Hostname), false, nil
    66  	}
    67  
    68  	container, err = d.createContainer(client, *config, d.config.InfraImage)
    69  	if err != nil {
    70  		return nil, false, err
    71  	}
    72  
    73  	if err = d.startContainer(container); err != nil {
    74  		return nil, false, err
    75  	}
    76  
    77  	// until the container is started, InspectContainerWithOptions
    78  	// returns a mostly-empty struct
    79  	container, err = client.InspectContainerWithOptions(docker.InspectContainerOptions{
    80  		ID: container.ID,
    81  	})
    82  	if err != nil {
    83  		return nil, false, err
    84  	}
    85  
    86  	return specFromContainer(container, createSpec.Hostname), true, nil
    87  }
    88  
    89  func (d *Driver) DestroyNetwork(allocID string, spec *drivers.NetworkIsolationSpec) error {
    90  	client, _, err := d.dockerClients()
    91  	if err != nil {
    92  		return fmt.Errorf("failed to connect to docker daemon: %s", err)
    93  	}
    94  
    95  	if err := client.RemoveContainer(docker.RemoveContainerOptions{
    96  		Force: true,
    97  		ID:    spec.Labels[dockerNetSpecLabelKey],
    98  	}); err != nil {
    99  		return err
   100  	}
   101  
   102  	if d.config.GC.Image {
   103  
   104  		// The Docker image ID is needed in order to correctly update the image
   105  		// reference count. Any error finding this, however, should not result
   106  		// in an error shutting down the allocrunner.
   107  		dockerImage, err := client.InspectImage(d.config.InfraImage)
   108  		if err != nil {
   109  			d.logger.Warn("InspectImage failed for infra_image container destroy",
   110  				"image", d.config.InfraImage, "error", err)
   111  			return nil
   112  		}
   113  		d.coordinator.RemoveImage(dockerImage.ID, allocID)
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  // createSandboxContainerConfig creates a docker container configuration which
   120  // starts a container with an empty network namespace.
   121  func (d *Driver) createSandboxContainerConfig(allocID string, createSpec *drivers.NetworkCreateRequest) (*docker.CreateContainerOptions, error) {
   122  
   123  	return &docker.CreateContainerOptions{
   124  		Name: fmt.Sprintf("nomad_init_%s", allocID),
   125  		Config: &docker.Config{
   126  			Image:    d.config.InfraImage,
   127  			Hostname: createSpec.Hostname,
   128  		},
   129  		HostConfig: &docker.HostConfig{
   130  			// Set the network mode to none which creates a network namespace
   131  			// with only a loopback interface.
   132  			NetworkMode: "none",
   133  		},
   134  	}, nil
   135  }
   136  
   137  // pullInfraImage conditionally pulls the `infra_image` from the Docker registry
   138  // only if its name uses the "latest" tag or the image doesn't already exist locally.
   139  func (d *Driver) pullInfraImage(allocID string) error {
   140  	repo, tag := parseDockerImage(d.config.InfraImage)
   141  
   142  	// There's a (narrow) time-of-check-time-of-use race here. If we call
   143  	// InspectImage and then a concurrent task shutdown happens before we call
   144  	// IncrementImageReference, we could end up removing the image, and it
   145  	// would no longer exist by the time we get to PullImage below.
   146  	d.coordinator.imageLock.Lock()
   147  
   148  	if tag != "latest" {
   149  		dockerImage, err := client.InspectImage(d.config.InfraImage)
   150  		if err != nil {
   151  			d.logger.Debug("InspectImage failed for infra_image container pull",
   152  				"image", d.config.InfraImage, "error", err)
   153  		} else if dockerImage != nil {
   154  			// Image exists, so no pull is attempted; just increment its reference
   155  			// count and unlock the image lock.
   156  			d.coordinator.incrementImageReferenceImpl(dockerImage.ID, d.config.InfraImage, allocID)
   157  			d.coordinator.imageLock.Unlock()
   158  			return nil
   159  		}
   160  	}
   161  
   162  	// At this point we have performed all the image work needed, so unlock. It
   163  	// is possible in environments with slow networks that the image pull may
   164  	// take a while, so while defer unlock would be best, this allows us to
   165  	// remove the lock sooner.
   166  	d.coordinator.imageLock.Unlock()
   167  
   168  	authOptions, err := firstValidAuth(repo, []authBackend{
   169  		authFromDockerConfig(d.config.Auth.Config),
   170  		authFromHelper(d.config.Auth.Helper),
   171  	})
   172  	if err != nil {
   173  		d.logger.Debug("auth failed for infra_image container pull", "image", d.config.InfraImage, "error", err)
   174  	}
   175  
   176  	_, err = d.coordinator.PullImage(d.config.InfraImage, authOptions, allocID, noopLogEventFn, d.config.infraImagePullTimeoutDuration, d.config.pullActivityTimeoutDuration)
   177  	return err
   178  }