github.com/bigcommerce/nomad@v0.9.3-bc/drivers/docker/config.go (about) 1 package docker 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "time" 8 9 docker "github.com/fsouza/go-dockerclient" 10 hclog "github.com/hashicorp/go-hclog" 11 "github.com/hashicorp/nomad/helper/pluginutils/hclutils" 12 "github.com/hashicorp/nomad/helper/pluginutils/loader" 13 "github.com/hashicorp/nomad/plugins/base" 14 "github.com/hashicorp/nomad/plugins/drivers" 15 "github.com/hashicorp/nomad/plugins/shared/hclspec" 16 ) 17 18 const ( 19 // NoSuchContainerError is returned by the docker daemon if the container 20 // does not exist. 21 NoSuchContainerError = "No such container" 22 23 // ContainerNotRunningError is returned by the docker daemon if the container 24 // is not running, yet we requested it to stop 25 ContainerNotRunningError = "Container not running" 26 27 // pluginName is the name of the plugin 28 pluginName = "docker" 29 30 // fingerprintPeriod is the interval at which the driver will send fingerprint responses 31 fingerprintPeriod = 30 * time.Second 32 33 // dockerTimeout is the length of time a request can be outstanding before 34 // it is timed out. 35 dockerTimeout = 5 * time.Minute 36 37 // dockerBasicCaps is comma-separated list of Linux capabilities that are 38 // allowed by docker by default, as documented in 39 // https://docs.docker.com/engine/reference/run/#block-io-bandwidth-blkio-constraint 40 dockerBasicCaps = "CHOWN,DAC_OVERRIDE,FSETID,FOWNER,MKNOD,NET_RAW,SETGID," + 41 "SETUID,SETFCAP,SETPCAP,NET_BIND_SERVICE,SYS_CHROOT,KILL,AUDIT_WRITE" 42 43 // dockerAuthHelperPrefix is the prefix to attach to the credential helper 44 // and should be found in the $PATH. Example: ${prefix-}${helper-name} 45 dockerAuthHelperPrefix = "docker-credential-" 46 ) 47 48 func PluginLoader(opts map[string]string) (map[string]interface{}, error) { 49 conf := map[string]interface{}{} 50 if v, ok := opts["docker.endpoint"]; ok { 51 conf["endpoint"] = v 52 } 53 54 // dockerd auth 55 authConf := map[string]interface{}{} 56 if v, ok := opts["docker.auth.config"]; ok { 57 authConf["config"] = v 58 } 59 if v, ok := opts["docker.auth.helper"]; ok { 60 authConf["helper"] = v 61 } 62 conf["auth"] = authConf 63 64 // dockerd tls 65 if _, ok := opts["docker.tls.cert"]; ok { 66 conf["tls"] = map[string]interface{}{ 67 "cert": opts["docker.tls.cert"], 68 "key": opts["docker.tls.key"], 69 "ca": opts["docker.tls.ca"], 70 } 71 } 72 73 // garbage collection 74 gcConf := map[string]interface{}{} 75 if v, err := strconv.ParseBool(opts["docker.cleanup.image"]); err == nil { 76 gcConf["image"] = v 77 } 78 if v, ok := opts["docker.cleanup.image.delay"]; ok { 79 gcConf["image_delay"] = v 80 } 81 if v, err := strconv.ParseBool(opts["docker.cleanup.container"]); err == nil { 82 gcConf["container"] = v 83 } 84 conf["gc"] = gcConf 85 86 // volume options 87 volConf := map[string]interface{}{} 88 if v, err := strconv.ParseBool(opts["docker.volumes.enabled"]); err == nil { 89 volConf["enabled"] = v 90 } 91 if v, ok := opts["docker.volumes.selinuxlabel"]; ok { 92 volConf["selinuxlabel"] = v 93 } 94 conf["volumes"] = volConf 95 96 // capabilities 97 if v, ok := opts["docker.caps.whitelist"]; ok { 98 conf["allow_caps"] = strings.Split(v, ",") 99 } 100 101 // privileged containers 102 if v, err := strconv.ParseBool(opts["docker.privileged.enabled"]); err == nil { 103 conf["allow_privileged"] = v 104 } 105 106 // nvidia_runtime 107 if v, ok := opts["docker.nvidia_runtime"]; ok { 108 conf["nvidia_runtime"] = v 109 } 110 111 return conf, nil 112 } 113 114 var ( 115 // PluginID is the rawexec plugin metadata registered in the plugin 116 // catalog. 117 PluginID = loader.PluginID{ 118 Name: pluginName, 119 PluginType: base.PluginTypeDriver, 120 } 121 122 // PluginConfig is the rawexec factory function registered in the 123 // plugin catalog. 124 PluginConfig = &loader.InternalPluginConfig{ 125 Config: map[string]interface{}{}, 126 Factory: func(l hclog.Logger) interface{} { return NewDockerDriver(l) }, 127 } 128 129 // pluginInfo is the response returned for the PluginInfo RPC 130 pluginInfo = &base.PluginInfoResponse{ 131 Type: base.PluginTypeDriver, 132 PluginApiVersions: []string{drivers.ApiVersion010}, 133 PluginVersion: "0.1.0", 134 Name: pluginName, 135 } 136 137 // configSpec is the hcl specification returned by the ConfigSchema RPC 138 // and is used to parse the contents of the 'plugin "docker" {...}' block. 139 // Example: 140 // plugin "docker" { 141 // config { 142 // endpoint = "unix:///var/run/docker.sock" 143 // auth { 144 // config = "/etc/docker-auth.json" 145 // helper = "docker-credential-aws" 146 // } 147 // tls { 148 // cert = "/etc/nomad/nomad.pub" 149 // key = "/etc/nomad/nomad.pem" 150 // ca = "/etc/nomad/nomad.cert" 151 // } 152 // gc { 153 // image = true 154 // image_delay = "5m" 155 // container = false 156 // } 157 // volumes { 158 // enabled = true 159 // selinuxlabel = "z" 160 // } 161 // allow_privileged = false 162 // allow_caps = ["CHOWN", "NET_RAW" ... ] 163 // nvidia_runtime = "nvidia" 164 // } 165 // } 166 configSpec = hclspec.NewObject(map[string]*hclspec.Spec{ 167 "endpoint": hclspec.NewAttr("endpoint", "string", false), 168 169 // docker daemon auth option for image registry 170 "auth": hclspec.NewBlock("auth", false, hclspec.NewObject(map[string]*hclspec.Spec{ 171 "config": hclspec.NewAttr("config", "string", false), 172 "helper": hclspec.NewAttr("helper", "string", false), 173 })), 174 175 // client tls options 176 "tls": hclspec.NewBlock("tls", false, hclspec.NewObject(map[string]*hclspec.Spec{ 177 "cert": hclspec.NewAttr("cert", "string", false), 178 "key": hclspec.NewAttr("key", "string", false), 179 "ca": hclspec.NewAttr("ca", "string", false), 180 })), 181 182 // garbage collection options 183 // default needed for both if the gc {...} block is not set and 184 // if the default fields are missing 185 "gc": hclspec.NewDefault(hclspec.NewBlock("gc", false, hclspec.NewObject(map[string]*hclspec.Spec{ 186 "image": hclspec.NewDefault( 187 hclspec.NewAttr("image", "bool", false), 188 hclspec.NewLiteral("true"), 189 ), 190 "image_delay": hclspec.NewAttr("image_delay", "string", false), 191 "container": hclspec.NewDefault( 192 hclspec.NewAttr("container", "bool", false), 193 hclspec.NewLiteral("true"), 194 ), 195 })), hclspec.NewLiteral(`{ 196 image = true 197 container = true 198 }`)), 199 200 // docker volume options 201 // defaulted needed for both if the volumes {...} block is not set and 202 // if the default fields are missing 203 "volumes": hclspec.NewDefault(hclspec.NewBlock("volumes", false, hclspec.NewObject(map[string]*hclspec.Spec{ 204 "enabled": hclspec.NewDefault( 205 hclspec.NewAttr("enabled", "bool", false), 206 hclspec.NewLiteral("true"), 207 ), 208 "selinuxlabel": hclspec.NewAttr("selinuxlabel", "string", false), 209 })), hclspec.NewLiteral("{ enabled = true }")), 210 "allow_privileged": hclspec.NewAttr("allow_privileged", "bool", false), 211 "allow_caps": hclspec.NewDefault( 212 hclspec.NewAttr("allow_caps", "list(string)", false), 213 hclspec.NewLiteral(`["CHOWN","DAC_OVERRIDE","FSETID","FOWNER","MKNOD","NET_RAW","SETGID","SETUID","SETFCAP","SETPCAP","NET_BIND_SERVICE","SYS_CHROOT","KILL","AUDIT_WRITE"]`), 214 ), 215 "nvidia_runtime": hclspec.NewDefault( 216 hclspec.NewAttr("nvidia_runtime", "string", false), 217 hclspec.NewLiteral(`"nvidia"`), 218 ), 219 }) 220 221 // taskConfigSpec is the hcl specification for the driver config section of 222 // a task within a job. It is returned in the TaskConfigSchema RPC 223 taskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{ 224 "image": hclspec.NewAttr("image", "string", true), 225 "advertise_ipv6_address": hclspec.NewAttr("advertise_ipv6_address", "bool", false), 226 "args": hclspec.NewAttr("args", "list(string)", false), 227 "auth": hclspec.NewBlock("auth", false, hclspec.NewObject(map[string]*hclspec.Spec{ 228 "username": hclspec.NewAttr("username", "string", false), 229 "password": hclspec.NewAttr("password", "string", false), 230 "email": hclspec.NewAttr("email", "string", false), 231 "server_address": hclspec.NewAttr("server_address", "string", false), 232 })), 233 "auth_soft_fail": hclspec.NewAttr("auth_soft_fail", "bool", false), 234 "cap_add": hclspec.NewAttr("cap_add", "list(string)", false), 235 "cap_drop": hclspec.NewAttr("cap_drop", "list(string)", false), 236 "command": hclspec.NewAttr("command", "string", false), 237 "cpu_hard_limit": hclspec.NewAttr("cpu_hard_limit", "bool", false), 238 "cpu_cfs_period": hclspec.NewAttr("cpu_cfs_period", "number", false), 239 "devices": hclspec.NewBlockList("devices", hclspec.NewObject(map[string]*hclspec.Spec{ 240 "host_path": hclspec.NewAttr("host_path", "string", false), 241 "container_path": hclspec.NewAttr("container_path", "string", false), 242 "cgroup_permissions": hclspec.NewAttr("cgroup_permissions", "string", false), 243 })), 244 "dns_search_domains": hclspec.NewAttr("dns_search_domains", "list(string)", false), 245 "dns_options": hclspec.NewAttr("dns_options", "list(string)", false), 246 "dns_servers": hclspec.NewAttr("dns_servers", "list(string)", false), 247 "entrypoint": hclspec.NewAttr("entrypoint", "list(string)", false), 248 "extra_hosts": hclspec.NewAttr("extra_hosts", "list(string)", false), 249 "force_pull": hclspec.NewAttr("force_pull", "bool", false), 250 "hostname": hclspec.NewAttr("hostname", "string", false), 251 "interactive": hclspec.NewAttr("interactive", "bool", false), 252 "ipc_mode": hclspec.NewAttr("ipc_mode", "string", false), 253 "ipv4_address": hclspec.NewAttr("ipv4_address", "string", false), 254 "ipv6_address": hclspec.NewAttr("ipv6_address", "string", false), 255 "labels": hclspec.NewAttr("labels", "list(map(string))", false), 256 "load": hclspec.NewAttr("load", "string", false), 257 "logging": hclspec.NewBlock("logging", false, hclspec.NewObject(map[string]*hclspec.Spec{ 258 "type": hclspec.NewAttr("type", "string", false), 259 "driver": hclspec.NewAttr("driver", "string", false), 260 "config": hclspec.NewAttr("config", "list(map(string))", false), 261 })), 262 "mac_address": hclspec.NewAttr("mac_address", "string", false), 263 "mounts": hclspec.NewBlockList("mounts", hclspec.NewObject(map[string]*hclspec.Spec{ 264 "type": hclspec.NewDefault( 265 hclspec.NewAttr("type", "string", false), 266 hclspec.NewLiteral("\"volume\""), 267 ), 268 "target": hclspec.NewAttr("target", "string", false), 269 "source": hclspec.NewAttr("source", "string", false), 270 "readonly": hclspec.NewAttr("readonly", "bool", false), 271 "bind_options": hclspec.NewBlock("bind_options", false, hclspec.NewObject(map[string]*hclspec.Spec{ 272 "propagation": hclspec.NewAttr("propagation", "string", false), 273 })), 274 "tmpfs_options": hclspec.NewBlock("tmpfs_options", false, hclspec.NewObject(map[string]*hclspec.Spec{ 275 "size": hclspec.NewAttr("size", "number", false), 276 "mode": hclspec.NewAttr("mode", "number", false), 277 })), 278 "volume_options": hclspec.NewBlock("volume_options", false, hclspec.NewObject(map[string]*hclspec.Spec{ 279 "no_copy": hclspec.NewAttr("no_copy", "bool", false), 280 "labels": hclspec.NewAttr("labels", "list(map(string))", false), 281 "driver_config": hclspec.NewBlock("driver_config", false, hclspec.NewObject(map[string]*hclspec.Spec{ 282 "name": hclspec.NewAttr("name", "string", false), 283 "options": hclspec.NewAttr("options", "list(map(string))", false), 284 })), 285 })), 286 })), 287 "network_aliases": hclspec.NewAttr("network_aliases", "list(string)", false), 288 "network_mode": hclspec.NewAttr("network_mode", "string", false), 289 "pids_limit": hclspec.NewAttr("pids_limit", "number", false), 290 "pid_mode": hclspec.NewAttr("pid_mode", "string", false), 291 "port_map": hclspec.NewAttr("port_map", "list(map(number))", false), 292 "privileged": hclspec.NewAttr("privileged", "bool", false), 293 "readonly_rootfs": hclspec.NewAttr("readonly_rootfs", "bool", false), 294 "security_opt": hclspec.NewAttr("security_opt", "list(string)", false), 295 "shm_size": hclspec.NewAttr("shm_size", "number", false), 296 "storage_opt": hclspec.NewBlockAttrs("storage_opt", "string", false), 297 "sysctl": hclspec.NewAttr("sysctl", "list(map(string))", false), 298 "tty": hclspec.NewAttr("tty", "bool", false), 299 "ulimit": hclspec.NewAttr("ulimit", "list(map(string))", false), 300 "uts_mode": hclspec.NewAttr("uts_mode", "string", false), 301 "userns_mode": hclspec.NewAttr("userns_mode", "string", false), 302 "volumes": hclspec.NewAttr("volumes", "list(string)", false), 303 "volume_driver": hclspec.NewAttr("volume_driver", "string", false), 304 "work_dir": hclspec.NewAttr("work_dir", "string", false), 305 }) 306 307 // capabilities is returned by the Capabilities RPC and indicates what 308 // optional features this driver supports 309 capabilities = &drivers.Capabilities{ 310 SendSignals: true, 311 Exec: true, 312 FSIsolation: drivers.FSIsolationImage, 313 } 314 ) 315 316 type TaskConfig struct { 317 Image string `codec:"image"` 318 AdvertiseIPv6Addr bool `codec:"advertise_ipv6_address"` 319 Args []string `codec:"args"` 320 Auth DockerAuth `codec:"auth"` 321 AuthSoftFail bool `codec:"auth_soft_fail"` 322 CapAdd []string `codec:"cap_add"` 323 CapDrop []string `codec:"cap_drop"` 324 Command string `codec:"command"` 325 CPUCFSPeriod int64 `codec:"cpu_cfs_period"` 326 CPUHardLimit bool `codec:"cpu_hard_limit"` 327 Devices []DockerDevice `codec:"devices"` 328 DNSSearchDomains []string `codec:"dns_search_domains"` 329 DNSOptions []string `codec:"dns_options"` 330 DNSServers []string `codec:"dns_servers"` 331 Entrypoint []string `codec:"entrypoint"` 332 ExtraHosts []string `codec:"extra_hosts"` 333 ForcePull bool `codec:"force_pull"` 334 Hostname string `codec:"hostname"` 335 Interactive bool `codec:"interactive"` 336 IPCMode string `codec:"ipc_mode"` 337 IPv4Address string `codec:"ipv4_address"` 338 IPv6Address string `codec:"ipv6_address"` 339 Labels hclutils.MapStrStr `codec:"labels"` 340 LoadImage string `codec:"load"` 341 Logging DockerLogging `codec:"logging"` 342 MacAddress string `codec:"mac_address"` 343 Mounts []DockerMount `codec:"mounts"` 344 NetworkAliases []string `codec:"network_aliases"` 345 NetworkMode string `codec:"network_mode"` 346 PidsLimit int64 `codec:"pids_limit"` 347 PidMode string `codec:"pid_mode"` 348 PortMap hclutils.MapStrInt `codec:"port_map"` 349 Privileged bool `codec:"privileged"` 350 ReadonlyRootfs bool `codec:"readonly_rootfs"` 351 SecurityOpt []string `codec:"security_opt"` 352 ShmSize int64 `codec:"shm_size"` 353 StorageOpt map[string]string `codec:"storage_opt"` 354 Sysctl hclutils.MapStrStr `codec:"sysctl"` 355 TTY bool `codec:"tty"` 356 Ulimit hclutils.MapStrStr `codec:"ulimit"` 357 UTSMode string `codec:"uts_mode"` 358 UsernsMode string `codec:"userns_mode"` 359 Volumes []string `codec:"volumes"` 360 VolumeDriver string `codec:"volume_driver"` 361 WorkDir string `codec:"work_dir"` 362 } 363 364 type DockerAuth struct { 365 Username string `codec:"username"` 366 Password string `codec:"password"` 367 Email string `codec:"email"` 368 ServerAddr string `codec:"server_address"` 369 } 370 371 type DockerDevice struct { 372 HostPath string `codec:"host_path"` 373 ContainerPath string `codec:"container_path"` 374 CgroupPermissions string `codec:"cgroup_permissions"` 375 } 376 377 func (d DockerDevice) toDockerDevice() (docker.Device, error) { 378 dd := docker.Device{ 379 PathOnHost: d.HostPath, 380 PathInContainer: d.ContainerPath, 381 CgroupPermissions: d.CgroupPermissions, 382 } 383 384 if d.HostPath == "" { 385 return dd, fmt.Errorf("host path must be set in configuration for devices") 386 } 387 388 if dd.CgroupPermissions == "" { 389 dd.CgroupPermissions = "rwm" 390 } 391 392 if !validateCgroupPermission(dd.CgroupPermissions) { 393 return dd, fmt.Errorf("invalid cgroup permission string: %q", dd.CgroupPermissions) 394 } 395 396 return dd, nil 397 } 398 399 type DockerLogging struct { 400 Type string `codec:"type"` 401 Driver string `codec:"driver"` 402 Config hclutils.MapStrStr `codec:"config"` 403 } 404 405 type DockerMount struct { 406 Type string `codec:"type"` 407 Target string `codec:"target"` 408 Source string `codec:"source"` 409 ReadOnly bool `codec:"readonly"` 410 BindOptions DockerBindOptions `codec:"bind_options"` 411 VolumeOptions DockerVolumeOptions `codec:"volume_options"` 412 TmpfsOptions DockerTmpfsOptions `codec:"tmpfs_options"` 413 } 414 415 func (m DockerMount) toDockerHostMount() (docker.HostMount, error) { 416 if m.Type == "" { 417 // for backward compatbility, as type is optional 418 m.Type = "volume" 419 } 420 421 hm := docker.HostMount{ 422 Target: m.Target, 423 Source: m.Source, 424 Type: m.Type, 425 ReadOnly: m.ReadOnly, 426 } 427 428 switch m.Type { 429 case "volume": 430 vo := m.VolumeOptions 431 hm.VolumeOptions = &docker.VolumeOptions{ 432 NoCopy: vo.NoCopy, 433 Labels: vo.Labels, 434 DriverConfig: docker.VolumeDriverConfig{ 435 Name: vo.DriverConfig.Name, 436 Options: vo.DriverConfig.Options, 437 }, 438 } 439 case "bind": 440 hm.BindOptions = &docker.BindOptions{ 441 Propagation: m.BindOptions.Propagation, 442 } 443 case "tmpfs": 444 if m.Source != "" { 445 return hm, fmt.Errorf(`invalid source, must be "" for tmpfs`) 446 } 447 hm.TempfsOptions = &docker.TempfsOptions{ 448 SizeBytes: m.TmpfsOptions.SizeBytes, 449 Mode: m.TmpfsOptions.Mode, 450 } 451 default: 452 return hm, fmt.Errorf(`invalid mount type, must be "bind", "volume", "tmpfs": %q`, m.Type) 453 } 454 455 return hm, nil 456 } 457 458 type DockerVolumeOptions struct { 459 NoCopy bool `codec:"no_copy"` 460 Labels hclutils.MapStrStr `codec:"labels"` 461 DriverConfig DockerVolumeDriverConfig `codec:"driver_config"` 462 } 463 464 type DockerBindOptions struct { 465 Propagation string `codec:"propagation"` 466 } 467 468 type DockerTmpfsOptions struct { 469 SizeBytes int64 `codec:"size"` 470 Mode int `codec:"mode"` 471 } 472 473 // DockerVolumeDriverConfig holds a map of volume driver specific options 474 type DockerVolumeDriverConfig struct { 475 Name string `codec:"name"` 476 Options hclutils.MapStrStr `codec:"options"` 477 } 478 479 type DriverConfig struct { 480 Endpoint string `codec:"endpoint"` 481 Auth AuthConfig `codec:"auth"` 482 TLS TLSConfig `codec:"tls"` 483 GC GCConfig `codec:"gc"` 484 Volumes VolumeConfig `codec:"volumes"` 485 AllowPrivileged bool `codec:"allow_privileged"` 486 AllowCaps []string `codec:"allow_caps"` 487 GPURuntimeName string `codec:"nvidia_runtime"` 488 } 489 490 type AuthConfig struct { 491 Config string `codec:"config"` 492 Helper string `codec:"helper"` 493 } 494 495 type TLSConfig struct { 496 Cert string `codec:"cert"` 497 Key string `codec:"key"` 498 CA string `codec:"ca"` 499 } 500 501 type GCConfig struct { 502 Image bool `codec:"image"` 503 ImageDelay string `codec:"image_delay"` 504 imageDelayDuration time.Duration `codec:"-"` 505 Container bool `codec:"container"` 506 } 507 508 type VolumeConfig struct { 509 Enabled bool `codec:"enabled"` 510 SelinuxLabel string `codec:"selinuxlabel"` 511 } 512 513 func (d *Driver) PluginInfo() (*base.PluginInfoResponse, error) { 514 return pluginInfo, nil 515 } 516 517 func (d *Driver) ConfigSchema() (*hclspec.Spec, error) { 518 return configSpec, nil 519 } 520 521 func (d *Driver) SetConfig(c *base.Config) error { 522 var config DriverConfig 523 if len(c.PluginConfig) != 0 { 524 if err := base.MsgPackDecode(c.PluginConfig, &config); err != nil { 525 return err 526 } 527 } 528 529 d.config = &config 530 if len(d.config.GC.ImageDelay) > 0 { 531 dur, err := time.ParseDuration(d.config.GC.ImageDelay) 532 if err != nil { 533 return fmt.Errorf("failed to parse 'image_delay' duration: %v", err) 534 } 535 d.config.GC.imageDelayDuration = dur 536 } 537 538 if c.AgentConfig != nil { 539 d.clientConfig = c.AgentConfig.Driver 540 } 541 542 dockerClient, _, err := d.dockerClients() 543 if err != nil { 544 return fmt.Errorf("failed to get docker client: %v", err) 545 } 546 coordinatorConfig := &dockerCoordinatorConfig{ 547 client: dockerClient, 548 cleanup: d.config.GC.Image, 549 logger: d.logger, 550 removeDelay: d.config.GC.imageDelayDuration, 551 } 552 553 d.coordinator = newDockerCoordinator(coordinatorConfig) 554 555 return nil 556 } 557 558 func (d *Driver) TaskConfigSchema() (*hclspec.Spec, error) { 559 return taskConfigSpec, nil 560 } 561 562 func (d *Driver) Capabilities() (*drivers.Capabilities, error) { 563 return capabilities, nil 564 }