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