github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/client/driver/docker.go (about) 1 package driver 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "log" 8 "net" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "strconv" 14 "strings" 15 "sync" 16 "syscall" 17 "time" 18 19 "github.com/armon/circbuf" 20 "github.com/fsouza/go-dockerclient" 21 22 "github.com/docker/docker/cli/config/configfile" 23 "github.com/docker/docker/reference" 24 "github.com/docker/docker/registry" 25 26 "github.com/armon/go-metrics" 27 "github.com/hashicorp/go-multierror" 28 "github.com/hashicorp/go-plugin" 29 "github.com/hashicorp/nomad/client/allocdir" 30 "github.com/hashicorp/nomad/client/driver/env" 31 "github.com/hashicorp/nomad/client/driver/executor" 32 dstructs "github.com/hashicorp/nomad/client/driver/structs" 33 cstructs "github.com/hashicorp/nomad/client/structs" 34 "github.com/hashicorp/nomad/helper" 35 "github.com/hashicorp/nomad/helper/fields" 36 shelpers "github.com/hashicorp/nomad/helper/stats" 37 "github.com/hashicorp/nomad/nomad/structs" 38 "github.com/mitchellh/mapstructure" 39 ) 40 41 var ( 42 // createClientsLock is a lock that protects reading/writing global client 43 // variables 44 createClientsLock sync.Mutex 45 46 // client is a docker client with a timeout of 5 minutes. This is for doing 47 // all operations with the docker daemon besides which are not long running 48 // such as creating, killing containers, etc. 49 client *docker.Client 50 51 // waitClient is a docker client with no timeouts. This is used for long 52 // running operations such as waiting on containers and collect stats 53 waitClient *docker.Client 54 55 // healthCheckClient is a docker client with a timeout of 1 minute. This is 56 // necessary to have a shorter timeout than other API or fingerprint calls 57 healthCheckClient *docker.Client 58 59 // The statistics the Docker driver exposes 60 DockerMeasuredMemStats = []string{"RSS", "Cache", "Swap", "Max Usage"} 61 DockerMeasuredCpuStats = []string{"Throttled Periods", "Throttled Time", "Percent"} 62 63 // recoverableErrTimeouts returns a recoverable error if the error was due 64 // to timeouts 65 recoverableErrTimeouts = func(err error) error { 66 r := false 67 if strings.Contains(err.Error(), "Client.Timeout exceeded while awaiting headers") || 68 strings.Contains(err.Error(), "EOF") { 69 r = true 70 } 71 return structs.NewRecoverableError(err, r) 72 } 73 ) 74 75 const ( 76 // NoSuchContainerError is returned by the docker daemon if the container 77 // does not exist. 78 NoSuchContainerError = "No such container" 79 80 // The key populated in Node Attributes to indicate presence of the Docker 81 // driver 82 dockerDriverAttr = "driver.docker" 83 84 // dockerSELinuxLabelConfigOption is the key for configuring the 85 // SELinux label for binds. 86 dockerSELinuxLabelConfigOption = "docker.volumes.selinuxlabel" 87 88 // dockerVolumesConfigOption is the key for enabling the use of custom 89 // bind volumes to arbitrary host paths. 90 dockerVolumesConfigOption = "docker.volumes.enabled" 91 dockerVolumesConfigDefault = true 92 93 // dockerPrivilegedConfigOption is the key for running containers in 94 // Docker's privileged mode. 95 dockerPrivilegedConfigOption = "docker.privileged.enabled" 96 97 // dockerCleanupImageConfigOption is the key for whether or not to 98 // cleanup images after the task exits. 99 dockerCleanupImageConfigOption = "docker.cleanup.image" 100 dockerCleanupImageConfigDefault = true 101 102 // dockerPullTimeoutConfigOption is the key for setting an images pull 103 // timeout 104 dockerImageRemoveDelayConfigOption = "docker.cleanup.image.delay" 105 dockerImageRemoveDelayConfigDefault = 3 * time.Minute 106 107 // dockerCapsWhitelistConfigOption is the key for setting the list of 108 // allowed Linux capabilities 109 dockerCapsWhitelistConfigOption = "docker.caps.whitelist" 110 dockerCapsWhitelistConfigDefault = dockerBasicCaps 111 112 // dockerTimeout is the length of time a request can be outstanding before 113 // it is timed out. 114 dockerTimeout = 5 * time.Minute 115 116 // dockerHealthCheckTimeout is the length of time a request for a health 117 // check client can be outstanding before it is timed out. 118 dockerHealthCheckTimeout = 1 * time.Minute 119 120 // dockerImageResKey is the CreatedResources key for docker images 121 dockerImageResKey = "image" 122 123 // dockerAuthHelperPrefix is the prefix to attach to the credential helper 124 // and should be found in the $PATH. Example: ${prefix-}${helper-name} 125 dockerAuthHelperPrefix = "docker-credential-" 126 127 // dockerBasicCaps is comma-separated list of Linux capabilities that are 128 // allowed by docker by default, as documented in 129 // https://docs.docker.com/engine/reference/run/#block-io-bandwidth-blkio-constraint 130 dockerBasicCaps = "CHOWN,DAC_OVERRIDE,FSETID,FOWNER,MKNOD,NET_RAW,SETGID," + 131 "SETUID,SETFCAP,SETPCAP,NET_BIND_SERVICE,SYS_CHROOT,KILL,AUDIT_WRITE" 132 133 // This is cpu.cfs_period_us: the length of a period. 134 // The default values is 100 milliseconds (ms) represented in microseconds (us). 135 // Below is the documentation: 136 // https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt 137 // https://docs.docker.com/engine/api/v1.35/# 138 defaultCFSPeriodUS = 100000 139 140 // dockerCleanupContainerConfigOption is the key for whether or not to 141 // remove containers after the task exits. 142 dockerCleanupContainerConfigOption = "docker.cleanup.container" 143 dockerCleanupContainerConfigDefault = true 144 ) 145 146 type DockerDriver struct { 147 DriverContext 148 149 driverConfig *DockerDriverConfig 150 imageID string 151 152 // A tri-state boolean to know if the fingerprinting has happened and 153 // whether it has been successful 154 fingerprintSuccess *bool 155 } 156 157 type DockerDriverAuth struct { 158 Username string `mapstructure:"username"` // username for the registry 159 Password string `mapstructure:"password"` // password to access the registry 160 Email string `mapstructure:"email"` // email address of the user who is allowed to access the registry 161 ServerAddress string `mapstructure:"server_address"` // server address of the registry 162 } 163 164 type DockerLoggingOpts struct { 165 Type string `mapstructure:"type"` 166 ConfigRaw []map[string]string `mapstructure:"config"` 167 Config map[string]string `mapstructure:"-"` 168 } 169 170 type DockerMount struct { 171 Target string `mapstructure:"target"` 172 Source string `mapstructure:"source"` 173 ReadOnly bool `mapstructure:"readonly"` 174 VolumeOptions []*DockerVolumeOptions `mapstructure:"volume_options"` 175 } 176 177 type DockerDevice struct { 178 HostPath string `mapstructure:"host_path"` 179 ContainerPath string `mapstructure:"container_path"` 180 CgroupPermissions string `mapstructure:"cgroup_permissions"` 181 } 182 183 type DockerVolumeOptions struct { 184 NoCopy bool `mapstructure:"no_copy"` 185 Labels []map[string]string `mapstructure:"labels"` 186 DriverConfig []DockerVolumeDriverConfig `mapstructure:"driver_config"` 187 } 188 189 // VolumeDriverConfig holds a map of volume driver specific options 190 type DockerVolumeDriverConfig struct { 191 Name string `mapstructure:"name"` 192 Options []map[string]string `mapstructure:"options"` 193 } 194 195 // DockerDriverConfig defines the user specified config block in a jobspec 196 type DockerDriverConfig struct { 197 ImageName string `mapstructure:"image"` // Container's Image Name 198 LoadImage string `mapstructure:"load"` // LoadImage is a path to an image archive file 199 Command string `mapstructure:"command"` // The Command to run when the container starts up 200 Args []string `mapstructure:"args"` // The arguments to the Command 201 Entrypoint []string `mapstructure:"entrypoint"` // Override the containers entrypoint 202 IpcMode string `mapstructure:"ipc_mode"` // The IPC mode of the container - host and none 203 NetworkMode string `mapstructure:"network_mode"` // The network mode of the container - host, nat and none 204 NetworkAliases []string `mapstructure:"network_aliases"` // The network-scoped alias for the container 205 IPv4Address string `mapstructure:"ipv4_address"` // The container ipv4 address 206 IPv6Address string `mapstructure:"ipv6_address"` // the container ipv6 address 207 PidMode string `mapstructure:"pid_mode"` // The PID mode of the container - host and none 208 UTSMode string `mapstructure:"uts_mode"` // The UTS mode of the container - host and none 209 UsernsMode string `mapstructure:"userns_mode"` // The User namespace mode of the container - host and none 210 PortMapRaw []map[string]string `mapstructure:"port_map"` // 211 PortMap map[string]int `mapstructure:"-"` // A map of host port labels and the ports exposed on the container 212 Privileged bool `mapstructure:"privileged"` // Flag to run the container in privileged mode 213 SysctlRaw []map[string]string `mapstructure:"sysctl"` // 214 Sysctl map[string]string `mapstructure:"-"` // The sysctl custom configurations 215 UlimitRaw []map[string]string `mapstructure:"ulimit"` // 216 Ulimit []docker.ULimit `mapstructure:"-"` // The ulimit custom configurations 217 DNSServers []string `mapstructure:"dns_servers"` // DNS Server for containers 218 DNSSearchDomains []string `mapstructure:"dns_search_domains"` // DNS Search domains for containers 219 DNSOptions []string `mapstructure:"dns_options"` // DNS Options 220 ExtraHosts []string `mapstructure:"extra_hosts"` // Add host to /etc/hosts (host:IP) 221 Hostname string `mapstructure:"hostname"` // Hostname for containers 222 LabelsRaw []map[string]string `mapstructure:"labels"` // 223 Labels map[string]string `mapstructure:"-"` // Labels to set when the container starts up 224 Auth []DockerDriverAuth `mapstructure:"auth"` // Authentication credentials for a private Docker registry 225 AuthSoftFail bool `mapstructure:"auth_soft_fail"` // Soft-fail if auth creds are provided but fail 226 TTY bool `mapstructure:"tty"` // Allocate a Pseudo-TTY 227 Interactive bool `mapstructure:"interactive"` // Keep STDIN open even if not attached 228 ShmSize int64 `mapstructure:"shm_size"` // Size of /dev/shm of the container in bytes 229 WorkDir string `mapstructure:"work_dir"` // Working directory inside the container 230 Logging []DockerLoggingOpts `mapstructure:"logging"` // Logging options for syslog server 231 Volumes []string `mapstructure:"volumes"` // Host-Volumes to mount in, syntax: /path/to/host/directory:/destination/path/in/container 232 Mounts []DockerMount `mapstructure:"mounts"` // Docker volumes to mount 233 VolumeDriver string `mapstructure:"volume_driver"` // Docker volume driver used for the container's volumes 234 ForcePull bool `mapstructure:"force_pull"` // Always force pull before running image, useful if your tags are mutable 235 MacAddress string `mapstructure:"mac_address"` // Pin mac address to container 236 SecurityOpt []string `mapstructure:"security_opt"` // Flags to pass directly to security-opt 237 Devices []DockerDevice `mapstructure:"devices"` // To allow mounting USB or other serial control devices 238 CapAdd []string `mapstructure:"cap_add"` // Flags to pass directly to cap-add 239 CapDrop []string `mapstructure:"cap_drop"` // Flags to pass directly to cap-drop 240 ReadonlyRootfs bool `mapstructure:"readonly_rootfs"` // Mount the container’s root filesystem as read only 241 AdvertiseIPv6Address bool `mapstructure:"advertise_ipv6_address"` // Flag to use the GlobalIPv6Address from the container as the detected IP 242 CPUHardLimit bool `mapstructure:"cpu_hard_limit"` // Enforce CPU hard limit. 243 CPUCFSPeriod int64 `mapstructure:"cpu_cfs_period"` // Set the period for the CFS scheduler for the cgroup. 244 PidsLimit int64 `mapstructure:"pids_limit"` // Enforce Docker Pids limit 245 } 246 247 func sliceMergeUlimit(ulimitsRaw map[string]string) ([]docker.ULimit, error) { 248 var ulimits []docker.ULimit 249 250 for name, ulimitRaw := range ulimitsRaw { 251 if len(ulimitRaw) == 0 { 252 return []docker.ULimit{}, fmt.Errorf("Malformed ulimit specification %v: %q, cannot be empty", name, ulimitRaw) 253 } 254 // hard limit is optional 255 if strings.Contains(ulimitRaw, ":") == false { 256 ulimitRaw = ulimitRaw + ":" + ulimitRaw 257 } 258 259 splitted := strings.SplitN(ulimitRaw, ":", 2) 260 if len(splitted) < 2 { 261 return []docker.ULimit{}, fmt.Errorf("Malformed ulimit specification %v: %v", name, ulimitRaw) 262 } 263 soft, err := strconv.Atoi(splitted[0]) 264 if err != nil { 265 return []docker.ULimit{}, fmt.Errorf("Malformed soft ulimit %v: %v", name, ulimitRaw) 266 } 267 hard, err := strconv.Atoi(splitted[1]) 268 if err != nil { 269 return []docker.ULimit{}, fmt.Errorf("Malformed hard ulimit %v: %v", name, ulimitRaw) 270 } 271 272 ulimit := docker.ULimit{ 273 Name: name, 274 Soft: int64(soft), 275 Hard: int64(hard), 276 } 277 ulimits = append(ulimits, ulimit) 278 } 279 return ulimits, nil 280 } 281 282 // Validate validates a docker driver config 283 func (c *DockerDriverConfig) Validate() error { 284 if c.ImageName == "" { 285 return fmt.Errorf("Docker Driver needs an image name") 286 } 287 if len(c.Devices) > 0 { 288 for _, dev := range c.Devices { 289 if dev.HostPath == "" { 290 return fmt.Errorf("host path must be set in configuration for devices") 291 } 292 if dev.CgroupPermissions != "" { 293 for _, c := range dev.CgroupPermissions { 294 ch := string(c) 295 if ch != "r" && ch != "w" && ch != "m" { 296 return fmt.Errorf("invalid cgroup permission string: %q", dev.CgroupPermissions) 297 } 298 } 299 } 300 } 301 } 302 c.Sysctl = mapMergeStrStr(c.SysctlRaw...) 303 c.Labels = mapMergeStrStr(c.LabelsRaw...) 304 if len(c.Logging) > 0 { 305 c.Logging[0].Config = mapMergeStrStr(c.Logging[0].ConfigRaw...) 306 } 307 308 mergedUlimitsRaw := mapMergeStrStr(c.UlimitRaw...) 309 ulimit, err := sliceMergeUlimit(mergedUlimitsRaw) 310 if err != nil { 311 return err 312 } 313 c.Ulimit = ulimit 314 return nil 315 } 316 317 // NewDockerDriverConfig returns a docker driver config by parsing the HCL 318 // config 319 func NewDockerDriverConfig(task *structs.Task, env *env.TaskEnv) (*DockerDriverConfig, error) { 320 var dconf DockerDriverConfig 321 322 if err := mapstructure.WeakDecode(task.Config, &dconf); err != nil { 323 return nil, err 324 } 325 326 // Interpolate everything that is a string 327 dconf.ImageName = env.ReplaceEnv(dconf.ImageName) 328 dconf.Command = env.ReplaceEnv(dconf.Command) 329 dconf.Entrypoint = env.ParseAndReplace(dconf.Entrypoint) 330 dconf.IpcMode = env.ReplaceEnv(dconf.IpcMode) 331 dconf.NetworkMode = env.ReplaceEnv(dconf.NetworkMode) 332 dconf.NetworkAliases = env.ParseAndReplace(dconf.NetworkAliases) 333 dconf.IPv4Address = env.ReplaceEnv(dconf.IPv4Address) 334 dconf.IPv6Address = env.ReplaceEnv(dconf.IPv6Address) 335 dconf.PidMode = env.ReplaceEnv(dconf.PidMode) 336 dconf.UTSMode = env.ReplaceEnv(dconf.UTSMode) 337 dconf.Hostname = env.ReplaceEnv(dconf.Hostname) 338 dconf.WorkDir = env.ReplaceEnv(dconf.WorkDir) 339 dconf.LoadImage = env.ReplaceEnv(dconf.LoadImage) 340 dconf.Volumes = env.ParseAndReplace(dconf.Volumes) 341 dconf.VolumeDriver = env.ReplaceEnv(dconf.VolumeDriver) 342 dconf.DNSServers = env.ParseAndReplace(dconf.DNSServers) 343 dconf.DNSSearchDomains = env.ParseAndReplace(dconf.DNSSearchDomains) 344 dconf.DNSOptions = env.ParseAndReplace(dconf.DNSOptions) 345 dconf.ExtraHosts = env.ParseAndReplace(dconf.ExtraHosts) 346 dconf.MacAddress = env.ReplaceEnv(dconf.MacAddress) 347 dconf.SecurityOpt = env.ParseAndReplace(dconf.SecurityOpt) 348 dconf.CapAdd = env.ParseAndReplace(dconf.CapAdd) 349 dconf.CapDrop = env.ParseAndReplace(dconf.CapDrop) 350 351 for _, m := range dconf.SysctlRaw { 352 for k, v := range m { 353 delete(m, k) 354 m[env.ReplaceEnv(k)] = env.ReplaceEnv(v) 355 } 356 } 357 358 for _, m := range dconf.UlimitRaw { 359 for k, v := range m { 360 delete(m, k) 361 m[env.ReplaceEnv(k)] = env.ReplaceEnv(v) 362 } 363 } 364 365 for _, m := range dconf.LabelsRaw { 366 for k, v := range m { 367 delete(m, k) 368 m[env.ReplaceEnv(k)] = env.ReplaceEnv(v) 369 } 370 } 371 dconf.Labels = mapMergeStrStr(dconf.LabelsRaw...) 372 373 for i, a := range dconf.Auth { 374 dconf.Auth[i].Username = env.ReplaceEnv(a.Username) 375 dconf.Auth[i].Password = env.ReplaceEnv(a.Password) 376 dconf.Auth[i].Email = env.ReplaceEnv(a.Email) 377 dconf.Auth[i].ServerAddress = env.ReplaceEnv(a.ServerAddress) 378 } 379 380 for i, l := range dconf.Logging { 381 dconf.Logging[i].Type = env.ReplaceEnv(l.Type) 382 for _, c := range l.ConfigRaw { 383 for k, v := range c { 384 delete(c, k) 385 c[env.ReplaceEnv(k)] = env.ReplaceEnv(v) 386 } 387 } 388 } 389 390 for i, m := range dconf.Mounts { 391 dconf.Mounts[i].Target = env.ReplaceEnv(m.Target) 392 dconf.Mounts[i].Source = env.ReplaceEnv(m.Source) 393 394 if len(m.VolumeOptions) > 1 { 395 return nil, fmt.Errorf("Only one volume_options stanza allowed") 396 } 397 398 if len(m.VolumeOptions) == 1 { 399 vo := m.VolumeOptions[0] 400 if len(vo.Labels) > 1 { 401 return nil, fmt.Errorf("labels may only be specified once in volume_options stanza") 402 } 403 404 if len(vo.Labels) == 1 { 405 for k, v := range vo.Labels[0] { 406 if k != env.ReplaceEnv(k) { 407 delete(vo.Labels[0], k) 408 } 409 vo.Labels[0][env.ReplaceEnv(k)] = env.ReplaceEnv(v) 410 } 411 } 412 413 if len(vo.DriverConfig) > 1 { 414 return nil, fmt.Errorf("volume driver config may only be specified once") 415 } 416 if len(vo.DriverConfig) == 1 { 417 vo.DriverConfig[0].Name = env.ReplaceEnv(vo.DriverConfig[0].Name) 418 if len(vo.DriverConfig[0].Options) > 1 { 419 return nil, fmt.Errorf("volume driver options may only be specified once") 420 } 421 422 if len(vo.DriverConfig[0].Options) == 1 { 423 options := vo.DriverConfig[0].Options[0] 424 for k, v := range options { 425 if k != env.ReplaceEnv(k) { 426 delete(options, k) 427 } 428 options[env.ReplaceEnv(k)] = env.ReplaceEnv(v) 429 } 430 } 431 } 432 } 433 } 434 435 if len(dconf.Logging) > 0 { 436 dconf.Logging[0].Config = mapMergeStrStr(dconf.Logging[0].ConfigRaw...) 437 } 438 439 portMap := make(map[string]int) 440 for _, m := range dconf.PortMapRaw { 441 for k, v := range m { 442 ki, vi := env.ReplaceEnv(k), env.ReplaceEnv(v) 443 p, err := strconv.Atoi(vi) 444 if err != nil { 445 return nil, fmt.Errorf("failed to parse port map value %v to %v: %v", ki, vi, err) 446 } 447 portMap[ki] = p 448 } 449 } 450 dconf.PortMap = portMap 451 452 // Remove any http 453 if strings.Contains(dconf.ImageName, "https://") { 454 dconf.ImageName = strings.Replace(dconf.ImageName, "https://", "", 1) 455 } 456 457 // If devices are configured set default cgroup permissions 458 if len(dconf.Devices) > 0 { 459 for i, dev := range dconf.Devices { 460 if dev.CgroupPermissions == "" { 461 dev.CgroupPermissions = "rwm" 462 } 463 dconf.Devices[i] = dev 464 } 465 } 466 467 if err := dconf.Validate(); err != nil { 468 return nil, err 469 } 470 return &dconf, nil 471 } 472 473 type dockerPID struct { 474 Version string 475 Image string 476 ImageID string 477 ContainerID string 478 KillTimeout time.Duration 479 MaxKillTimeout time.Duration 480 PluginConfig *PluginReattachConfig 481 } 482 483 type DockerHandle struct { 484 pluginClient *plugin.Client 485 executor executor.Executor 486 client *docker.Client 487 waitClient *docker.Client 488 logger *log.Logger 489 jobName string 490 taskGroupName string 491 taskName string 492 Image string 493 ImageID string 494 containerID string 495 version string 496 killTimeout time.Duration 497 maxKillTimeout time.Duration 498 resourceUsageLock sync.RWMutex 499 resourceUsage *cstructs.TaskResourceUsage 500 waitCh chan *dstructs.WaitResult 501 doneCh chan bool 502 removeContainerOnExit bool 503 } 504 505 func NewDockerDriver(ctx *DriverContext) Driver { 506 return &DockerDriver{DriverContext: *ctx} 507 } 508 509 func (d *DockerDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error { 510 client, _, err := d.dockerClients() 511 if err != nil { 512 if d.fingerprintSuccess == nil || *d.fingerprintSuccess { 513 d.logger.Printf("[INFO] driver.docker: failed to initialize client: %s", err) 514 } 515 d.fingerprintSuccess = helper.BoolToPtr(false) 516 return nil 517 } 518 519 // This is the first operation taken on the client so we'll try to 520 // establish a connection to the Docker daemon. If this fails it means 521 // Docker isn't available so we'll simply disable the docker driver. 522 env, err := client.Version() 523 if err != nil { 524 if d.fingerprintSuccess == nil || *d.fingerprintSuccess { 525 d.logger.Printf("[DEBUG] driver.docker: could not connect to docker daemon at %s: %s", client.Endpoint(), err) 526 } 527 d.fingerprintSuccess = helper.BoolToPtr(false) 528 resp.RemoveAttribute(dockerDriverAttr) 529 return nil 530 } 531 532 resp.AddAttribute(dockerDriverAttr, "1") 533 resp.AddAttribute("driver.docker.version", env.Get("Version")) 534 resp.Detected = true 535 536 privileged := d.config.ReadBoolDefault(dockerPrivilegedConfigOption, false) 537 if privileged { 538 resp.AddAttribute(dockerPrivilegedConfigOption, "1") 539 } 540 541 // Advertise if this node supports Docker volumes 542 if d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault) { 543 resp.AddAttribute("driver."+dockerVolumesConfigOption, "1") 544 } 545 546 // Detect bridge IP address - #2785 547 if nets, err := client.ListNetworks(); err != nil { 548 d.logger.Printf("[WARN] driver.docker: error discovering bridge IP: %v", err) 549 } else { 550 for _, n := range nets { 551 if n.Name != "bridge" { 552 continue 553 } 554 555 if len(n.IPAM.Config) == 0 { 556 d.logger.Printf("[WARN] driver.docker: no IPAM config for bridge network") 557 break 558 } 559 560 if n.IPAM.Config[0].Gateway != "" { 561 resp.AddAttribute("driver.docker.bridge_ip", n.IPAM.Config[0].Gateway) 562 } else if d.fingerprintSuccess == nil { 563 // Docker 17.09.0-ce dropped the Gateway IP from the bridge network 564 // See https://github.com/moby/moby/issues/32648 565 d.logger.Printf("[DEBUG] driver.docker: bridge_ip could not be discovered") 566 } 567 break 568 } 569 } 570 571 d.fingerprintSuccess = helper.BoolToPtr(true) 572 return nil 573 } 574 575 // HealthCheck implements the interface for the HealthCheck interface. This 576 // performs a health check on the docker driver, asserting whether the docker 577 // driver is responsive to a `docker ps` command. 578 func (d *DockerDriver) HealthCheck(req *cstructs.HealthCheckRequest, resp *cstructs.HealthCheckResponse) error { 579 dinfo := &structs.DriverInfo{ 580 UpdateTime: time.Now(), 581 } 582 583 healthCheckClient, err := d.dockerHealthCheckClient() 584 if err != nil { 585 d.logger.Printf("[WARN] driver.docker: failed to retrieve Docker client in the process of a docker health check: %v", err) 586 dinfo.HealthDescription = fmt.Sprintf("Failed retrieving Docker client: %v", err) 587 resp.AddDriverInfo("docker", dinfo) 588 return nil 589 } 590 591 _, err = healthCheckClient.ListContainers(docker.ListContainersOptions{All: false}) 592 if err != nil { 593 d.logger.Printf("[WARN] driver.docker: failed to list Docker containers in the process of a Docker health check: %v", err) 594 dinfo.HealthDescription = fmt.Sprintf("Failed to list Docker containers: %v", err) 595 resp.AddDriverInfo("docker", dinfo) 596 return nil 597 } 598 599 d.logger.Printf("[TRACE] driver.docker: docker driver is available and is responsive to `docker ps`") 600 dinfo.Healthy = true 601 dinfo.HealthDescription = "Driver is available and responsive" 602 resp.AddDriverInfo("docker", dinfo) 603 return nil 604 } 605 606 // GetHealthChecks implements the interface for the HealthCheck interface. This 607 // sets whether the driver is eligible for periodic health checks and the 608 // interval at which to do them. 609 func (d *DockerDriver) GetHealthCheckInterval(req *cstructs.HealthCheckIntervalRequest, resp *cstructs.HealthCheckIntervalResponse) error { 610 resp.Eligible = true 611 resp.Period = 1 * time.Minute 612 return nil 613 } 614 615 // Validate is used to validate the driver configuration 616 func (d *DockerDriver) Validate(config map[string]interface{}) error { 617 fd := &fields.FieldData{ 618 Raw: config, 619 Schema: map[string]*fields.FieldSchema{ 620 "image": { 621 Type: fields.TypeString, 622 Required: true, 623 }, 624 "load": { 625 Type: fields.TypeString, 626 }, 627 "command": { 628 Type: fields.TypeString, 629 }, 630 "args": { 631 Type: fields.TypeArray, 632 }, 633 "entrypoint": { 634 Type: fields.TypeArray, 635 }, 636 "ipc_mode": { 637 Type: fields.TypeString, 638 }, 639 "network_mode": { 640 Type: fields.TypeString, 641 }, 642 "network_aliases": { 643 Type: fields.TypeArray, 644 }, 645 "ipv4_address": { 646 Type: fields.TypeString, 647 }, 648 "ipv6_address": { 649 Type: fields.TypeString, 650 }, 651 "mac_address": { 652 Type: fields.TypeString, 653 }, 654 "pid_mode": { 655 Type: fields.TypeString, 656 }, 657 "uts_mode": { 658 Type: fields.TypeString, 659 }, 660 "userns_mode": { 661 Type: fields.TypeString, 662 }, 663 "sysctl": { 664 Type: fields.TypeArray, 665 }, 666 "ulimit": { 667 Type: fields.TypeArray, 668 }, 669 "port_map": { 670 Type: fields.TypeArray, 671 }, 672 "privileged": { 673 Type: fields.TypeBool, 674 }, 675 "dns_servers": { 676 Type: fields.TypeArray, 677 }, 678 "dns_options": { 679 Type: fields.TypeArray, 680 }, 681 "dns_search_domains": { 682 Type: fields.TypeArray, 683 }, 684 "extra_hosts": { 685 Type: fields.TypeArray, 686 }, 687 "hostname": { 688 Type: fields.TypeString, 689 }, 690 "labels": { 691 Type: fields.TypeArray, 692 }, 693 "auth": { 694 Type: fields.TypeArray, 695 }, 696 "auth_soft_fail": { 697 Type: fields.TypeBool, 698 }, 699 // COMPAT: Remove in 0.6.0. SSL is no longer needed 700 "ssl": { 701 Type: fields.TypeBool, 702 }, 703 "tty": { 704 Type: fields.TypeBool, 705 }, 706 "interactive": { 707 Type: fields.TypeBool, 708 }, 709 "shm_size": { 710 Type: fields.TypeInt, 711 }, 712 "work_dir": { 713 Type: fields.TypeString, 714 }, 715 "logging": { 716 Type: fields.TypeArray, 717 }, 718 "volumes": { 719 Type: fields.TypeArray, 720 }, 721 "volume_driver": { 722 Type: fields.TypeString, 723 }, 724 "mounts": { 725 Type: fields.TypeArray, 726 }, 727 "force_pull": { 728 Type: fields.TypeBool, 729 }, 730 "security_opt": { 731 Type: fields.TypeArray, 732 }, 733 "devices": { 734 Type: fields.TypeArray, 735 }, 736 "cap_add": { 737 Type: fields.TypeArray, 738 }, 739 "cap_drop": { 740 Type: fields.TypeArray, 741 }, 742 "readonly_rootfs": { 743 Type: fields.TypeBool, 744 }, 745 "advertise_ipv6_address": { 746 Type: fields.TypeBool, 747 }, 748 "cpu_hard_limit": { 749 Type: fields.TypeBool, 750 }, 751 "cpu_cfs_period": { 752 Type: fields.TypeInt, 753 }, 754 "pids_limit": { 755 Type: fields.TypeInt, 756 }, 757 }, 758 } 759 760 if err := fd.Validate(); err != nil { 761 return err 762 } 763 764 return nil 765 } 766 767 func (d *DockerDriver) Abilities() DriverAbilities { 768 return DriverAbilities{ 769 SendSignals: true, 770 Exec: true, 771 } 772 } 773 774 func (d *DockerDriver) FSIsolation() cstructs.FSIsolation { 775 return cstructs.FSIsolationImage 776 } 777 778 // getDockerCoordinator returns the docker coordinator and the caller ID to use when 779 // interacting with the coordinator 780 func (d *DockerDriver) getDockerCoordinator(client *docker.Client) (*dockerCoordinator, string) { 781 config := &dockerCoordinatorConfig{ 782 client: client, 783 cleanup: d.config.ReadBoolDefault(dockerCleanupImageConfigOption, dockerCleanupImageConfigDefault), 784 logger: d.logger, 785 removeDelay: d.config.ReadDurationDefault(dockerImageRemoveDelayConfigOption, dockerImageRemoveDelayConfigDefault), 786 } 787 788 return GetDockerCoordinator(config), fmt.Sprintf("%s-%s", d.DriverContext.allocID, d.DriverContext.taskName) 789 } 790 791 func (d *DockerDriver) Prestart(ctx *ExecContext, task *structs.Task) (*PrestartResponse, error) { 792 driverConfig, err := NewDockerDriverConfig(task, ctx.TaskEnv) 793 if err != nil { 794 return nil, err 795 } 796 797 // Set state needed by Start 798 d.driverConfig = driverConfig 799 800 // Initialize docker API clients 801 client, _, err := d.dockerClients() 802 if err != nil { 803 return nil, fmt.Errorf("Failed to connect to docker daemon: %s", err) 804 } 805 806 // Ensure the image is available 807 id, err := d.createImage(driverConfig, client, ctx.TaskDir) 808 if err != nil { 809 return nil, err 810 } 811 d.imageID = id 812 813 resp := NewPrestartResponse() 814 resp.CreatedResources.Add(dockerImageResKey, id) 815 816 // Return the PortMap if it's set 817 if len(driverConfig.PortMap) > 0 { 818 resp.Network = &cstructs.DriverNetwork{ 819 PortMap: driverConfig.PortMap, 820 } 821 } 822 return resp, nil 823 } 824 825 func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) { 826 pluginLogFile := filepath.Join(ctx.TaskDir.Dir, "executor.out") 827 executorConfig := &dstructs.ExecutorConfig{ 828 LogFile: pluginLogFile, 829 LogLevel: d.config.LogLevel, 830 } 831 832 exec, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig) 833 if err != nil { 834 return nil, err 835 } 836 executorCtx := &executor.ExecutorContext{ 837 TaskEnv: ctx.TaskEnv, 838 Task: task, 839 Driver: "docker", 840 LogDir: ctx.TaskDir.LogDir, 841 TaskDir: ctx.TaskDir.Dir, 842 PortLowerBound: d.config.ClientMinPort, 843 PortUpperBound: d.config.ClientMaxPort, 844 } 845 if err := exec.SetContext(executorCtx); err != nil { 846 pluginClient.Kill() 847 return nil, fmt.Errorf("failed to set executor context: %v", err) 848 } 849 850 // The user hasn't specified any logging options so launch our own syslog 851 // server if possible. 852 syslogAddr := "" 853 if len(d.driverConfig.Logging) == 0 { 854 if runtime.GOOS == "darwin" { 855 d.logger.Printf("[DEBUG] driver.docker: disabling syslog driver as Docker for Mac workaround") 856 } else { 857 ss, err := exec.LaunchSyslogServer() 858 if err != nil { 859 pluginClient.Kill() 860 return nil, fmt.Errorf("failed to start syslog collector: %v", err) 861 } 862 syslogAddr = ss.Addr 863 } 864 } 865 866 config, err := d.createContainerConfig(ctx, task, d.driverConfig, syslogAddr) 867 if err != nil { 868 d.logger.Printf("[ERR] driver.docker: failed to create container configuration for image %q (%q): %v", d.driverConfig.ImageName, d.imageID, err) 869 pluginClient.Kill() 870 return nil, fmt.Errorf("Failed to create container configuration for image %q (%q): %v", d.driverConfig.ImageName, d.imageID, err) 871 } 872 873 container, err := d.createContainer(client, config) 874 if err != nil { 875 wrapped := fmt.Sprintf("Failed to create container: %v", err) 876 d.logger.Printf("[ERR] driver.docker: %s", wrapped) 877 pluginClient.Kill() 878 return nil, structs.WrapRecoverable(wrapped, err) 879 } 880 881 d.logger.Printf("[INFO] driver.docker: created container %s", container.ID) 882 883 // We don't need to start the container if the container is already running 884 // since we don't create containers which are already present on the host 885 // and are running 886 if !container.State.Running { 887 // Start the container 888 if err := d.startContainer(container); err != nil { 889 d.logger.Printf("[ERR] driver.docker: failed to start container %s: %s", container.ID, err) 890 pluginClient.Kill() 891 return nil, structs.NewRecoverableError(fmt.Errorf("Failed to start container %s: %s", container.ID, err), structs.IsRecoverable(err)) 892 } 893 894 // InspectContainer to get all of the container metadata as 895 // much of the metadata (eg networking) isn't populated until 896 // the container is started 897 runningContainer, err := client.InspectContainer(container.ID) 898 if err != nil { 899 err = fmt.Errorf("failed to inspect started container %s: %s", container.ID, err) 900 d.logger.Printf("[ERR] driver.docker: %v", err) 901 pluginClient.Kill() 902 return nil, structs.NewRecoverableError(err, true) 903 } 904 container = runningContainer 905 d.logger.Printf("[INFO] driver.docker: started container %s", container.ID) 906 } else { 907 d.logger.Printf("[DEBUG] driver.docker: re-attaching to container %s with status %q", 908 container.ID, container.State.String()) 909 } 910 911 // Return a driver handle 912 maxKill := d.DriverContext.config.MaxKillTimeout 913 h := &DockerHandle{ 914 client: client, 915 waitClient: waitClient, 916 executor: exec, 917 pluginClient: pluginClient, 918 logger: d.logger, 919 jobName: d.DriverContext.jobName, 920 taskGroupName: d.DriverContext.taskGroupName, 921 taskName: d.DriverContext.taskName, 922 Image: d.driverConfig.ImageName, 923 ImageID: d.imageID, 924 containerID: container.ID, 925 version: d.config.Version.VersionNumber(), 926 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 927 maxKillTimeout: maxKill, 928 doneCh: make(chan bool), 929 waitCh: make(chan *dstructs.WaitResult, 1), 930 removeContainerOnExit: d.config.ReadBoolDefault(dockerCleanupContainerConfigOption, dockerCleanupContainerConfigDefault), 931 } 932 go h.collectStats() 933 go h.run() 934 935 // Detect container address 936 ip, autoUse := d.detectIP(container) 937 938 // Create a response with the driver handle and container network metadata 939 resp := &StartResponse{ 940 Handle: h, 941 Network: &cstructs.DriverNetwork{ 942 PortMap: d.driverConfig.PortMap, 943 IP: ip, 944 AutoAdvertise: autoUse, 945 }, 946 } 947 return resp, nil 948 } 949 950 // detectIP of Docker container. Returns the first IP found as well as true if 951 // the IP should be advertised (bridge network IPs return false). Returns an 952 // empty string and false if no IP could be found. 953 func (d *DockerDriver) detectIP(c *docker.Container) (string, bool) { 954 if c.NetworkSettings == nil { 955 // This should only happen if there's been a coding error (such 956 // as not calling InspectContainer after CreateContainer). Code 957 // defensively in case the Docker API changes subtly. 958 d.logger.Printf("[ERROR] driver.docker: no network settings for container %s", c.ID) 959 return "", false 960 } 961 962 ip, ipName := "", "" 963 auto := false 964 for name, net := range c.NetworkSettings.Networks { 965 if net.IPAddress == "" { 966 // Ignore networks without an IP address 967 continue 968 } 969 970 ip = net.IPAddress 971 if d.driverConfig.AdvertiseIPv6Address { 972 ip = net.GlobalIPv6Address 973 auto = true 974 } 975 ipName = name 976 977 // Don't auto-advertise IPs for default networks (bridge on 978 // Linux, nat on Windows) 979 if name != "bridge" && name != "nat" { 980 auto = true 981 } 982 983 break 984 } 985 986 if n := len(c.NetworkSettings.Networks); n > 1 { 987 d.logger.Printf("[WARN] driver.docker: task %s multiple (%d) Docker networks for container %q but Nomad only supports 1: choosing %q", d.taskName, n, c.ID, ipName) 988 } 989 990 return ip, auto 991 } 992 993 func (d *DockerDriver) Cleanup(_ *ExecContext, res *CreatedResources) error { 994 retry := false 995 var merr multierror.Error 996 for key, resources := range res.Resources { 997 switch key { 998 case dockerImageResKey: 999 for _, value := range resources { 1000 err := d.cleanupImage(value) 1001 if err != nil { 1002 if structs.IsRecoverable(err) { 1003 retry = true 1004 } 1005 merr.Errors = append(merr.Errors, err) 1006 continue 1007 } 1008 1009 // Remove cleaned image from resources 1010 res.Remove(dockerImageResKey, value) 1011 } 1012 default: 1013 d.logger.Printf("[ERR] driver.docker: unknown resource to cleanup: %q", key) 1014 } 1015 } 1016 return structs.NewRecoverableError(merr.ErrorOrNil(), retry) 1017 } 1018 1019 // cleanupImage removes a Docker image. No error is returned if the image 1020 // doesn't exist or is still in use. Requires the global client to already be 1021 // initialized. 1022 func (d *DockerDriver) cleanupImage(imageID string) error { 1023 if !d.config.ReadBoolDefault(dockerCleanupImageConfigOption, dockerCleanupImageConfigDefault) { 1024 // Config says not to cleanup 1025 return nil 1026 } 1027 1028 coordinator, callerID := d.getDockerCoordinator(client) 1029 coordinator.RemoveImage(imageID, callerID) 1030 1031 return nil 1032 } 1033 1034 // dockerHealthCheckClient creates a single *docker.Client with a timeout of 1035 // one minute, which will be used when performing Docker health checks. 1036 func (d *DockerDriver) dockerHealthCheckClient() (*docker.Client, error) { 1037 createClientsLock.Lock() 1038 defer createClientsLock.Unlock() 1039 1040 if healthCheckClient != nil { 1041 return healthCheckClient, nil 1042 } 1043 1044 var err error 1045 healthCheckClient, err = d.newDockerClient(dockerHealthCheckTimeout) 1046 if err != nil { 1047 return nil, err 1048 } 1049 1050 return healthCheckClient, nil 1051 } 1052 1053 // dockerClients creates two *docker.Client, one for long running operations and 1054 // the other for shorter operations. In test / dev mode we can use ENV vars to 1055 // connect to the docker daemon. In production mode we will read docker.endpoint 1056 // from the config file. 1057 func (d *DockerDriver) dockerClients() (*docker.Client, *docker.Client, error) { 1058 createClientsLock.Lock() 1059 defer createClientsLock.Unlock() 1060 1061 if client != nil && waitClient != nil { 1062 return client, waitClient, nil 1063 } 1064 1065 var err error 1066 1067 // Onlt initialize the client if it hasn't yet been done 1068 if client == nil { 1069 client, err = d.newDockerClient(dockerTimeout) 1070 if err != nil { 1071 return nil, nil, err 1072 } 1073 } 1074 1075 // Only initialize the waitClient if it hasn't yet been done 1076 if waitClient == nil { 1077 waitClient, err = d.newDockerClient(0 * time.Minute) 1078 if err != nil { 1079 return nil, nil, err 1080 } 1081 } 1082 1083 return client, waitClient, nil 1084 } 1085 1086 // newDockerClient creates a new *docker.Client with a configurable timeout 1087 func (d *DockerDriver) newDockerClient(timeout time.Duration) (*docker.Client, error) { 1088 var err error 1089 var merr multierror.Error 1090 var newClient *docker.Client 1091 1092 // Default to using whatever is configured in docker.endpoint. If this is 1093 // not specified we'll fall back on NewClientFromEnv which reads config from 1094 // the DOCKER_* environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and 1095 // DOCKER_CERT_PATH. This allows us to lock down the config in production 1096 // but also accept the standard ENV configs for dev and test. 1097 dockerEndpoint := d.config.Read("docker.endpoint") 1098 if dockerEndpoint != "" { 1099 cert := d.config.Read("docker.tls.cert") 1100 key := d.config.Read("docker.tls.key") 1101 ca := d.config.Read("docker.tls.ca") 1102 1103 if cert+key+ca != "" { 1104 d.logger.Printf("[DEBUG] driver.docker: using TLS client connection to %s", dockerEndpoint) 1105 newClient, err = docker.NewTLSClient(dockerEndpoint, cert, key, ca) 1106 if err != nil { 1107 merr.Errors = append(merr.Errors, err) 1108 } 1109 } else { 1110 d.logger.Printf("[DEBUG] driver.docker: using standard client connection to %s", dockerEndpoint) 1111 newClient, err = docker.NewClient(dockerEndpoint) 1112 if err != nil { 1113 merr.Errors = append(merr.Errors, err) 1114 } 1115 } 1116 } else { 1117 d.logger.Println("[DEBUG] driver.docker: using client connection initialized from environment") 1118 newClient, err = docker.NewClientFromEnv() 1119 if err != nil { 1120 merr.Errors = append(merr.Errors, err) 1121 } 1122 } 1123 1124 if timeout != 0 && newClient != nil { 1125 newClient.SetTimeout(timeout) 1126 } 1127 return newClient, merr.ErrorOrNil() 1128 } 1129 1130 func (d *DockerDriver) containerBinds(driverConfig *DockerDriverConfig, ctx *ExecContext, 1131 task *structs.Task) ([]string, error) { 1132 1133 allocDirBind := fmt.Sprintf("%s:%s", ctx.TaskDir.SharedAllocDir, ctx.TaskEnv.EnvMap[env.AllocDir]) 1134 taskLocalBind := fmt.Sprintf("%s:%s", ctx.TaskDir.LocalDir, ctx.TaskEnv.EnvMap[env.TaskLocalDir]) 1135 secretDirBind := fmt.Sprintf("%s:%s", ctx.TaskDir.SecretsDir, ctx.TaskEnv.EnvMap[env.SecretsDir]) 1136 binds := []string{allocDirBind, taskLocalBind, secretDirBind} 1137 1138 volumesEnabled := d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault) 1139 1140 if !volumesEnabled && driverConfig.VolumeDriver != "" { 1141 return nil, fmt.Errorf("%s is false; cannot use volume driver %q", dockerVolumesConfigOption, driverConfig.VolumeDriver) 1142 } 1143 1144 for _, userbind := range driverConfig.Volumes { 1145 parts := strings.Split(userbind, ":") 1146 if len(parts) < 2 { 1147 return nil, fmt.Errorf("invalid docker volume: %q", userbind) 1148 } 1149 1150 // Resolve dotted path segments 1151 parts[0] = filepath.Clean(parts[0]) 1152 1153 // Absolute paths aren't always supported 1154 if filepath.IsAbs(parts[0]) { 1155 if !volumesEnabled { 1156 // Disallow mounting arbitrary absolute paths 1157 return nil, fmt.Errorf("%s is false; cannot mount host paths: %+q", dockerVolumesConfigOption, userbind) 1158 } 1159 binds = append(binds, userbind) 1160 continue 1161 } 1162 1163 // Relative paths are always allowed as they mount within a container 1164 // When a VolumeDriver is set, we assume we receive a binding in the format volume-name:container-dest 1165 // Otherwise, we assume we receive a relative path binding in the format relative/to/task:/also/in/container 1166 if driverConfig.VolumeDriver == "" { 1167 // Expand path relative to alloc dir 1168 parts[0] = filepath.Join(ctx.TaskDir.Dir, parts[0]) 1169 } 1170 1171 binds = append(binds, strings.Join(parts, ":")) 1172 } 1173 1174 if selinuxLabel := d.config.Read(dockerSELinuxLabelConfigOption); selinuxLabel != "" { 1175 // Apply SELinux Label to each volume 1176 for i := range binds { 1177 binds[i] = fmt.Sprintf("%s:%s", binds[i], selinuxLabel) 1178 } 1179 } 1180 1181 return binds, nil 1182 } 1183 1184 // createContainerConfig initializes a struct needed to call docker.client.CreateContainer() 1185 func (d *DockerDriver) createContainerConfig(ctx *ExecContext, task *structs.Task, 1186 driverConfig *DockerDriverConfig, syslogAddr string) (docker.CreateContainerOptions, error) { 1187 var c docker.CreateContainerOptions 1188 if task.Resources == nil { 1189 // Guard against missing resources. We should never have been able to 1190 // schedule a job without specifying this. 1191 d.logger.Println("[ERR] driver.docker: task.Resources is empty") 1192 return c, fmt.Errorf("task.Resources is empty") 1193 } 1194 1195 binds, err := d.containerBinds(driverConfig, ctx, task) 1196 if err != nil { 1197 return c, err 1198 } 1199 1200 // create the config block that will later be consumed by go-dockerclient 1201 config := &docker.Config{ 1202 Image: d.imageID, 1203 Entrypoint: driverConfig.Entrypoint, 1204 Hostname: driverConfig.Hostname, 1205 User: task.User, 1206 Tty: driverConfig.TTY, 1207 OpenStdin: driverConfig.Interactive, 1208 StopTimeout: int(task.KillTimeout.Seconds()), 1209 StopSignal: task.KillSignal, 1210 } 1211 1212 if driverConfig.WorkDir != "" { 1213 config.WorkingDir = driverConfig.WorkDir 1214 } 1215 1216 memLimit := int64(task.Resources.MemoryMB) * 1024 * 1024 1217 1218 if len(driverConfig.Logging) == 0 { 1219 if runtime.GOOS == "darwin" { 1220 d.logger.Printf("[DEBUG] driver.docker: deferring logging to docker on Docker for Mac") 1221 } else { 1222 d.logger.Printf("[DEBUG] driver.docker: Setting default logging options to syslog and %s", syslogAddr) 1223 driverConfig.Logging = []DockerLoggingOpts{ 1224 {Type: "syslog", Config: map[string]string{"syslog-address": syslogAddr}}, 1225 } 1226 } 1227 } 1228 1229 hostConfig := &docker.HostConfig{ 1230 // Convert MB to bytes. This is an absolute value. 1231 Memory: memLimit, 1232 // Convert Mhz to shares. This is a relative value. 1233 CPUShares: int64(task.Resources.CPU), 1234 1235 // Binds are used to mount a host volume into the container. We mount a 1236 // local directory for storage and a shared alloc directory that can be 1237 // used to share data between different tasks in the same task group. 1238 Binds: binds, 1239 1240 VolumeDriver: driverConfig.VolumeDriver, 1241 1242 PidsLimit: driverConfig.PidsLimit, 1243 } 1244 1245 // Calculate CPU Quota 1246 // cfs_quota_us is the time per core, so we must 1247 // multiply the time by the number of cores available 1248 // See https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/sec-cpu 1249 if driverConfig.CPUHardLimit { 1250 numCores := runtime.NumCPU() 1251 percentTicks := float64(task.Resources.CPU) / float64(d.node.Resources.CPU) 1252 if driverConfig.CPUCFSPeriod < 0 || driverConfig.CPUCFSPeriod > 1000000 { 1253 return c, fmt.Errorf("invalid value for cpu_cfs_period") 1254 } 1255 if driverConfig.CPUCFSPeriod == 0 { 1256 driverConfig.CPUCFSPeriod = defaultCFSPeriodUS 1257 } 1258 hostConfig.CPUPeriod = driverConfig.CPUCFSPeriod 1259 hostConfig.CPUQuota = int64(percentTicks*float64(driverConfig.CPUCFSPeriod)) * int64(numCores) 1260 } 1261 1262 // Windows does not support MemorySwap/MemorySwappiness #2193 1263 if runtime.GOOS == "windows" { 1264 hostConfig.MemorySwap = 0 1265 hostConfig.MemorySwappiness = -1 1266 } else { 1267 hostConfig.MemorySwap = memLimit // MemorySwap is memory + swap. 1268 } 1269 1270 if len(driverConfig.Logging) != 0 { 1271 d.logger.Printf("[DEBUG] driver.docker: Using config for logging: %+v", driverConfig.Logging[0]) 1272 hostConfig.LogConfig = docker.LogConfig{ 1273 Type: driverConfig.Logging[0].Type, 1274 Config: driverConfig.Logging[0].Config, 1275 } 1276 } 1277 1278 d.logger.Printf("[DEBUG] driver.docker: using %d bytes memory for %s", hostConfig.Memory, task.Name) 1279 d.logger.Printf("[DEBUG] driver.docker: using %d cpu shares for %s", hostConfig.CPUShares, task.Name) 1280 if driverConfig.CPUHardLimit { 1281 d.logger.Printf("[DEBUG] driver.docker: using %dms cpu quota and %dms cpu period for %s", hostConfig.CPUQuota, defaultCFSPeriodUS, task.Name) 1282 } 1283 d.logger.Printf("[DEBUG] driver.docker: binding directories %#v for %s", hostConfig.Binds, task.Name) 1284 1285 // set privileged mode 1286 hostPrivileged := d.config.ReadBoolDefault(dockerPrivilegedConfigOption, false) 1287 if driverConfig.Privileged && !hostPrivileged { 1288 return c, fmt.Errorf(`Docker privileged mode is disabled on this Nomad agent`) 1289 } 1290 hostConfig.Privileged = driverConfig.Privileged 1291 1292 // set capabilities 1293 hostCapsWhitelistConfig := d.config.ReadDefault( 1294 dockerCapsWhitelistConfigOption, dockerCapsWhitelistConfigDefault) 1295 hostCapsWhitelist := make(map[string]struct{}) 1296 for _, cap := range strings.Split(hostCapsWhitelistConfig, ",") { 1297 cap = strings.ToLower(strings.TrimSpace(cap)) 1298 hostCapsWhitelist[cap] = struct{}{} 1299 } 1300 1301 if _, ok := hostCapsWhitelist["all"]; !ok { 1302 effectiveCaps, err := tweakCapabilities( 1303 strings.Split(dockerBasicCaps, ","), 1304 driverConfig.CapAdd, 1305 driverConfig.CapDrop, 1306 ) 1307 if err != nil { 1308 return c, err 1309 } 1310 var missingCaps []string 1311 for _, cap := range effectiveCaps { 1312 cap = strings.ToLower(cap) 1313 if _, ok := hostCapsWhitelist[cap]; !ok { 1314 missingCaps = append(missingCaps, cap) 1315 } 1316 } 1317 if len(missingCaps) > 0 { 1318 return c, fmt.Errorf("Docker driver doesn't have the following caps whitelisted on this Nomad agent: %s", missingCaps) 1319 } 1320 } 1321 1322 hostConfig.CapAdd = driverConfig.CapAdd 1323 hostConfig.CapDrop = driverConfig.CapDrop 1324 1325 // set SHM size 1326 if driverConfig.ShmSize != 0 { 1327 hostConfig.ShmSize = driverConfig.ShmSize 1328 } 1329 1330 // set DNS servers 1331 for _, ip := range driverConfig.DNSServers { 1332 if net.ParseIP(ip) != nil { 1333 hostConfig.DNS = append(hostConfig.DNS, ip) 1334 } else { 1335 d.logger.Printf("[ERR] driver.docker: invalid ip address for container dns server: %s", ip) 1336 } 1337 } 1338 1339 if len(driverConfig.Devices) > 0 { 1340 var devices []docker.Device 1341 for _, device := range driverConfig.Devices { 1342 dev := docker.Device{ 1343 PathOnHost: device.HostPath, 1344 PathInContainer: device.ContainerPath, 1345 CgroupPermissions: device.CgroupPermissions} 1346 devices = append(devices, dev) 1347 } 1348 hostConfig.Devices = devices 1349 } 1350 1351 // Setup mounts 1352 for _, m := range driverConfig.Mounts { 1353 hm := docker.HostMount{ 1354 Target: m.Target, 1355 Source: m.Source, 1356 Type: "volume", // Only type supported 1357 ReadOnly: m.ReadOnly, 1358 } 1359 if len(m.VolumeOptions) == 1 { 1360 vo := m.VolumeOptions[0] 1361 hm.VolumeOptions = &docker.VolumeOptions{ 1362 NoCopy: vo.NoCopy, 1363 } 1364 1365 if len(vo.DriverConfig) == 1 { 1366 dc := vo.DriverConfig[0] 1367 hm.VolumeOptions.DriverConfig = docker.VolumeDriverConfig{ 1368 Name: dc.Name, 1369 } 1370 if len(dc.Options) == 1 { 1371 hm.VolumeOptions.DriverConfig.Options = dc.Options[0] 1372 } 1373 } 1374 if len(vo.Labels) == 1 { 1375 hm.VolumeOptions.Labels = vo.Labels[0] 1376 } 1377 } 1378 hostConfig.Mounts = append(hostConfig.Mounts, hm) 1379 } 1380 1381 // set DNS search domains and extra hosts 1382 hostConfig.DNSSearch = driverConfig.DNSSearchDomains 1383 hostConfig.DNSOptions = driverConfig.DNSOptions 1384 hostConfig.ExtraHosts = driverConfig.ExtraHosts 1385 1386 hostConfig.IpcMode = driverConfig.IpcMode 1387 hostConfig.PidMode = driverConfig.PidMode 1388 hostConfig.UTSMode = driverConfig.UTSMode 1389 hostConfig.UsernsMode = driverConfig.UsernsMode 1390 hostConfig.SecurityOpt = driverConfig.SecurityOpt 1391 hostConfig.Sysctls = driverConfig.Sysctl 1392 hostConfig.Ulimits = driverConfig.Ulimit 1393 hostConfig.ReadonlyRootfs = driverConfig.ReadonlyRootfs 1394 1395 hostConfig.NetworkMode = driverConfig.NetworkMode 1396 if hostConfig.NetworkMode == "" { 1397 // docker default 1398 d.logger.Printf("[DEBUG] driver.docker: networking mode not specified; defaulting to %s", defaultNetworkMode) 1399 hostConfig.NetworkMode = defaultNetworkMode 1400 } 1401 1402 // Setup port mapping and exposed ports 1403 if len(task.Resources.Networks) == 0 { 1404 d.logger.Println("[DEBUG] driver.docker: No network interfaces are available") 1405 if len(driverConfig.PortMap) > 0 { 1406 return c, fmt.Errorf("Trying to map ports but no network interface is available") 1407 } 1408 } else { 1409 // TODO add support for more than one network 1410 network := task.Resources.Networks[0] 1411 publishedPorts := map[docker.Port][]docker.PortBinding{} 1412 exposedPorts := map[docker.Port]struct{}{} 1413 1414 for _, port := range network.ReservedPorts { 1415 // By default we will map the allocated port 1:1 to the container 1416 containerPortInt := port.Value 1417 1418 // If the user has mapped a port using port_map we'll change it here 1419 if mapped, ok := driverConfig.PortMap[port.Label]; ok { 1420 containerPortInt = mapped 1421 } 1422 1423 hostPortStr := strconv.Itoa(port.Value) 1424 containerPort := docker.Port(strconv.Itoa(containerPortInt)) 1425 1426 publishedPorts[containerPort+"/tcp"] = getPortBinding(network.IP, hostPortStr) 1427 publishedPorts[containerPort+"/udp"] = getPortBinding(network.IP, hostPortStr) 1428 d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (static)", network.IP, port.Value, port.Value) 1429 1430 exposedPorts[containerPort+"/tcp"] = struct{}{} 1431 exposedPorts[containerPort+"/udp"] = struct{}{} 1432 d.logger.Printf("[DEBUG] driver.docker: exposed port %d", port.Value) 1433 } 1434 1435 for _, port := range network.DynamicPorts { 1436 // By default we will map the allocated port 1:1 to the container 1437 containerPortInt := port.Value 1438 1439 // If the user has mapped a port using port_map we'll change it here 1440 if mapped, ok := driverConfig.PortMap[port.Label]; ok { 1441 containerPortInt = mapped 1442 } 1443 1444 hostPortStr := strconv.Itoa(port.Value) 1445 containerPort := docker.Port(strconv.Itoa(containerPortInt)) 1446 1447 publishedPorts[containerPort+"/tcp"] = getPortBinding(network.IP, hostPortStr) 1448 publishedPorts[containerPort+"/udp"] = getPortBinding(network.IP, hostPortStr) 1449 d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (mapped)", network.IP, port.Value, containerPortInt) 1450 1451 exposedPorts[containerPort+"/tcp"] = struct{}{} 1452 exposedPorts[containerPort+"/udp"] = struct{}{} 1453 d.logger.Printf("[DEBUG] driver.docker: exposed port %s", containerPort) 1454 } 1455 1456 hostConfig.PortBindings = publishedPorts 1457 config.ExposedPorts = exposedPorts 1458 } 1459 1460 parsedArgs := ctx.TaskEnv.ParseAndReplace(driverConfig.Args) 1461 1462 // If the user specified a custom command to run, we'll inject it here. 1463 if driverConfig.Command != "" { 1464 // Validate command 1465 if err := validateCommand(driverConfig.Command, "args"); err != nil { 1466 return c, err 1467 } 1468 1469 cmd := []string{driverConfig.Command} 1470 if len(driverConfig.Args) != 0 { 1471 cmd = append(cmd, parsedArgs...) 1472 } 1473 d.logger.Printf("[DEBUG] driver.docker: setting container startup command to: %s", strings.Join(cmd, " ")) 1474 config.Cmd = cmd 1475 } else if len(driverConfig.Args) != 0 { 1476 config.Cmd = parsedArgs 1477 } 1478 1479 if len(driverConfig.Labels) > 0 { 1480 config.Labels = driverConfig.Labels 1481 d.logger.Printf("[DEBUG] driver.docker: applied labels on the container: %+v", config.Labels) 1482 } 1483 1484 config.Env = ctx.TaskEnv.List() 1485 1486 containerName := fmt.Sprintf("%s-%s", task.Name, d.DriverContext.allocID) 1487 d.logger.Printf("[DEBUG] driver.docker: setting container name to: %s", containerName) 1488 1489 var networkingConfig *docker.NetworkingConfig 1490 if len(driverConfig.NetworkAliases) > 0 || driverConfig.IPv4Address != "" || driverConfig.IPv6Address != "" { 1491 networkingConfig = &docker.NetworkingConfig{ 1492 EndpointsConfig: map[string]*docker.EndpointConfig{ 1493 hostConfig.NetworkMode: {}, 1494 }, 1495 } 1496 } 1497 1498 if len(driverConfig.NetworkAliases) > 0 { 1499 networkingConfig.EndpointsConfig[hostConfig.NetworkMode].Aliases = driverConfig.NetworkAliases 1500 d.logger.Printf("[DEBUG] driver.docker: using network_mode %q with network aliases: %v", 1501 hostConfig.NetworkMode, strings.Join(driverConfig.NetworkAliases, ", ")) 1502 } 1503 1504 if driverConfig.IPv4Address != "" || driverConfig.IPv6Address != "" { 1505 networkingConfig.EndpointsConfig[hostConfig.NetworkMode].IPAMConfig = &docker.EndpointIPAMConfig{ 1506 IPv4Address: driverConfig.IPv4Address, 1507 IPv6Address: driverConfig.IPv6Address, 1508 } 1509 d.logger.Printf("[DEBUG] driver.docker: using network_mode %q with ipv4: %q and ipv6: %q", 1510 hostConfig.NetworkMode, driverConfig.IPv4Address, driverConfig.IPv6Address) 1511 } 1512 1513 if driverConfig.MacAddress != "" { 1514 config.MacAddress = driverConfig.MacAddress 1515 d.logger.Printf("[DEBUG] driver.docker: using pinned mac address: %q", config.MacAddress) 1516 } 1517 1518 return docker.CreateContainerOptions{ 1519 Name: containerName, 1520 Config: config, 1521 HostConfig: hostConfig, 1522 NetworkingConfig: networkingConfig, 1523 }, nil 1524 } 1525 1526 func (d *DockerDriver) Periodic() (bool, time.Duration) { 1527 return true, 15 * time.Second 1528 } 1529 1530 // createImage creates a docker image either by pulling it from a registry or by 1531 // loading it from the file system 1532 func (d *DockerDriver) createImage(driverConfig *DockerDriverConfig, client *docker.Client, taskDir *allocdir.TaskDir) (string, error) { 1533 image := driverConfig.ImageName 1534 repo, tag := parseDockerImage(image) 1535 1536 coordinator, callerID := d.getDockerCoordinator(client) 1537 1538 // We're going to check whether the image is already downloaded. If the tag 1539 // is "latest", or ForcePull is set, we have to check for a new version every time so we don't 1540 // bother to check and cache the id here. We'll download first, then cache. 1541 if driverConfig.ForcePull { 1542 d.logger.Printf("[DEBUG] driver.docker: force pull image '%s' instead of inspecting local", dockerImageRef(repo, tag)) 1543 } else if tag != "latest" { 1544 if dockerImage, _ := client.InspectImage(image); dockerImage != nil { 1545 // Image exists so just increment its reference count 1546 coordinator.IncrementImageReference(dockerImage.ID, image, callerID) 1547 return dockerImage.ID, nil 1548 } 1549 } 1550 1551 // Load the image if specified 1552 if driverConfig.LoadImage != "" { 1553 return d.loadImage(driverConfig, client, taskDir) 1554 } 1555 1556 // Download the image 1557 return d.pullImage(driverConfig, client, repo, tag) 1558 } 1559 1560 // pullImage creates an image by pulling it from a docker registry 1561 func (d *DockerDriver) pullImage(driverConfig *DockerDriverConfig, client *docker.Client, repo, tag string) (id string, err error) { 1562 authOptions, err := d.resolveRegistryAuthentication(driverConfig, repo) 1563 if err != nil { 1564 if d.driverConfig.AuthSoftFail { 1565 d.logger.Printf("[WARN] Failed to find docker auth for repo %q: %v", repo, err) 1566 } else { 1567 return "", fmt.Errorf("Failed to find docker auth for repo %q: %v", repo, err) 1568 } 1569 } 1570 1571 if authIsEmpty(authOptions) { 1572 d.logger.Printf("[DEBUG] driver.docker: did not find docker auth for repo %q", repo) 1573 } 1574 1575 d.emitEvent("Downloading image %s", dockerImageRef(repo, tag)) 1576 coordinator, callerID := d.getDockerCoordinator(client) 1577 1578 return coordinator.PullImage(driverConfig.ImageName, authOptions, callerID, d.emitEvent) 1579 } 1580 1581 // authBackend encapsulates a function that resolves registry credentials. 1582 type authBackend func(string) (*docker.AuthConfiguration, error) 1583 1584 // resolveRegistryAuthentication attempts to retrieve auth credentials for the 1585 // repo, trying all authentication-backends possible. 1586 func (d *DockerDriver) resolveRegistryAuthentication(driverConfig *DockerDriverConfig, repo string) (*docker.AuthConfiguration, error) { 1587 return firstValidAuth(repo, []authBackend{ 1588 authFromTaskConfig(driverConfig), 1589 authFromDockerConfig(d.config.Read("docker.auth.config")), 1590 authFromHelper(d.config.Read("docker.auth.helper")), 1591 }) 1592 } 1593 1594 // loadImage creates an image by loading it from the file system 1595 func (d *DockerDriver) loadImage(driverConfig *DockerDriverConfig, client *docker.Client, 1596 taskDir *allocdir.TaskDir) (id string, err error) { 1597 1598 archive := filepath.Join(taskDir.LocalDir, driverConfig.LoadImage) 1599 d.logger.Printf("[DEBUG] driver.docker: loading image from: %v", archive) 1600 1601 f, err := os.Open(archive) 1602 if err != nil { 1603 return "", fmt.Errorf("unable to open image archive: %v", err) 1604 } 1605 1606 if err := client.LoadImage(docker.LoadImageOptions{InputStream: f}); err != nil { 1607 return "", err 1608 } 1609 f.Close() 1610 1611 dockerImage, err := client.InspectImage(driverConfig.ImageName) 1612 if err != nil { 1613 return "", recoverableErrTimeouts(err) 1614 } 1615 1616 coordinator, callerID := d.getDockerCoordinator(client) 1617 coordinator.IncrementImageReference(dockerImage.ID, driverConfig.ImageName, callerID) 1618 return dockerImage.ID, nil 1619 } 1620 1621 // createContainer creates the container given the passed configuration. It 1622 // attempts to handle any transient Docker errors. 1623 func (d *DockerDriver) createContainer(client createContainerClient, config docker.CreateContainerOptions) (*docker.Container, error) { 1624 // Create a container 1625 attempted := 0 1626 CREATE: 1627 container, createErr := client.CreateContainer(config) 1628 if createErr == nil { 1629 return container, nil 1630 } 1631 1632 d.logger.Printf("[DEBUG] driver.docker: failed to create container %q from image %q (ID: %q) (attempt %d): %v", 1633 config.Name, d.driverConfig.ImageName, d.imageID, attempted+1, createErr) 1634 1635 // Volume management tools like Portworx may not have detached a volume 1636 // from a previous node before Nomad started a task replacement task. 1637 // Treat these errors as recoverable so we retry. 1638 if strings.Contains(strings.ToLower(createErr.Error()), "volume is attached on another node") { 1639 return nil, structs.NewRecoverableError(createErr, true) 1640 } 1641 1642 // If the container already exists determine whether it's already 1643 // running or if it's dead and needs to be recreated. 1644 if strings.Contains(strings.ToLower(createErr.Error()), "container already exists") { 1645 containers, err := client.ListContainers(docker.ListContainersOptions{ 1646 All: true, 1647 }) 1648 if err != nil { 1649 d.logger.Printf("[ERR] driver.docker: failed to query list of containers matching name:%s", config.Name) 1650 return nil, recoverableErrTimeouts(fmt.Errorf("Failed to query list of containers: %s", err)) 1651 } 1652 1653 // Delete matching containers 1654 // Adding a / infront of the container name since Docker returns the 1655 // container names with a / pre-pended to the Nomad generated container names 1656 containerName := "/" + config.Name 1657 d.logger.Printf("[DEBUG] driver.docker: searching for container name %q to purge", containerName) 1658 for _, shimContainer := range containers { 1659 d.logger.Printf("[DEBUG] driver.docker: listed container %+v", shimContainer.Names) 1660 found := false 1661 for _, name := range shimContainer.Names { 1662 if name == containerName { 1663 d.logger.Printf("[DEBUG] driver.docker: Found container %v: %v", containerName, shimContainer.ID) 1664 found = true 1665 break 1666 } 1667 } 1668 1669 if !found { 1670 continue 1671 } 1672 1673 // Inspect the container and if the container isn't dead then return 1674 // the container 1675 container, err := client.InspectContainer(shimContainer.ID) 1676 if err != nil { 1677 err = fmt.Errorf("Failed to inspect container %s: %s", shimContainer.ID, err) 1678 1679 // This error is always recoverable as it could 1680 // be caused by races between listing 1681 // containers and this container being removed. 1682 // See #2802 1683 return nil, structs.NewRecoverableError(err, true) 1684 } 1685 if container != nil && container.State.Running { 1686 return container, nil 1687 } 1688 1689 err = client.RemoveContainer(docker.RemoveContainerOptions{ 1690 ID: container.ID, 1691 Force: true, 1692 }) 1693 if err != nil { 1694 d.logger.Printf("[ERR] driver.docker: failed to purge container %s", container.ID) 1695 return nil, recoverableErrTimeouts(fmt.Errorf("Failed to purge container %s: %s", container.ID, err)) 1696 } else if err == nil { 1697 d.logger.Printf("[INFO] driver.docker: purged container %s", container.ID) 1698 } 1699 } 1700 1701 if attempted < 5 { 1702 attempted++ 1703 time.Sleep(1 * time.Second) 1704 goto CREATE 1705 } 1706 } else if strings.Contains(strings.ToLower(createErr.Error()), "no such image") { 1707 // There is still a very small chance this is possible even with the 1708 // coordinator so retry. 1709 return nil, structs.NewRecoverableError(createErr, true) 1710 } 1711 1712 return nil, recoverableErrTimeouts(createErr) 1713 } 1714 1715 // startContainer starts the passed container. It attempts to handle any 1716 // transient Docker errors. 1717 func (d *DockerDriver) startContainer(c *docker.Container) error { 1718 // Start a container 1719 attempted := 0 1720 START: 1721 startErr := client.StartContainer(c.ID, c.HostConfig) 1722 if startErr == nil { 1723 return nil 1724 } 1725 1726 d.logger.Printf("[DEBUG] driver.docker: failed to start container %q (attempt %d): %v", c.ID, attempted+1, startErr) 1727 1728 // If it is a 500 error it is likely we can retry and be successful 1729 if strings.Contains(startErr.Error(), "API error (500)") { 1730 if attempted < 5 { 1731 attempted++ 1732 time.Sleep(1 * time.Second) 1733 goto START 1734 } 1735 return structs.NewRecoverableError(startErr, true) 1736 } 1737 1738 return recoverableErrTimeouts(startErr) 1739 } 1740 1741 func (d *DockerDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 1742 // Split the handle 1743 pidBytes := []byte(strings.TrimPrefix(handleID, "DOCKER:")) 1744 pid := &dockerPID{} 1745 if err := json.Unmarshal(pidBytes, pid); err != nil { 1746 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 1747 } 1748 d.logger.Printf("[INFO] driver.docker: re-attaching to docker process: %s", pid.ContainerID) 1749 d.logger.Printf("[DEBUG] driver.docker: re-attached to handle: %s", handleID) 1750 pluginConfig := &plugin.ClientConfig{ 1751 Reattach: pid.PluginConfig.PluginConfig(), 1752 } 1753 1754 client, waitClient, err := d.dockerClients() 1755 if err != nil { 1756 return nil, fmt.Errorf("Failed to connect to docker daemon: %s", err) 1757 } 1758 1759 // Look for a running container with this ID 1760 containers, err := client.ListContainers(docker.ListContainersOptions{ 1761 Filters: map[string][]string{ 1762 "id": {pid.ContainerID}, 1763 }, 1764 }) 1765 if err != nil { 1766 return nil, fmt.Errorf("Failed to query for container %s: %v", pid.ContainerID, err) 1767 } 1768 1769 found := false 1770 for _, container := range containers { 1771 if container.ID == pid.ContainerID { 1772 found = true 1773 } 1774 } 1775 if !found { 1776 return nil, fmt.Errorf("Failed to find container %s", pid.ContainerID) 1777 } 1778 exec, pluginClient, err := createExecutorWithConfig(pluginConfig, d.config.LogOutput) 1779 if err != nil { 1780 d.logger.Printf("[INFO] driver.docker: couldn't re-attach to the plugin process: %v", err) 1781 d.logger.Printf("[DEBUG] driver.docker: stopping container %q", pid.ContainerID) 1782 if e := client.StopContainer(pid.ContainerID, uint(pid.KillTimeout.Seconds())); e != nil { 1783 d.logger.Printf("[DEBUG] driver.docker: couldn't stop container: %v", e) 1784 } 1785 return nil, err 1786 } 1787 1788 ver, _ := exec.Version() 1789 d.logger.Printf("[DEBUG] driver.docker: version of executor: %v", ver.Version) 1790 1791 // Increment the reference count since we successfully attached to this 1792 // container 1793 coordinator, callerID := d.getDockerCoordinator(client) 1794 coordinator.IncrementImageReference(pid.ImageID, pid.Image, callerID) 1795 1796 // Return a driver handle 1797 h := &DockerHandle{ 1798 client: client, 1799 waitClient: waitClient, 1800 executor: exec, 1801 pluginClient: pluginClient, 1802 logger: d.logger, 1803 jobName: d.DriverContext.jobName, 1804 taskGroupName: d.DriverContext.taskGroupName, 1805 taskName: d.DriverContext.taskName, 1806 Image: pid.Image, 1807 ImageID: pid.ImageID, 1808 containerID: pid.ContainerID, 1809 version: pid.Version, 1810 killTimeout: pid.KillTimeout, 1811 maxKillTimeout: pid.MaxKillTimeout, 1812 doneCh: make(chan bool), 1813 waitCh: make(chan *dstructs.WaitResult, 1), 1814 } 1815 go h.collectStats() 1816 go h.run() 1817 return h, nil 1818 } 1819 1820 func (h *DockerHandle) ID() string { 1821 // Return a handle to the PID 1822 pid := dockerPID{ 1823 Version: h.version, 1824 ContainerID: h.containerID, 1825 Image: h.Image, 1826 ImageID: h.ImageID, 1827 KillTimeout: h.killTimeout, 1828 MaxKillTimeout: h.maxKillTimeout, 1829 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 1830 } 1831 data, err := json.Marshal(pid) 1832 if err != nil { 1833 h.logger.Printf("[ERR] driver.docker: failed to marshal docker PID to JSON: %s", err) 1834 } 1835 return fmt.Sprintf("DOCKER:%s", string(data)) 1836 } 1837 1838 func (h *DockerHandle) ContainerID() string { 1839 return h.containerID 1840 } 1841 1842 func (h *DockerHandle) WaitCh() chan *dstructs.WaitResult { 1843 return h.waitCh 1844 } 1845 1846 func (h *DockerHandle) Update(task *structs.Task) error { 1847 // Store the updated kill timeout. 1848 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 1849 if err := h.executor.UpdateTask(task); err != nil { 1850 h.logger.Printf("[DEBUG] driver.docker: failed to update log config: %v", err) 1851 } 1852 1853 // Update is not possible 1854 return nil 1855 } 1856 1857 func (h *DockerHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 1858 fullCmd := make([]string, len(args)+1) 1859 fullCmd[0] = cmd 1860 copy(fullCmd[1:], args) 1861 createExecOpts := docker.CreateExecOptions{ 1862 AttachStdin: false, 1863 AttachStdout: true, 1864 AttachStderr: true, 1865 Tty: false, 1866 Cmd: fullCmd, 1867 Container: h.containerID, 1868 Context: ctx, 1869 } 1870 exec, err := h.client.CreateExec(createExecOpts) 1871 if err != nil { 1872 return nil, 0, err 1873 } 1874 1875 output, _ := circbuf.NewBuffer(int64(dstructs.CheckBufSize)) 1876 startOpts := docker.StartExecOptions{ 1877 Detach: false, 1878 Tty: false, 1879 OutputStream: output, 1880 ErrorStream: output, 1881 Context: ctx, 1882 } 1883 if err := client.StartExec(exec.ID, startOpts); err != nil { 1884 return nil, 0, err 1885 } 1886 res, err := client.InspectExec(exec.ID) 1887 if err != nil { 1888 return output.Bytes(), 0, err 1889 } 1890 return output.Bytes(), res.ExitCode, nil 1891 } 1892 1893 func (h *DockerHandle) Signal(s os.Signal) error { 1894 // Convert types 1895 sysSig, ok := s.(syscall.Signal) 1896 if !ok { 1897 return fmt.Errorf("Failed to determine signal number") 1898 } 1899 1900 // TODO When we expose signals we will need a mapping layer that converts 1901 // MacOS signals to the correct signal number for docker. Or we change the 1902 // interface to take a signal string and leave it up to driver to map? 1903 1904 dockerSignal := docker.Signal(sysSig) 1905 opts := docker.KillContainerOptions{ 1906 ID: h.containerID, 1907 Signal: dockerSignal, 1908 } 1909 return h.client.KillContainer(opts) 1910 1911 } 1912 1913 // Kill is used to terminate the task. This uses `docker stop -t killTimeout` 1914 func (h *DockerHandle) Kill() error { 1915 // Stop the container 1916 err := h.waitClient.StopContainer(h.containerID, uint(h.killTimeout.Seconds())) 1917 if err != nil { 1918 h.executor.Exit() 1919 h.pluginClient.Kill() 1920 1921 // Container has already been removed. 1922 if strings.Contains(err.Error(), NoSuchContainerError) { 1923 h.logger.Printf("[DEBUG] driver.docker: attempted to stop nonexistent container %s", h.containerID) 1924 return nil 1925 } 1926 h.logger.Printf("[ERR] driver.docker: failed to stop container %s: %v", h.containerID, err) 1927 return fmt.Errorf("Failed to stop container %s: %s", h.containerID, err) 1928 } 1929 h.logger.Printf("[INFO] driver.docker: stopped container %s", h.containerID) 1930 return nil 1931 } 1932 1933 func (h *DockerHandle) Stats() (*cstructs.TaskResourceUsage, error) { 1934 h.resourceUsageLock.RLock() 1935 defer h.resourceUsageLock.RUnlock() 1936 var err error 1937 if h.resourceUsage == nil { 1938 err = fmt.Errorf("stats collection hasn't started yet") 1939 } 1940 return h.resourceUsage, err 1941 } 1942 1943 func (h *DockerHandle) run() { 1944 // Wait for it... 1945 exitCode, werr := h.waitClient.WaitContainer(h.containerID) 1946 if werr != nil { 1947 h.logger.Printf("[ERR] driver.docker: failed to wait for %s; container already terminated", h.containerID) 1948 } 1949 1950 if exitCode != 0 { 1951 werr = fmt.Errorf("Docker container exited with non-zero exit code: %d", exitCode) 1952 } 1953 1954 container, ierr := h.waitClient.InspectContainer(h.containerID) 1955 if ierr != nil { 1956 h.logger.Printf("[ERR] driver.docker: failed to inspect container %s: %v", h.containerID, ierr) 1957 } else if container.State.OOMKilled { 1958 werr = fmt.Errorf("OOM Killed") 1959 labels := []metrics.Label{ 1960 { 1961 Name: "job", 1962 Value: h.jobName, 1963 }, 1964 { 1965 Name: "task_group", 1966 Value: h.taskGroupName, 1967 }, 1968 { 1969 Name: "task", 1970 Value: h.taskName, 1971 }, 1972 } 1973 metrics.IncrCounterWithLabels([]string{"driver", "docker", "oom"}, 1, labels) 1974 } 1975 1976 close(h.doneCh) 1977 1978 // Shutdown the syslog collector 1979 if err := h.executor.Exit(); err != nil { 1980 h.logger.Printf("[ERR] driver.docker: failed to kill the syslog collector: %v", err) 1981 } 1982 h.pluginClient.Kill() 1983 1984 // Stop the container just incase the docker daemon's wait returned 1985 // incorrectly 1986 if err := h.client.StopContainer(h.containerID, 0); err != nil { 1987 _, noSuchContainer := err.(*docker.NoSuchContainer) 1988 _, containerNotRunning := err.(*docker.ContainerNotRunning) 1989 if !containerNotRunning && !noSuchContainer { 1990 h.logger.Printf("[ERR] driver.docker: error stopping container: %v", err) 1991 } 1992 } 1993 1994 // Remove the container 1995 if h.removeContainerOnExit == true { 1996 if err := h.client.RemoveContainer(docker.RemoveContainerOptions{ID: h.containerID, RemoveVolumes: true, Force: true}); err != nil { 1997 h.logger.Printf("[ERR] driver.docker: error removing container: %v", err) 1998 } 1999 } else { 2000 h.logger.Printf("[DEBUG] driver.docker: not removing container %v because of config", h.containerID) 2001 } 2002 2003 // Send the results 2004 h.waitCh <- dstructs.NewWaitResult(exitCode, 0, werr) 2005 close(h.waitCh) 2006 } 2007 2008 // collectStats starts collecting resource usage stats of a docker container 2009 func (h *DockerHandle) collectStats() { 2010 statsCh := make(chan *docker.Stats) 2011 statsOpts := docker.StatsOptions{ID: h.containerID, Done: h.doneCh, Stats: statsCh, Stream: true} 2012 go func() { 2013 //TODO handle Stats error 2014 if err := h.waitClient.Stats(statsOpts); err != nil { 2015 h.logger.Printf("[DEBUG] driver.docker: error collecting stats from container %s: %v", h.containerID, err) 2016 } 2017 }() 2018 numCores := runtime.NumCPU() 2019 for { 2020 select { 2021 case s := <-statsCh: 2022 if s != nil { 2023 ms := &cstructs.MemoryStats{ 2024 RSS: s.MemoryStats.Stats.Rss, 2025 Cache: s.MemoryStats.Stats.Cache, 2026 Swap: s.MemoryStats.Stats.Swap, 2027 MaxUsage: s.MemoryStats.MaxUsage, 2028 Measured: DockerMeasuredMemStats, 2029 } 2030 2031 cs := &cstructs.CpuStats{ 2032 ThrottledPeriods: s.CPUStats.ThrottlingData.ThrottledPeriods, 2033 ThrottledTime: s.CPUStats.ThrottlingData.ThrottledTime, 2034 Measured: DockerMeasuredCpuStats, 2035 } 2036 2037 // Calculate percentage 2038 cs.Percent = calculatePercent( 2039 s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage, 2040 s.CPUStats.SystemCPUUsage, s.PreCPUStats.SystemCPUUsage, numCores) 2041 cs.SystemMode = calculatePercent( 2042 s.CPUStats.CPUUsage.UsageInKernelmode, s.PreCPUStats.CPUUsage.UsageInKernelmode, 2043 s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage, numCores) 2044 cs.UserMode = calculatePercent( 2045 s.CPUStats.CPUUsage.UsageInUsermode, s.PreCPUStats.CPUUsage.UsageInUsermode, 2046 s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage, numCores) 2047 cs.TotalTicks = (cs.Percent / 100) * shelpers.TotalTicksAvailable() / float64(numCores) 2048 2049 h.resourceUsageLock.Lock() 2050 h.resourceUsage = &cstructs.TaskResourceUsage{ 2051 ResourceUsage: &cstructs.ResourceUsage{ 2052 MemoryStats: ms, 2053 CpuStats: cs, 2054 }, 2055 Timestamp: s.Read.UTC().UnixNano(), 2056 } 2057 h.resourceUsageLock.Unlock() 2058 } 2059 case <-h.doneCh: 2060 return 2061 } 2062 } 2063 } 2064 2065 func calculatePercent(newSample, oldSample, newTotal, oldTotal uint64, cores int) float64 { 2066 numerator := newSample - oldSample 2067 denom := newTotal - oldTotal 2068 if numerator <= 0 || denom <= 0 { 2069 return 0.0 2070 } 2071 2072 return (float64(numerator) / float64(denom)) * float64(cores) * 100.0 2073 } 2074 2075 // loadDockerConfig loads the docker config at the specified path, returning an 2076 // error if it couldn't be read. 2077 func loadDockerConfig(file string) (*configfile.ConfigFile, error) { 2078 f, err := os.Open(file) 2079 if err != nil { 2080 return nil, fmt.Errorf("Failed to open auth config file: %v, error: %v", file, err) 2081 } 2082 defer f.Close() 2083 2084 cfile := new(configfile.ConfigFile) 2085 if err = cfile.LoadFromReader(f); err != nil { 2086 return nil, fmt.Errorf("Failed to parse auth config file: %v", err) 2087 } 2088 return cfile, nil 2089 } 2090 2091 // parseRepositoryInfo takes a repo and returns the Docker RepositoryInfo. This 2092 // is useful for interacting with a Docker config object. 2093 func parseRepositoryInfo(repo string) (*registry.RepositoryInfo, error) { 2094 name, err := reference.ParseNamed(repo) 2095 if err != nil { 2096 return nil, fmt.Errorf("Failed to parse named repo %q: %v", repo, err) 2097 } 2098 2099 repoInfo, err := registry.ParseRepositoryInfo(name) 2100 if err != nil { 2101 return nil, fmt.Errorf("Failed to parse repository: %v", err) 2102 } 2103 2104 return repoInfo, nil 2105 } 2106 2107 // firstValidAuth tries a list of auth backends, returning first error or AuthConfiguration 2108 func firstValidAuth(repo string, backends []authBackend) (*docker.AuthConfiguration, error) { 2109 for _, backend := range backends { 2110 auth, err := backend(repo) 2111 if auth != nil || err != nil { 2112 return auth, err 2113 } 2114 } 2115 return nil, nil 2116 } 2117 2118 // authFromTaskConfig generates an authBackend for any auth given in the task-configuration 2119 func authFromTaskConfig(driverConfig *DockerDriverConfig) authBackend { 2120 return func(string) (*docker.AuthConfiguration, error) { 2121 if len(driverConfig.Auth) == 0 { 2122 return nil, nil 2123 } 2124 auth := driverConfig.Auth[0] 2125 return &docker.AuthConfiguration{ 2126 Username: auth.Username, 2127 Password: auth.Password, 2128 Email: auth.Email, 2129 ServerAddress: auth.ServerAddress, 2130 }, nil 2131 } 2132 } 2133 2134 // authFromDockerConfig generate an authBackend for a dockercfg-compatible file. 2135 // The authBacken can either be from explicit auth definitions or via credential 2136 // helpers 2137 func authFromDockerConfig(file string) authBackend { 2138 return func(repo string) (*docker.AuthConfiguration, error) { 2139 if file == "" { 2140 return nil, nil 2141 } 2142 repoInfo, err := parseRepositoryInfo(repo) 2143 if err != nil { 2144 return nil, err 2145 } 2146 2147 cfile, err := loadDockerConfig(file) 2148 if err != nil { 2149 return nil, err 2150 } 2151 2152 return firstValidAuth(repo, []authBackend{ 2153 func(string) (*docker.AuthConfiguration, error) { 2154 dockerAuthConfig := registry.ResolveAuthConfig(cfile.AuthConfigs, repoInfo.Index) 2155 auth := &docker.AuthConfiguration{ 2156 Username: dockerAuthConfig.Username, 2157 Password: dockerAuthConfig.Password, 2158 Email: dockerAuthConfig.Email, 2159 ServerAddress: dockerAuthConfig.ServerAddress, 2160 } 2161 if authIsEmpty(auth) { 2162 return nil, nil 2163 } 2164 return auth, nil 2165 }, 2166 authFromHelper(cfile.CredentialHelpers[registry.GetAuthConfigKey(repoInfo.Index)]), 2167 authFromHelper(cfile.CredentialsStore), 2168 }) 2169 } 2170 } 2171 2172 // authFromHelper generates an authBackend for a docker-credentials-helper; 2173 // A script taking the requested domain on input, outputting JSON with 2174 // "Username" and "Secret" 2175 func authFromHelper(helperName string) authBackend { 2176 return func(repo string) (*docker.AuthConfiguration, error) { 2177 if helperName == "" { 2178 return nil, nil 2179 } 2180 helper := dockerAuthHelperPrefix + helperName 2181 cmd := exec.Command(helper, "get") 2182 2183 repoParsed, err := reference.ParseNamed(repo) 2184 if err != nil { 2185 return nil, err 2186 } 2187 2188 // Ensure that the HTTPs prefix exists 2189 repoAddr := fmt.Sprintf("https://%s", repoParsed.Hostname()) 2190 2191 cmd.Stdin = strings.NewReader(repoAddr) 2192 output, err := cmd.Output() 2193 if err != nil { 2194 switch err.(type) { 2195 default: 2196 return nil, err 2197 case *exec.ExitError: 2198 return nil, fmt.Errorf("%s with input %q failed with stderr: %s", helper, repo, output) 2199 } 2200 } 2201 2202 var response map[string]string 2203 if err := json.Unmarshal(output, &response); err != nil { 2204 return nil, err 2205 } 2206 2207 auth := &docker.AuthConfiguration{ 2208 Username: response["Username"], 2209 Password: response["Secret"], 2210 } 2211 2212 if authIsEmpty(auth) { 2213 return nil, nil 2214 } 2215 return auth, nil 2216 } 2217 } 2218 2219 // authIsEmpty returns if auth is nil or an empty structure 2220 func authIsEmpty(auth *docker.AuthConfiguration) bool { 2221 if auth == nil { 2222 return false 2223 } 2224 return auth.Username == "" && 2225 auth.Password == "" && 2226 auth.Email == "" && 2227 auth.ServerAddress == "" 2228 } 2229 2230 // createContainerClient is the subset of Docker Client methods used by the 2231 // createContainer method to ease testing subtle error conditions. 2232 type createContainerClient interface { 2233 CreateContainer(docker.CreateContainerOptions) (*docker.Container, error) 2234 InspectContainer(id string) (*docker.Container, error) 2235 ListContainers(docker.ListContainersOptions) ([]docker.APIContainers, error) 2236 RemoveContainer(opts docker.RemoveContainerOptions) error 2237 } 2238 2239 func parseDockerImage(image string) (repo, tag string) { 2240 repo, tag = docker.ParseRepositoryTag(image) 2241 if tag != "" { 2242 return repo, tag 2243 } 2244 if i := strings.IndexRune(image, '@'); i > -1 { // Has digest (@sha256:...) 2245 // when pulling images with a digest, the repository contains the sha hash, and the tag is empty 2246 // see: https://github.com/fsouza/go-dockerclient/blob/master/image_test.go#L471 2247 repo = image 2248 } else { 2249 tag = "latest" 2250 } 2251 return repo, tag 2252 } 2253 2254 func dockerImageRef(repo string, tag string) string { 2255 if tag == "" { 2256 return repo 2257 } 2258 return fmt.Sprintf("%s:%s", repo, tag) 2259 }