github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/client/driver/docker.go (about) 1 package driver 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "net" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "regexp" 12 "runtime" 13 "strconv" 14 "strings" 15 "sync" 16 "syscall" 17 "time" 18 19 docker "github.com/fsouza/go-dockerclient" 20 21 "github.com/hashicorp/go-multierror" 22 "github.com/hashicorp/go-plugin" 23 "github.com/hashicorp/nomad/client/allocdir" 24 "github.com/hashicorp/nomad/client/config" 25 "github.com/hashicorp/nomad/client/driver/env" 26 "github.com/hashicorp/nomad/client/driver/executor" 27 dstructs "github.com/hashicorp/nomad/client/driver/structs" 28 cstructs "github.com/hashicorp/nomad/client/structs" 29 "github.com/hashicorp/nomad/helper/discover" 30 "github.com/hashicorp/nomad/helper/fields" 31 shelpers "github.com/hashicorp/nomad/helper/stats" 32 "github.com/hashicorp/nomad/nomad/structs" 33 "github.com/mitchellh/mapstructure" 34 ) 35 36 var ( 37 // We store the clients globally to cache the connection to the docker daemon. 38 createClients sync.Once 39 40 // client is a docker client with a timeout of 1 minute. This is for doing 41 // all operations with the docker daemon besides which are not long running 42 // such as creating, killing containers, etc. 43 client *docker.Client 44 45 // waitClient is a docker client with no timeouts. This is used for long 46 // running operations such as waiting on containers and collect stats 47 waitClient *docker.Client 48 49 // The statistics the Docker driver exposes 50 DockerMeasuredMemStats = []string{"RSS", "Cache", "Swap", "Max Usage"} 51 DockerMeasuredCpuStats = []string{"Throttled Periods", "Throttled Time", "Percent"} 52 ) 53 54 const ( 55 // NoSuchContainerError is returned by the docker daemon if the container 56 // does not exist. 57 NoSuchContainerError = "No such container" 58 59 // The key populated in Node Attributes to indicate presence of the Docker 60 // driver 61 dockerDriverAttr = "driver.docker" 62 63 // dockerSELinuxLabelConfigOption is the key for configuring the 64 // SELinux label for binds. 65 dockerSELinuxLabelConfigOption = "docker.volumes.selinuxlabel" 66 67 // dockerVolumesConfigOption is the key for enabling the use of custom 68 // bind volumes to arbitrary host paths. 69 dockerVolumesConfigOption = "docker.volumes.enabled" 70 dockerVolumesConfigDefault = true 71 72 // dockerPrivilegedConfigOption is the key for running containers in 73 // Docker's privileged mode. 74 dockerPrivilegedConfigOption = "docker.privileged.enabled" 75 76 // dockerTimeout is the length of time a request can be outstanding before 77 // it is timed out. 78 dockerTimeout = 1 * time.Minute 79 ) 80 81 type DockerDriver struct { 82 DriverContext 83 } 84 85 type DockerDriverAuth struct { 86 Username string `mapstructure:"username"` // username for the registry 87 Password string `mapstructure:"password"` // password to access the registry 88 Email string `mapstructure:"email"` // email address of the user who is allowed to access the registry 89 ServerAddress string `mapstructure:"server_address"` // server address of the registry 90 } 91 92 type DockerLoggingOpts struct { 93 Type string `mapstructure:"type"` 94 ConfigRaw []map[string]string `mapstructure:"config"` 95 Config map[string]string `mapstructure:"-"` 96 } 97 98 type DockerDriverConfig struct { 99 ImageName string `mapstructure:"image"` // Container's Image Name 100 LoadImages []string `mapstructure:"load"` // LoadImage is array of paths to image archive files 101 Command string `mapstructure:"command"` // The Command/Entrypoint to run when the container starts up 102 Args []string `mapstructure:"args"` // The arguments to the Command/Entrypoint 103 IpcMode string `mapstructure:"ipc_mode"` // The IPC mode of the container - host and none 104 NetworkMode string `mapstructure:"network_mode"` // The network mode of the container - host, nat and none 105 PidMode string `mapstructure:"pid_mode"` // The PID mode of the container - host and none 106 UTSMode string `mapstructure:"uts_mode"` // The UTS mode of the container - host and none 107 UsernsMode string `mapstructure:"userns_mode"` // The User namespace mode of the container - host and none 108 PortMapRaw []map[string]int `mapstructure:"port_map"` // 109 PortMap map[string]int `mapstructure:"-"` // A map of host port labels and the ports exposed on the container 110 Privileged bool `mapstructure:"privileged"` // Flag to run the container in privileged mode 111 DNSServers []string `mapstructure:"dns_servers"` // DNS Server for containers 112 DNSSearchDomains []string `mapstructure:"dns_search_domains"` // DNS Search domains for containers 113 Hostname string `mapstructure:"hostname"` // Hostname for containers 114 LabelsRaw []map[string]string `mapstructure:"labels"` // 115 Labels map[string]string `mapstructure:"-"` // Labels to set when the container starts up 116 Auth []DockerDriverAuth `mapstructure:"auth"` // Authentication credentials for a private Docker registry 117 SSL bool `mapstructure:"ssl"` // Flag indicating repository is served via https 118 TTY bool `mapstructure:"tty"` // Allocate a Pseudo-TTY 119 Interactive bool `mapstructure:"interactive"` // Keep STDIN open even if not attached 120 ShmSize int64 `mapstructure:"shm_size"` // Size of /dev/shm of the container in bytes 121 WorkDir string `mapstructure:"work_dir"` // Working directory inside the container 122 Logging []DockerLoggingOpts `mapstructure:"logging"` // Logging options for syslog server 123 Volumes []string `mapstructure:"volumes"` // Host-Volumes to mount in, syntax: /path/to/host/directory:/destination/path/in/container 124 } 125 126 // Validate validates a docker driver config 127 func (c *DockerDriverConfig) Validate() error { 128 if c.ImageName == "" { 129 return fmt.Errorf("Docker Driver needs an image name") 130 } 131 132 c.PortMap = mapMergeStrInt(c.PortMapRaw...) 133 c.Labels = mapMergeStrStr(c.LabelsRaw...) 134 if len(c.Logging) > 0 { 135 c.Logging[0].Config = mapMergeStrStr(c.Logging[0].ConfigRaw...) 136 } 137 return nil 138 } 139 140 // NewDockerDriverConfig returns a docker driver config by parsing the HCL 141 // config 142 func NewDockerDriverConfig(task *structs.Task, env *env.TaskEnvironment) (*DockerDriverConfig, error) { 143 var dconf DockerDriverConfig 144 145 // Default to SSL 146 dconf.SSL = true 147 148 if err := mapstructure.WeakDecode(task.Config, &dconf); err != nil { 149 return nil, err 150 } 151 152 // Interpolate everthing that is a string 153 dconf.ImageName = env.ReplaceEnv(dconf.ImageName) 154 dconf.Command = env.ReplaceEnv(dconf.Command) 155 dconf.IpcMode = env.ReplaceEnv(dconf.IpcMode) 156 dconf.NetworkMode = env.ReplaceEnv(dconf.NetworkMode) 157 dconf.PidMode = env.ReplaceEnv(dconf.PidMode) 158 dconf.UTSMode = env.ReplaceEnv(dconf.UTSMode) 159 dconf.Hostname = env.ReplaceEnv(dconf.Hostname) 160 dconf.WorkDir = env.ReplaceEnv(dconf.WorkDir) 161 dconf.Volumes = env.ParseAndReplace(dconf.Volumes) 162 dconf.DNSServers = env.ParseAndReplace(dconf.DNSServers) 163 dconf.DNSSearchDomains = env.ParseAndReplace(dconf.DNSSearchDomains) 164 dconf.LoadImages = env.ParseAndReplace(dconf.LoadImages) 165 166 for _, m := range dconf.LabelsRaw { 167 for k, v := range m { 168 delete(m, k) 169 m[env.ReplaceEnv(k)] = env.ReplaceEnv(v) 170 } 171 } 172 173 for _, a := range dconf.Auth { 174 a.Username = env.ReplaceEnv(a.Username) 175 a.Password = env.ReplaceEnv(a.Password) 176 a.Email = env.ReplaceEnv(a.Email) 177 a.ServerAddress = env.ReplaceEnv(a.ServerAddress) 178 } 179 180 for _, l := range dconf.Logging { 181 l.Type = env.ReplaceEnv(l.Type) 182 for _, c := range l.ConfigRaw { 183 for k, v := range c { 184 delete(c, k) 185 c[env.ReplaceEnv(k)] = env.ReplaceEnv(v) 186 } 187 } 188 } 189 190 for _, m := range dconf.PortMapRaw { 191 for k, v := range m { 192 delete(m, k) 193 m[env.ReplaceEnv(k)] = v 194 } 195 } 196 197 // Remove any http 198 if strings.Contains(dconf.ImageName, "https://") { 199 dconf.ImageName = strings.Replace(dconf.ImageName, "https://", "", 1) 200 } 201 202 if err := dconf.Validate(); err != nil { 203 return nil, err 204 } 205 return &dconf, nil 206 } 207 208 type dockerPID struct { 209 Version string 210 ImageID string 211 ContainerID string 212 KillTimeout time.Duration 213 MaxKillTimeout time.Duration 214 PluginConfig *PluginReattachConfig 215 } 216 217 type DockerHandle struct { 218 pluginClient *plugin.Client 219 executor executor.Executor 220 client *docker.Client 221 waitClient *docker.Client 222 logger *log.Logger 223 cleanupImage bool 224 imageID string 225 containerID string 226 version string 227 clkSpeed float64 228 killTimeout time.Duration 229 maxKillTimeout time.Duration 230 resourceUsageLock sync.RWMutex 231 resourceUsage *cstructs.TaskResourceUsage 232 waitCh chan *dstructs.WaitResult 233 doneCh chan bool 234 } 235 236 func NewDockerDriver(ctx *DriverContext) Driver { 237 return &DockerDriver{DriverContext: *ctx} 238 } 239 240 // Validate is used to validate the driver configuration 241 func (d *DockerDriver) Validate(config map[string]interface{}) error { 242 fd := &fields.FieldData{ 243 Raw: config, 244 Schema: map[string]*fields.FieldSchema{ 245 "image": &fields.FieldSchema{ 246 Type: fields.TypeString, 247 Required: true, 248 }, 249 "load": &fields.FieldSchema{ 250 Type: fields.TypeArray, 251 }, 252 "command": &fields.FieldSchema{ 253 Type: fields.TypeString, 254 }, 255 "args": &fields.FieldSchema{ 256 Type: fields.TypeArray, 257 }, 258 "ipc_mode": &fields.FieldSchema{ 259 Type: fields.TypeString, 260 }, 261 "network_mode": &fields.FieldSchema{ 262 Type: fields.TypeString, 263 }, 264 "pid_mode": &fields.FieldSchema{ 265 Type: fields.TypeString, 266 }, 267 "uts_mode": &fields.FieldSchema{ 268 Type: fields.TypeString, 269 }, 270 "userns_mode": &fields.FieldSchema{ 271 Type: fields.TypeString, 272 }, 273 "port_map": &fields.FieldSchema{ 274 Type: fields.TypeArray, 275 }, 276 "privileged": &fields.FieldSchema{ 277 Type: fields.TypeBool, 278 }, 279 "dns_servers": &fields.FieldSchema{ 280 Type: fields.TypeArray, 281 }, 282 "dns_search_domains": &fields.FieldSchema{ 283 Type: fields.TypeArray, 284 }, 285 "hostname": &fields.FieldSchema{ 286 Type: fields.TypeString, 287 }, 288 "labels": &fields.FieldSchema{ 289 Type: fields.TypeArray, 290 }, 291 "auth": &fields.FieldSchema{ 292 Type: fields.TypeArray, 293 }, 294 "ssl": &fields.FieldSchema{ 295 Type: fields.TypeBool, 296 }, 297 "tty": &fields.FieldSchema{ 298 Type: fields.TypeBool, 299 }, 300 "interactive": &fields.FieldSchema{ 301 Type: fields.TypeBool, 302 }, 303 "shm_size": &fields.FieldSchema{ 304 Type: fields.TypeInt, 305 }, 306 "work_dir": &fields.FieldSchema{ 307 Type: fields.TypeString, 308 }, 309 "logging": &fields.FieldSchema{ 310 Type: fields.TypeArray, 311 }, 312 "volumes": &fields.FieldSchema{ 313 Type: fields.TypeArray, 314 }, 315 }, 316 } 317 318 if err := fd.Validate(); err != nil { 319 return err 320 } 321 322 return nil 323 } 324 325 func (d *DockerDriver) Abilities() DriverAbilities { 326 return DriverAbilities{ 327 SendSignals: true, 328 } 329 } 330 331 func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 332 // Set environment variables. 333 d.taskEnv.SetAllocDir(allocdir.SharedAllocContainerPath). 334 SetTaskLocalDir(allocdir.TaskLocalContainerPath).SetSecretsDir(allocdir.TaskSecretsContainerPath).Build() 335 336 driverConfig, err := NewDockerDriverConfig(task, d.taskEnv) 337 if err != nil { 338 return nil, err 339 } 340 341 cleanupImage := d.config.ReadBoolDefault("docker.cleanup.image", true) 342 343 taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName] 344 if !ok { 345 return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName) 346 } 347 348 // Initialize docker API clients 349 client, waitClient, err := d.dockerClients() 350 if err != nil { 351 return nil, fmt.Errorf("Failed to connect to docker daemon: %s", err) 352 } 353 354 if err := d.createImage(driverConfig, client, taskDir); err != nil { 355 return nil, err 356 } 357 358 image := driverConfig.ImageName 359 // Now that we have the image we can get the image id 360 dockerImage, err := client.InspectImage(image) 361 if err != nil { 362 d.logger.Printf("[ERR] driver.docker: failed getting image id for %s: %s", image, err) 363 return nil, fmt.Errorf("Failed to determine image id for `%s`: %s", image, err) 364 } 365 d.logger.Printf("[DEBUG] driver.docker: identified image %s as %s", image, dockerImage.ID) 366 367 bin, err := discover.NomadExecutable() 368 if err != nil { 369 return nil, fmt.Errorf("unable to find the nomad binary: %v", err) 370 } 371 pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name)) 372 pluginConfig := &plugin.ClientConfig{ 373 Cmd: exec.Command(bin, "executor", pluginLogFile), 374 } 375 376 exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 377 if err != nil { 378 return nil, err 379 } 380 executorCtx := &executor.ExecutorContext{ 381 TaskEnv: d.taskEnv, 382 Task: task, 383 Driver: "docker", 384 AllocDir: ctx.AllocDir, 385 AllocID: ctx.AllocID, 386 PortLowerBound: d.config.ClientMinPort, 387 PortUpperBound: d.config.ClientMaxPort, 388 } 389 if err := exec.SetContext(executorCtx); err != nil { 390 pluginClient.Kill() 391 return nil, fmt.Errorf("failed to set executor context: %v", err) 392 } 393 394 // Only launch syslog server if we're going to use it! 395 syslogAddr := "" 396 if runtime.GOOS == "darwin" && len(driverConfig.Logging) == 0 { 397 d.logger.Printf("[DEBUG] driver.docker: disabling syslog driver as Docker for Mac workaround") 398 } else if len(driverConfig.Logging) == 0 || driverConfig.Logging[0].Type == "syslog" { 399 ss, err := exec.LaunchSyslogServer() 400 if err != nil { 401 pluginClient.Kill() 402 return nil, fmt.Errorf("failed to start syslog collector: %v", err) 403 } 404 syslogAddr = ss.Addr 405 } 406 407 config, err := d.createContainerConfig(ctx, task, driverConfig, syslogAddr) 408 if err != nil { 409 d.logger.Printf("[ERR] driver.docker: failed to create container configuration for image %s: %s", image, err) 410 pluginClient.Kill() 411 return nil, fmt.Errorf("Failed to create container configuration for image %s: %s", image, err) 412 } 413 414 container, rerr := d.createContainer(config) 415 if rerr != nil { 416 d.logger.Printf("[ERR] driver.docker: failed to create container: %s", rerr) 417 pluginClient.Kill() 418 rerr.Err = fmt.Sprintf("Failed to create container: %s", rerr.Err) 419 return nil, rerr 420 } 421 422 d.logger.Printf("[INFO] driver.docker: created container %s", container.ID) 423 424 // Start the container 425 err = client.StartContainer(container.ID, container.HostConfig) 426 if err != nil { 427 d.logger.Printf("[ERR] driver.docker: failed to start container %s: %s", container.ID, err) 428 pluginClient.Kill() 429 return nil, fmt.Errorf("Failed to start container %s: %s", container.ID, err) 430 } 431 d.logger.Printf("[INFO] driver.docker: started container %s", container.ID) 432 433 // Return a driver handle 434 maxKill := d.DriverContext.config.MaxKillTimeout 435 h := &DockerHandle{ 436 client: client, 437 waitClient: waitClient, 438 executor: exec, 439 pluginClient: pluginClient, 440 cleanupImage: cleanupImage, 441 logger: d.logger, 442 imageID: dockerImage.ID, 443 containerID: container.ID, 444 version: d.config.Version, 445 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 446 maxKillTimeout: maxKill, 447 doneCh: make(chan bool), 448 waitCh: make(chan *dstructs.WaitResult, 1), 449 } 450 if err := exec.SyncServices(consulContext(d.config, container.ID)); err != nil { 451 d.logger.Printf("[ERR] driver.docker: error registering services with consul for task: %q: %v", task.Name, err) 452 } 453 go h.collectStats() 454 go h.run() 455 return h, nil 456 } 457 458 // dockerClients creates two *docker.Client, one for long running operations and 459 // the other for shorter operations. In test / dev mode we can use ENV vars to 460 // connect to the docker daemon. In production mode we will read docker.endpoint 461 // from the config file. 462 func (d *DockerDriver) dockerClients() (*docker.Client, *docker.Client, error) { 463 if client != nil && waitClient != nil { 464 return client, waitClient, nil 465 } 466 467 var err error 468 var merr multierror.Error 469 createClients.Do(func() { 470 if err = shelpers.Init(); err != nil { 471 d.logger.Printf("[FATAL] driver.docker: unable to initialize stats: %v", err) 472 return 473 } 474 475 // Default to using whatever is configured in docker.endpoint. If this is 476 // not specified we'll fall back on NewClientFromEnv which reads config from 477 // the DOCKER_* environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and 478 // DOCKER_CERT_PATH. This allows us to lock down the config in production 479 // but also accept the standard ENV configs for dev and test. 480 dockerEndpoint := d.config.Read("docker.endpoint") 481 if dockerEndpoint != "" { 482 cert := d.config.Read("docker.tls.cert") 483 key := d.config.Read("docker.tls.key") 484 ca := d.config.Read("docker.tls.ca") 485 486 if cert+key+ca != "" { 487 d.logger.Printf("[DEBUG] driver.docker: using TLS client connection to %s", dockerEndpoint) 488 client, err = docker.NewTLSClient(dockerEndpoint, cert, key, ca) 489 if err != nil { 490 merr.Errors = append(merr.Errors, err) 491 } 492 waitClient, err = docker.NewTLSClient(dockerEndpoint, cert, key, ca) 493 if err != nil { 494 merr.Errors = append(merr.Errors, err) 495 } 496 } else { 497 d.logger.Printf("[DEBUG] driver.docker: using standard client connection to %s", dockerEndpoint) 498 client, err = docker.NewClient(dockerEndpoint) 499 if err != nil { 500 merr.Errors = append(merr.Errors, err) 501 } 502 waitClient, err = docker.NewClient(dockerEndpoint) 503 if err != nil { 504 merr.Errors = append(merr.Errors, err) 505 } 506 } 507 client.SetTimeout(dockerTimeout) 508 return 509 } 510 511 d.logger.Println("[DEBUG] driver.docker: using client connection initialized from environment") 512 client, err = docker.NewClientFromEnv() 513 if err != nil { 514 merr.Errors = append(merr.Errors, err) 515 } 516 client.SetTimeout(dockerTimeout) 517 518 waitClient, err = docker.NewClientFromEnv() 519 if err != nil { 520 merr.Errors = append(merr.Errors, err) 521 } 522 }) 523 return client, waitClient, merr.ErrorOrNil() 524 } 525 526 func (d *DockerDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 527 // Get the current status so that we can log any debug messages only if the 528 // state changes 529 _, currentlyEnabled := node.Attributes[dockerDriverAttr] 530 531 // Initialize docker API clients 532 client, _, err := d.dockerClients() 533 if err != nil { 534 delete(node.Attributes, dockerDriverAttr) 535 if currentlyEnabled { 536 d.logger.Printf("[INFO] driver.docker: failed to initialize client: %s", err) 537 } 538 return false, nil 539 } 540 541 privileged := d.config.ReadBoolDefault(dockerPrivilegedConfigOption, false) 542 if privileged { 543 node.Attributes[dockerPrivilegedConfigOption] = "1" 544 } 545 546 // This is the first operation taken on the client so we'll try to 547 // establish a connection to the Docker daemon. If this fails it means 548 // Docker isn't available so we'll simply disable the docker driver. 549 env, err := client.Version() 550 if err != nil { 551 if currentlyEnabled { 552 d.logger.Printf("[DEBUG] driver.docker: could not connect to docker daemon at %s: %s", client.Endpoint(), err) 553 } 554 delete(node.Attributes, dockerDriverAttr) 555 return false, nil 556 } 557 558 node.Attributes[dockerDriverAttr] = "1" 559 node.Attributes["driver.docker.version"] = env.Get("Version") 560 561 // Advertise if this node supports Docker volumes 562 if d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault) { 563 node.Attributes["driver."+dockerVolumesConfigOption] = "1" 564 } 565 566 return true, nil 567 } 568 569 func (d *DockerDriver) containerBinds(driverConfig *DockerDriverConfig, alloc *allocdir.AllocDir, 570 task *structs.Task) ([]string, error) { 571 572 shared := alloc.SharedDir 573 taskDir, ok := alloc.TaskDirs[task.Name] 574 if !ok { 575 return nil, fmt.Errorf("Failed to find task local directory: %v", task.Name) 576 } 577 local := filepath.Join(taskDir, allocdir.TaskLocal) 578 579 secret, err := alloc.GetSecretDir(task.Name) 580 if err != nil { 581 return nil, err 582 } 583 584 allocDirBind := fmt.Sprintf("%s:%s", shared, allocdir.SharedAllocContainerPath) 585 taskLocalBind := fmt.Sprintf("%s:%s", local, allocdir.TaskLocalContainerPath) 586 secretDirBind := fmt.Sprintf("%s:%s", secret, allocdir.TaskSecretsContainerPath) 587 binds := []string{allocDirBind, taskLocalBind, secretDirBind} 588 589 volumesEnabled := d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault) 590 591 for _, userbind := range driverConfig.Volumes { 592 parts := strings.Split(userbind, ":") 593 if len(parts) < 2 { 594 return nil, fmt.Errorf("invalid docker volume: %q", userbind) 595 } 596 597 // Resolve dotted path segments 598 parts[0] = filepath.Clean(parts[0]) 599 600 // Absolute paths aren't always supported 601 if filepath.IsAbs(parts[0]) { 602 if !volumesEnabled { 603 // Disallow mounting arbitrary absolute paths 604 return nil, fmt.Errorf("%s is false; cannot mount host paths: %+q", dockerVolumesConfigOption, userbind) 605 } 606 binds = append(binds, userbind) 607 continue 608 } 609 610 // Relative paths are always allowed as they mount within a container 611 // Expand path relative to alloc dir 612 parts[0] = filepath.Join(shared, parts[0]) 613 binds = append(binds, strings.Join(parts, ":")) 614 } 615 616 if selinuxLabel := d.config.Read(dockerSELinuxLabelConfigOption); selinuxLabel != "" { 617 // Apply SELinux Label to each volume 618 for i := range binds { 619 binds[i] = fmt.Sprintf("%s:%s", binds[i], selinuxLabel) 620 } 621 } 622 623 return binds, nil 624 } 625 626 // createContainerConfig initializes a struct needed to call docker.client.CreateContainer() 627 func (d *DockerDriver) createContainerConfig(ctx *ExecContext, task *structs.Task, 628 driverConfig *DockerDriverConfig, syslogAddr string) (docker.CreateContainerOptions, error) { 629 var c docker.CreateContainerOptions 630 if task.Resources == nil { 631 // Guard against missing resources. We should never have been able to 632 // schedule a job without specifying this. 633 d.logger.Println("[ERR] driver.docker: task.Resources is empty") 634 return c, fmt.Errorf("task.Resources is empty") 635 } 636 637 binds, err := d.containerBinds(driverConfig, ctx.AllocDir, task) 638 if err != nil { 639 return c, err 640 } 641 642 config := &docker.Config{ 643 Image: driverConfig.ImageName, 644 Hostname: driverConfig.Hostname, 645 User: task.User, 646 Tty: driverConfig.TTY, 647 OpenStdin: driverConfig.Interactive, 648 } 649 650 if driverConfig.WorkDir != "" { 651 config.WorkingDir = driverConfig.WorkDir 652 } 653 654 memLimit := int64(task.Resources.MemoryMB) * 1024 * 1024 655 656 if len(driverConfig.Logging) == 0 { 657 if runtime.GOOS != "darwin" { 658 d.logger.Printf("[DEBUG] driver.docker: Setting default logging options to syslog and %s", syslogAddr) 659 driverConfig.Logging = []DockerLoggingOpts{ 660 {Type: "syslog", Config: map[string]string{"syslog-address": syslogAddr}}, 661 } 662 } else { 663 d.logger.Printf("[DEBUG] driver.docker: deferring logging to docker on Docker for Mac") 664 } 665 } 666 667 hostConfig := &docker.HostConfig{ 668 // Convert MB to bytes. This is an absolute value. 669 Memory: memLimit, 670 MemorySwap: memLimit, // MemorySwap is memory + swap. 671 // Convert Mhz to shares. This is a relative value. 672 CPUShares: int64(task.Resources.CPU), 673 674 // Binds are used to mount a host volume into the container. We mount a 675 // local directory for storage and a shared alloc directory that can be 676 // used to share data between different tasks in the same task group. 677 Binds: binds, 678 } 679 680 if len(driverConfig.Logging) != 0 { 681 d.logger.Printf("[DEBUG] driver.docker: Using config for logging: %+v", driverConfig.Logging[0]) 682 hostConfig.LogConfig = docker.LogConfig{ 683 Type: driverConfig.Logging[0].Type, 684 Config: driverConfig.Logging[0].Config, 685 } 686 } 687 688 d.logger.Printf("[DEBUG] driver.docker: using %d bytes memory for %s", hostConfig.Memory, task.Name) 689 d.logger.Printf("[DEBUG] driver.docker: using %d cpu shares for %s", hostConfig.CPUShares, task.Name) 690 d.logger.Printf("[DEBUG] driver.docker: binding directories %#v for %s", hostConfig.Binds, task.Name) 691 692 // set privileged mode 693 hostPrivileged := d.config.ReadBoolDefault(dockerPrivilegedConfigOption, false) 694 if driverConfig.Privileged && !hostPrivileged { 695 return c, fmt.Errorf(`Docker privileged mode is disabled on this Nomad agent`) 696 } 697 hostConfig.Privileged = driverConfig.Privileged 698 699 // set SHM size 700 if driverConfig.ShmSize != 0 { 701 hostConfig.ShmSize = driverConfig.ShmSize 702 } 703 704 // set DNS servers 705 for _, ip := range driverConfig.DNSServers { 706 if net.ParseIP(ip) != nil { 707 hostConfig.DNS = append(hostConfig.DNS, ip) 708 } else { 709 d.logger.Printf("[ERR] driver.docker: invalid ip address for container dns server: %s", ip) 710 } 711 } 712 713 // set DNS search domains 714 for _, domain := range driverConfig.DNSSearchDomains { 715 hostConfig.DNSSearch = append(hostConfig.DNSSearch, domain) 716 } 717 718 hostConfig.IpcMode = driverConfig.IpcMode 719 hostConfig.PidMode = driverConfig.PidMode 720 hostConfig.UTSMode = driverConfig.UTSMode 721 hostConfig.UsernsMode = driverConfig.UsernsMode 722 723 hostConfig.NetworkMode = driverConfig.NetworkMode 724 if hostConfig.NetworkMode == "" { 725 // docker default 726 d.logger.Printf("[DEBUG] driver.docker: networking mode not specified; defaulting to %s", defaultNetworkMode) 727 hostConfig.NetworkMode = defaultNetworkMode 728 } 729 730 // Setup port mapping and exposed ports 731 if len(task.Resources.Networks) == 0 { 732 d.logger.Println("[DEBUG] driver.docker: No network interfaces are available") 733 if len(driverConfig.PortMap) > 0 { 734 return c, fmt.Errorf("Trying to map ports but no network interface is available") 735 } 736 } else { 737 // TODO add support for more than one network 738 network := task.Resources.Networks[0] 739 publishedPorts := map[docker.Port][]docker.PortBinding{} 740 exposedPorts := map[docker.Port]struct{}{} 741 742 for _, port := range network.ReservedPorts { 743 // By default we will map the allocated port 1:1 to the container 744 containerPortInt := port.Value 745 746 // If the user has mapped a port using port_map we'll change it here 747 if mapped, ok := driverConfig.PortMap[port.Label]; ok { 748 containerPortInt = mapped 749 } 750 751 hostPortStr := strconv.Itoa(port.Value) 752 containerPort := docker.Port(strconv.Itoa(containerPortInt)) 753 754 publishedPorts[containerPort+"/tcp"] = getPortBinding(network.IP, hostPortStr) 755 publishedPorts[containerPort+"/udp"] = getPortBinding(network.IP, hostPortStr) 756 d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (static)", network.IP, port.Value, port.Value) 757 758 exposedPorts[containerPort+"/tcp"] = struct{}{} 759 exposedPorts[containerPort+"/udp"] = struct{}{} 760 d.logger.Printf("[DEBUG] driver.docker: exposed port %d", port.Value) 761 } 762 763 for _, port := range network.DynamicPorts { 764 // By default we will map the allocated port 1:1 to the container 765 containerPortInt := port.Value 766 767 // If the user has mapped a port using port_map we'll change it here 768 if mapped, ok := driverConfig.PortMap[port.Label]; ok { 769 containerPortInt = mapped 770 } 771 772 hostPortStr := strconv.Itoa(port.Value) 773 containerPort := docker.Port(strconv.Itoa(containerPortInt)) 774 775 publishedPorts[containerPort+"/tcp"] = getPortBinding(network.IP, hostPortStr) 776 publishedPorts[containerPort+"/udp"] = getPortBinding(network.IP, hostPortStr) 777 d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (mapped)", network.IP, port.Value, containerPortInt) 778 779 exposedPorts[containerPort+"/tcp"] = struct{}{} 780 exposedPorts[containerPort+"/udp"] = struct{}{} 781 d.logger.Printf("[DEBUG] driver.docker: exposed port %s", containerPort) 782 } 783 784 d.taskEnv.SetPortMap(driverConfig.PortMap) 785 786 hostConfig.PortBindings = publishedPorts 787 config.ExposedPorts = exposedPorts 788 } 789 790 d.taskEnv.Build() 791 parsedArgs := d.taskEnv.ParseAndReplace(driverConfig.Args) 792 793 // If the user specified a custom command to run as their entrypoint, we'll 794 // inject it here. 795 if driverConfig.Command != "" { 796 // Validate command 797 if err := validateCommand(driverConfig.Command, "args"); err != nil { 798 return c, err 799 } 800 801 cmd := []string{driverConfig.Command} 802 if len(driverConfig.Args) != 0 { 803 cmd = append(cmd, parsedArgs...) 804 } 805 d.logger.Printf("[DEBUG] driver.docker: setting container startup command to: %s", strings.Join(cmd, " ")) 806 config.Cmd = cmd 807 } else if len(driverConfig.Args) != 0 { 808 config.Cmd = parsedArgs 809 } 810 811 if len(driverConfig.Labels) > 0 { 812 config.Labels = driverConfig.Labels 813 d.logger.Printf("[DEBUG] driver.docker: applied labels on the container: %+v", config.Labels) 814 } 815 816 config.Env = d.taskEnv.EnvList() 817 818 containerName := fmt.Sprintf("%s-%s", task.Name, ctx.AllocID) 819 d.logger.Printf("[DEBUG] driver.docker: setting container name to: %s", containerName) 820 821 return docker.CreateContainerOptions{ 822 Name: containerName, 823 Config: config, 824 HostConfig: hostConfig, 825 }, nil 826 } 827 828 var ( 829 // imageNotFoundMatcher is a regex expression that matches the image not 830 // found error Docker returns. 831 imageNotFoundMatcher = regexp.MustCompile(`Error: image .+ not found`) 832 ) 833 834 // recoverablePullError wraps the error gotten when trying to pull and image if 835 // the error is recoverable. 836 func (d *DockerDriver) recoverablePullError(err error, image string) error { 837 recoverable := true 838 if imageNotFoundMatcher.MatchString(err.Error()) { 839 recoverable = false 840 } 841 return structs.NewRecoverableError(fmt.Errorf("Failed to pull `%s`: %s", image, err), recoverable) 842 } 843 844 func (d *DockerDriver) Periodic() (bool, time.Duration) { 845 return true, 15 * time.Second 846 } 847 848 // createImage creates a docker image either by pulling it from a registry or by 849 // loading it from the file system 850 func (d *DockerDriver) createImage(driverConfig *DockerDriverConfig, client *docker.Client, taskDir string) error { 851 image := driverConfig.ImageName 852 repo, tag := docker.ParseRepositoryTag(image) 853 if tag == "" { 854 tag = "latest" 855 } 856 857 var dockerImage *docker.Image 858 var err error 859 // We're going to check whether the image is already downloaded. If the tag 860 // is "latest" we have to check for a new version every time so we don't 861 // bother to check and cache the id here. We'll download first, then cache. 862 if tag != "latest" { 863 dockerImage, err = client.InspectImage(image) 864 } 865 866 // Download the image 867 if dockerImage == nil { 868 if len(driverConfig.LoadImages) > 0 { 869 return d.loadImage(driverConfig, client, taskDir) 870 } 871 872 return d.pullImage(driverConfig, client, repo, tag) 873 } 874 return err 875 } 876 877 // pullImage creates an image by pulling it from a docker registry 878 func (d *DockerDriver) pullImage(driverConfig *DockerDriverConfig, client *docker.Client, repo string, tag string) error { 879 pullOptions := docker.PullImageOptions{ 880 Repository: repo, 881 Tag: tag, 882 } 883 884 authOptions := docker.AuthConfiguration{} 885 if len(driverConfig.Auth) != 0 { 886 authOptions = docker.AuthConfiguration{ 887 Username: driverConfig.Auth[0].Username, 888 Password: driverConfig.Auth[0].Password, 889 Email: driverConfig.Auth[0].Email, 890 ServerAddress: driverConfig.Auth[0].ServerAddress, 891 } 892 } 893 894 if authConfigFile := d.config.Read("docker.auth.config"); authConfigFile != "" { 895 if f, err := os.Open(authConfigFile); err == nil { 896 defer f.Close() 897 var authConfigurations *docker.AuthConfigurations 898 if authConfigurations, err = docker.NewAuthConfigurations(f); err != nil { 899 return fmt.Errorf("Failed to create docker auth object: %v", err) 900 } 901 902 authConfigurationKey := "" 903 if driverConfig.SSL { 904 authConfigurationKey += "https://" 905 } 906 907 authConfigurationKey += strings.Split(driverConfig.ImageName, "/")[0] 908 if authConfiguration, ok := authConfigurations.Configs[authConfigurationKey]; ok { 909 authOptions = authConfiguration 910 } 911 } else { 912 return fmt.Errorf("Failed to open auth config file: %v, error: %v", authConfigFile, err) 913 } 914 } 915 916 err := client.PullImage(pullOptions, authOptions) 917 if err != nil { 918 d.logger.Printf("[ERR] driver.docker: failed pulling container %s:%s: %s", repo, tag, err) 919 return d.recoverablePullError(err, driverConfig.ImageName) 920 } 921 d.logger.Printf("[DEBUG] driver.docker: docker pull %s:%s succeeded", repo, tag) 922 return nil 923 } 924 925 // loadImage creates an image by loading it from the file system 926 func (d *DockerDriver) loadImage(driverConfig *DockerDriverConfig, client *docker.Client, taskDir string) error { 927 var errors multierror.Error 928 for _, image := range driverConfig.LoadImages { 929 archive := filepath.Join(taskDir, allocdir.TaskLocal, image) 930 d.logger.Printf("[DEBUG] driver.docker: loading image from: %v", archive) 931 f, err := os.Open(archive) 932 if err != nil { 933 errors.Errors = append(errors.Errors, fmt.Errorf("unable to open image archive: %v", err)) 934 continue 935 } 936 if err := client.LoadImage(docker.LoadImageOptions{InputStream: f}); err != nil { 937 errors.Errors = append(errors.Errors, err) 938 } 939 f.Close() 940 } 941 return errors.ErrorOrNil() 942 } 943 944 // createContainer creates the container given the passed configuration. It 945 // attempts to handle any transient Docker errors. 946 func (d *DockerDriver) createContainer(config docker.CreateContainerOptions) (*docker.Container, *structs.RecoverableError) { 947 attempted := 0 948 949 recoverable := func(err error) *structs.RecoverableError { 950 r := false 951 if strings.Contains(err.Error(), "Client.Timeout exceeded while awaiting headers") || 952 strings.Contains(err.Error(), "EOF") || 953 strings.Contains(err.Error(), "container already exists") { 954 r = true 955 } 956 return structs.NewRecoverableError(err, r) 957 } 958 959 // Create a container 960 CREATE: 961 container, err := client.CreateContainer(config) 962 if err == nil { 963 return container, nil 964 } 965 966 if strings.Contains(strings.ToLower(err.Error()), "container already exists") { 967 containers, err := client.ListContainers(docker.ListContainersOptions{}) 968 if err != nil { 969 d.logger.Printf("[ERR] driver.docker: failed to query list of containers matching name:%s", config.Name) 970 return nil, recoverable(fmt.Errorf("Failed to query list of containers: %s", err)) 971 } 972 973 // Delete matching containers 974 for _, container := range containers { 975 found := false 976 for _, name := range container.Names { 977 if name == config.Name { 978 found = true 979 break 980 } 981 } 982 983 if !found { 984 continue 985 } 986 987 err = client.RemoveContainer(docker.RemoveContainerOptions{ 988 ID: container.ID, 989 }) 990 if err != nil { 991 d.logger.Printf("[ERR] driver.docker: failed to purge container %s", container.ID) 992 return nil, recoverable(fmt.Errorf("Failed to purge container %s: %s", container.ID, err)) 993 } else if err == nil { 994 d.logger.Printf("[INFO] driver.docker: purged container %s", container.ID) 995 } 996 } 997 998 if attempted < 5 { 999 attempted++ 1000 goto CREATE 1001 } 1002 } 1003 1004 return nil, recoverable(err) 1005 } 1006 1007 func (d *DockerDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 1008 cleanupImage := d.config.ReadBoolDefault("docker.cleanup.image", true) 1009 1010 // Split the handle 1011 pidBytes := []byte(strings.TrimPrefix(handleID, "DOCKER:")) 1012 pid := &dockerPID{} 1013 if err := json.Unmarshal(pidBytes, pid); err != nil { 1014 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 1015 } 1016 d.logger.Printf("[INFO] driver.docker: re-attaching to docker process: %s", pid.ContainerID) 1017 d.logger.Printf("[DEBUG] driver.docker: re-attached to handle: %s", handleID) 1018 pluginConfig := &plugin.ClientConfig{ 1019 Reattach: pid.PluginConfig.PluginConfig(), 1020 } 1021 1022 client, waitClient, err := d.dockerClients() 1023 if err != nil { 1024 return nil, fmt.Errorf("Failed to connect to docker daemon: %s", err) 1025 } 1026 1027 // Look for a running container with this ID 1028 containers, err := client.ListContainers(docker.ListContainersOptions{ 1029 Filters: map[string][]string{ 1030 "id": []string{pid.ContainerID}, 1031 }, 1032 }) 1033 if err != nil { 1034 return nil, fmt.Errorf("Failed to query for container %s: %v", pid.ContainerID, err) 1035 } 1036 1037 found := false 1038 for _, container := range containers { 1039 if container.ID == pid.ContainerID { 1040 found = true 1041 } 1042 } 1043 if !found { 1044 return nil, fmt.Errorf("Failed to find container %s", pid.ContainerID) 1045 } 1046 exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 1047 if err != nil { 1048 d.logger.Printf("[INFO] driver.docker: couldn't re-attach to the plugin process: %v", err) 1049 d.logger.Printf("[DEBUG] driver.docker: stopping container %q", pid.ContainerID) 1050 if e := client.StopContainer(pid.ContainerID, uint(pid.KillTimeout.Seconds())); e != nil { 1051 d.logger.Printf("[DEBUG] driver.docker: couldn't stop container: %v", e) 1052 } 1053 return nil, err 1054 } 1055 1056 ver, _ := exec.Version() 1057 d.logger.Printf("[DEBUG] driver.docker: version of executor: %v", ver.Version) 1058 1059 // Return a driver handle 1060 h := &DockerHandle{ 1061 client: client, 1062 waitClient: waitClient, 1063 executor: exec, 1064 pluginClient: pluginClient, 1065 cleanupImage: cleanupImage, 1066 logger: d.logger, 1067 imageID: pid.ImageID, 1068 containerID: pid.ContainerID, 1069 version: pid.Version, 1070 killTimeout: pid.KillTimeout, 1071 maxKillTimeout: pid.MaxKillTimeout, 1072 doneCh: make(chan bool), 1073 waitCh: make(chan *dstructs.WaitResult, 1), 1074 } 1075 if err := exec.SyncServices(consulContext(d.config, pid.ContainerID)); err != nil { 1076 h.logger.Printf("[ERR] driver.docker: error registering services with consul: %v", err) 1077 } 1078 1079 go h.collectStats() 1080 go h.run() 1081 return h, nil 1082 } 1083 1084 func (h *DockerHandle) ID() string { 1085 // Return a handle to the PID 1086 pid := dockerPID{ 1087 Version: h.version, 1088 ImageID: h.imageID, 1089 ContainerID: h.containerID, 1090 KillTimeout: h.killTimeout, 1091 MaxKillTimeout: h.maxKillTimeout, 1092 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 1093 } 1094 data, err := json.Marshal(pid) 1095 if err != nil { 1096 h.logger.Printf("[ERR] driver.docker: failed to marshal docker PID to JSON: %s", err) 1097 } 1098 return fmt.Sprintf("DOCKER:%s", string(data)) 1099 } 1100 1101 func (h *DockerHandle) ContainerID() string { 1102 return h.containerID 1103 } 1104 1105 func (h *DockerHandle) WaitCh() chan *dstructs.WaitResult { 1106 return h.waitCh 1107 } 1108 1109 func (h *DockerHandle) Update(task *structs.Task) error { 1110 // Store the updated kill timeout. 1111 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 1112 if err := h.executor.UpdateTask(task); err != nil { 1113 h.logger.Printf("[DEBUG] driver.docker: failed to update log config: %v", err) 1114 } 1115 1116 // Update is not possible 1117 return nil 1118 } 1119 1120 func (h *DockerHandle) Signal(s os.Signal) error { 1121 // Convert types 1122 sysSig, ok := s.(syscall.Signal) 1123 if !ok { 1124 return fmt.Errorf("Failed to determine signal number") 1125 } 1126 1127 dockerSignal := docker.Signal(sysSig) 1128 opts := docker.KillContainerOptions{ 1129 ID: h.containerID, 1130 Signal: dockerSignal, 1131 } 1132 return h.client.KillContainer(opts) 1133 1134 } 1135 1136 // Kill is used to terminate the task. This uses `docker stop -t killTimeout` 1137 func (h *DockerHandle) Kill() error { 1138 // Stop the container 1139 err := h.client.StopContainer(h.containerID, uint(h.killTimeout.Seconds())) 1140 if err != nil { 1141 h.executor.Exit() 1142 h.pluginClient.Kill() 1143 1144 // Container has already been removed. 1145 if strings.Contains(err.Error(), NoSuchContainerError) { 1146 h.logger.Printf("[DEBUG] driver.docker: attempted to stop non-existent container %s", h.containerID) 1147 return nil 1148 } 1149 h.logger.Printf("[ERR] driver.docker: failed to stop container %s: %v", h.containerID, err) 1150 return fmt.Errorf("Failed to stop container %s: %s", h.containerID, err) 1151 } 1152 h.logger.Printf("[INFO] driver.docker: stopped container %s", h.containerID) 1153 return nil 1154 } 1155 1156 func (h *DockerHandle) Stats() (*cstructs.TaskResourceUsage, error) { 1157 h.resourceUsageLock.RLock() 1158 defer h.resourceUsageLock.RUnlock() 1159 var err error 1160 if h.resourceUsage == nil { 1161 err = fmt.Errorf("stats collection hasn't started yet") 1162 } 1163 return h.resourceUsage, err 1164 } 1165 1166 func (h *DockerHandle) run() { 1167 // Wait for it... 1168 exitCode, werr := h.waitClient.WaitContainer(h.containerID) 1169 if werr != nil { 1170 h.logger.Printf("[ERR] driver.docker: failed to wait for %s; container already terminated", h.containerID) 1171 } 1172 1173 if exitCode != 0 { 1174 werr = fmt.Errorf("Docker container exited with non-zero exit code: %d", exitCode) 1175 } 1176 1177 close(h.doneCh) 1178 1179 // Remove services 1180 if err := h.executor.DeregisterServices(); err != nil { 1181 h.logger.Printf("[ERR] driver.docker: error deregistering services: %v", err) 1182 } 1183 1184 // Shutdown the syslog collector 1185 if err := h.executor.Exit(); err != nil { 1186 h.logger.Printf("[ERR] driver.docker: failed to kill the syslog collector: %v", err) 1187 } 1188 h.pluginClient.Kill() 1189 1190 // Stop the container just incase the docker daemon's wait returned 1191 // incorrectly 1192 if err := h.client.StopContainer(h.containerID, 0); err != nil { 1193 _, noSuchContainer := err.(*docker.NoSuchContainer) 1194 _, containerNotRunning := err.(*docker.ContainerNotRunning) 1195 if !containerNotRunning && !noSuchContainer { 1196 h.logger.Printf("[ERR] driver.docker: error stopping container: %v", err) 1197 } 1198 } 1199 1200 // Remove the container 1201 if err := h.client.RemoveContainer(docker.RemoveContainerOptions{ID: h.containerID, RemoveVolumes: true, Force: true}); err != nil { 1202 h.logger.Printf("[ERR] driver.docker: error removing container: %v", err) 1203 } 1204 1205 // Cleanup the image 1206 if h.cleanupImage { 1207 if err := h.client.RemoveImage(h.imageID); err != nil { 1208 h.logger.Printf("[DEBUG] driver.docker: error removing image: %v", err) 1209 } 1210 } 1211 1212 // Send the results 1213 h.waitCh <- dstructs.NewWaitResult(exitCode, 0, werr) 1214 close(h.waitCh) 1215 } 1216 1217 // collectStats starts collecting resource usage stats of a docker container 1218 func (h *DockerHandle) collectStats() { 1219 statsCh := make(chan *docker.Stats) 1220 statsOpts := docker.StatsOptions{ID: h.containerID, Done: h.doneCh, Stats: statsCh, Stream: true} 1221 go func() { 1222 //TODO handle Stats error 1223 if err := h.waitClient.Stats(statsOpts); err != nil { 1224 h.logger.Printf("[DEBUG] driver.docker: error collecting stats from container %s: %v", h.containerID, err) 1225 } 1226 }() 1227 numCores := runtime.NumCPU() 1228 for { 1229 select { 1230 case s := <-statsCh: 1231 if s != nil { 1232 ms := &cstructs.MemoryStats{ 1233 RSS: s.MemoryStats.Stats.Rss, 1234 Cache: s.MemoryStats.Stats.Cache, 1235 Swap: s.MemoryStats.Stats.Swap, 1236 MaxUsage: s.MemoryStats.MaxUsage, 1237 Measured: DockerMeasuredMemStats, 1238 } 1239 1240 cs := &cstructs.CpuStats{ 1241 ThrottledPeriods: s.CPUStats.ThrottlingData.ThrottledPeriods, 1242 ThrottledTime: s.CPUStats.ThrottlingData.ThrottledTime, 1243 Measured: DockerMeasuredCpuStats, 1244 } 1245 1246 // Calculate percentage 1247 cores := len(s.CPUStats.CPUUsage.PercpuUsage) 1248 cs.Percent = calculatePercent( 1249 s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage, 1250 s.CPUStats.SystemCPUUsage, s.PreCPUStats.SystemCPUUsage, cores) 1251 cs.SystemMode = calculatePercent( 1252 s.CPUStats.CPUUsage.UsageInKernelmode, s.PreCPUStats.CPUUsage.UsageInKernelmode, 1253 s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage, cores) 1254 cs.UserMode = calculatePercent( 1255 s.CPUStats.CPUUsage.UsageInUsermode, s.PreCPUStats.CPUUsage.UsageInUsermode, 1256 s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage, cores) 1257 cs.TotalTicks = (cs.Percent / 100) * shelpers.TotalTicksAvailable() / float64(numCores) 1258 1259 h.resourceUsageLock.Lock() 1260 h.resourceUsage = &cstructs.TaskResourceUsage{ 1261 ResourceUsage: &cstructs.ResourceUsage{ 1262 MemoryStats: ms, 1263 CpuStats: cs, 1264 }, 1265 Timestamp: s.Read.UTC().UnixNano(), 1266 } 1267 h.resourceUsageLock.Unlock() 1268 } 1269 case <-h.doneCh: 1270 return 1271 } 1272 } 1273 } 1274 1275 func calculatePercent(newSample, oldSample, newTotal, oldTotal uint64, cores int) float64 { 1276 numerator := newSample - oldSample 1277 denom := newTotal - oldTotal 1278 if numerator <= 0 || denom <= 0 { 1279 return 0.0 1280 } 1281 1282 return (float64(numerator) / float64(denom)) * float64(cores) * 100.0 1283 }