github.com/hernad/nomad@v1.6.112/drivers/docker/fingerprint.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package docker
     5  
     6  import (
     7  	"context"
     8  	"runtime"
     9  	"sort"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/hernad/nomad/client/lib/cgutil"
    14  	"github.com/hernad/nomad/helper/pointer"
    15  	"github.com/hernad/nomad/plugins/drivers"
    16  	"github.com/hernad/nomad/plugins/drivers/utils"
    17  	pstructs "github.com/hernad/nomad/plugins/shared/structs"
    18  )
    19  
    20  func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) {
    21  	// Start docker reconcilers when we start fingerprinting, a workaround for
    22  	// task drivers not having a kind of post-setup hook.
    23  	d.danglingReconciler.Start()
    24  	d.cpusetFixer.Start()
    25  
    26  	ch := make(chan *drivers.Fingerprint)
    27  	go d.handleFingerprint(ctx, ch)
    28  	return ch, nil
    29  }
    30  
    31  func (d *Driver) previouslyDetected() bool {
    32  	d.detectedLock.RLock()
    33  	defer d.detectedLock.RUnlock()
    34  
    35  	return d.detected
    36  }
    37  
    38  func (d *Driver) setDetected(detected bool) {
    39  	d.detectedLock.Lock()
    40  	defer d.detectedLock.Unlock()
    41  
    42  	d.detected = detected
    43  }
    44  
    45  // setFingerprintSuccess marks the driver as having fingerprinted successfully
    46  func (d *Driver) setFingerprintSuccess() {
    47  	d.fingerprintLock.Lock()
    48  	d.fingerprintSuccess = pointer.Of(true)
    49  	d.fingerprintLock.Unlock()
    50  }
    51  
    52  // setFingerprintFailure marks the driver as having failed fingerprinting
    53  func (d *Driver) setFingerprintFailure() {
    54  	d.fingerprintLock.Lock()
    55  	d.fingerprintSuccess = pointer.Of(false)
    56  	d.fingerprintLock.Unlock()
    57  }
    58  
    59  // fingerprintSuccessful returns true if the driver has
    60  // never fingerprinted or has successfully fingerprinted
    61  func (d *Driver) fingerprintSuccessful() bool {
    62  	d.fingerprintLock.Lock()
    63  	defer d.fingerprintLock.Unlock()
    64  	return d.fingerprintSuccess == nil || *d.fingerprintSuccess
    65  }
    66  
    67  func (d *Driver) handleFingerprint(ctx context.Context, ch chan *drivers.Fingerprint) {
    68  	defer close(ch)
    69  
    70  	ticker := time.NewTimer(0)
    71  	defer ticker.Stop()
    72  
    73  	for {
    74  		select {
    75  		case <-ctx.Done():
    76  			return
    77  		case <-d.ctx.Done():
    78  			return
    79  		case <-ticker.C:
    80  			ticker.Reset(fingerprintPeriod)
    81  			ch <- d.buildFingerprint()
    82  		}
    83  	}
    84  }
    85  
    86  func (d *Driver) buildFingerprint() *drivers.Fingerprint {
    87  	fp := &drivers.Fingerprint{
    88  		Attributes:        make(map[string]*pstructs.Attribute, 8),
    89  		Health:            drivers.HealthStateHealthy,
    90  		HealthDescription: drivers.DriverHealthy,
    91  	}
    92  
    93  	// disable if cgv2 && non-root
    94  	if cgutil.UseV2 && !utils.IsUnixRoot() {
    95  		fp.Health = drivers.HealthStateUndetected
    96  		fp.HealthDescription = drivers.DriverRequiresRootMessage
    97  		d.setFingerprintFailure()
    98  		return fp
    99  	}
   100  
   101  	dockerClient, err := d.getDockerClient()
   102  	if err != nil {
   103  		if d.fingerprintSuccessful() {
   104  			d.logger.Info("failed to initialize client", "error", err)
   105  		}
   106  		d.setFingerprintFailure()
   107  		return &drivers.Fingerprint{
   108  			Health:            drivers.HealthStateUndetected,
   109  			HealthDescription: "Failed to initialize docker client",
   110  		}
   111  	}
   112  
   113  	env, err := dockerClient.Version()
   114  	if err != nil {
   115  		if d.fingerprintSuccessful() {
   116  			d.logger.Debug("could not connect to docker daemon", "endpoint", dockerClient.Endpoint(), "error", err)
   117  		}
   118  		d.setFingerprintFailure()
   119  
   120  		result := drivers.HealthStateUndetected
   121  		if d.previouslyDetected() {
   122  			result = drivers.HealthStateUnhealthy
   123  		}
   124  
   125  		return &drivers.Fingerprint{
   126  			Health:            result,
   127  			HealthDescription: "Failed to connect to docker daemon",
   128  		}
   129  	}
   130  
   131  	d.setDetected(true)
   132  	fp.Attributes["driver.docker"] = pstructs.NewBoolAttribute(true)
   133  	fp.Attributes["driver.docker.version"] = pstructs.NewStringAttribute(env.Get("Version"))
   134  	if d.config.AllowPrivileged {
   135  		fp.Attributes["driver.docker.privileged.enabled"] = pstructs.NewBoolAttribute(true)
   136  	}
   137  
   138  	if d.config.PidsLimit > 0 {
   139  		fp.Attributes["driver.docker.pids.limit"] = pstructs.NewIntAttribute(d.config.PidsLimit, "")
   140  	}
   141  
   142  	if d.config.Volumes.Enabled {
   143  		fp.Attributes["driver.docker.volumes.enabled"] = pstructs.NewBoolAttribute(true)
   144  	}
   145  
   146  	if nets, err := dockerClient.ListNetworks(); err != nil {
   147  		d.logger.Warn("error discovering bridge IP", "error", err)
   148  	} else {
   149  		for _, n := range nets {
   150  			if n.Name != "bridge" {
   151  				continue
   152  			}
   153  
   154  			if len(n.IPAM.Config) == 0 {
   155  				d.logger.Warn("no IPAM config for bridge network")
   156  				break
   157  			}
   158  
   159  			if n.IPAM.Config[0].Gateway != "" {
   160  				fp.Attributes["driver.docker.bridge_ip"] = pstructs.NewStringAttribute(n.IPAM.Config[0].Gateway)
   161  			} else if d.fingerprintSuccess == nil {
   162  				// Docker 17.09.0-ce dropped the Gateway IP from the bridge network
   163  				// See https://github.com/moby/moby/issues/32648
   164  				d.logger.Debug("bridge_ip could not be discovered")
   165  			}
   166  			break
   167  		}
   168  	}
   169  
   170  	if dockerInfo, err := dockerClient.Info(); err != nil {
   171  		d.logger.Warn("failed to get Docker system info", "error", err)
   172  	} else {
   173  		runtimeNames := make([]string, 0, len(dockerInfo.Runtimes))
   174  		for name := range dockerInfo.Runtimes {
   175  			if d.config.GPURuntimeName == name {
   176  				// Nvidia runtime is detected by Docker.
   177  				// It makes possible to run GPU workloads using Docker driver on this host.
   178  				d.gpuRuntime = true
   179  			}
   180  			runtimeNames = append(runtimeNames, name)
   181  		}
   182  		sort.Strings(runtimeNames)
   183  
   184  		fp.Attributes["driver.docker.runtimes"] = pstructs.NewStringAttribute(
   185  			strings.Join(runtimeNames, ","))
   186  		fp.Attributes["driver.docker.os_type"] = pstructs.NewStringAttribute(dockerInfo.OSType)
   187  
   188  		// If this situations arises, we are running in Windows 10 with Linux Containers enabled via VM
   189  		if runtime.GOOS == "windows" && dockerInfo.OSType == "linux" {
   190  			if d.fingerprintSuccessful() {
   191  				d.logger.Warn("Docker is configured with Linux containers; switch to Windows Containers")
   192  			}
   193  
   194  			d.setFingerprintFailure()
   195  			return &drivers.Fingerprint{
   196  				Health:            drivers.HealthStateUnhealthy,
   197  				HealthDescription: "Docker is configured with Linux containers; switch to Windows Containers",
   198  			}
   199  		}
   200  	}
   201  
   202  	d.setFingerprintSuccess()
   203  
   204  	return fp
   205  }