github.com/anuvu/nomad@v0.8.7-atom1/client/driver/rkt.go (about) 1 // +build linux 2 3 package driver 4 5 import ( 6 "bytes" 7 "context" 8 "encoding/json" 9 "fmt" 10 "io/ioutil" 11 "log" 12 "math/rand" 13 "net" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "regexp" 18 "runtime" 19 "strconv" 20 "strings" 21 "syscall" 22 "time" 23 24 appcschema "github.com/appc/spec/schema" 25 rktv1 "github.com/rkt/rkt/api/v1" 26 27 "github.com/hashicorp/go-plugin" 28 "github.com/hashicorp/go-version" 29 "github.com/hashicorp/nomad/client/allocdir" 30 "github.com/hashicorp/nomad/client/config" 31 "github.com/hashicorp/nomad/client/driver/env" 32 "github.com/hashicorp/nomad/client/driver/executor" 33 dstructs "github.com/hashicorp/nomad/client/driver/structs" 34 cstructs "github.com/hashicorp/nomad/client/structs" 35 "github.com/hashicorp/nomad/helper" 36 "github.com/hashicorp/nomad/helper/fields" 37 "github.com/hashicorp/nomad/nomad/structs" 38 "github.com/mitchellh/mapstructure" 39 ) 40 41 var ( 42 reRktVersion = regexp.MustCompile(`rkt [vV]ersion[:]? (\d[.\d]+)`) 43 reAppcVersion = regexp.MustCompile(`appc [vV]ersion[:]? (\d[.\d]+)`) 44 ) 45 46 const ( 47 // minRktVersion is the earliest supported version of rkt. rkt added support 48 // for CPU and memory isolators in 0.14.0. We cannot support an earlier 49 // version to maintain an uniform interface across all drivers 50 minRktVersion = "1.27.0" 51 52 // The key populated in the Node Attributes to indicate the presence of the 53 // Rkt driver 54 rktDriverAttr = "driver.rkt" 55 56 // rktVolumesConfigOption is the key for enabling the use of custom 57 // bind volumes. 58 rktVolumesConfigOption = "rkt.volumes.enabled" 59 rktVolumesConfigDefault = true 60 61 // rktCmd is the command rkt is installed as. 62 rktCmd = "rkt" 63 64 // rktNetworkDeadline is how long to wait for container network to start 65 rktNetworkDeadline = 1 * time.Minute 66 ) 67 68 // RktDriver is a driver for running images via Rkt 69 // We attempt to chose sane defaults for now, with more configuration available 70 // planned in the future 71 type RktDriver struct { 72 DriverContext 73 74 // A tri-state boolean to know if the fingerprinting has happened and 75 // whether it has been successful 76 fingerprintSuccess *bool 77 } 78 79 type RktDriverConfig struct { 80 ImageName string `mapstructure:"image"` 81 Command string `mapstructure:"command"` 82 Args []string `mapstructure:"args"` 83 TrustPrefix string `mapstructure:"trust_prefix"` 84 DNSServers []string `mapstructure:"dns_servers"` // DNS Server for containers 85 DNSSearchDomains []string `mapstructure:"dns_search_domains"` // DNS Search domains for containers 86 Net []string `mapstructure:"net"` // Networks for the containers 87 PortMapRaw []map[string]string `mapstructure:"port_map"` // 88 PortMap map[string]string `mapstructure:"-"` // A map of host port and the port name defined in the image manifest file 89 Volumes []string `mapstructure:"volumes"` // Host-Volumes to mount in, syntax: /path/to/host/directory:/destination/path/in/container[:readOnly] 90 InsecureOptions []string `mapstructure:"insecure_options"` // list of args for --insecure-options 91 92 NoOverlay bool `mapstructure:"no_overlay"` // disable overlayfs for rkt run 93 Debug bool `mapstructure:"debug"` // Enable debug option for rkt command 94 Group string `mapstructure:"group"` // Group override for the container 95 } 96 97 // rktHandle is returned from Start/Open as a handle to the PID 98 type rktHandle struct { 99 uuid string 100 env *env.TaskEnv 101 taskDir *allocdir.TaskDir 102 pluginClient *plugin.Client 103 executorPid int 104 executor executor.Executor 105 logger *log.Logger 106 killTimeout time.Duration 107 maxKillTimeout time.Duration 108 waitCh chan *dstructs.WaitResult 109 doneCh chan struct{} 110 } 111 112 // rktPID is a struct to map the pid running the process to the vm image on 113 // disk 114 type rktPID struct { 115 UUID string 116 PluginConfig *PluginReattachConfig 117 ExecutorPid int 118 KillTimeout time.Duration 119 MaxKillTimeout time.Duration 120 } 121 122 // Retrieve pod status for the pod with the given UUID. 123 func rktGetStatus(uuid string, logger *log.Logger) (*rktv1.Pod, error) { 124 statusArgs := []string{ 125 "status", 126 "--format=json", 127 uuid, 128 } 129 var outBuf, errBuf bytes.Buffer 130 cmd := exec.Command(rktCmd, statusArgs...) 131 cmd.Stdout = &outBuf 132 cmd.Stderr = &errBuf 133 if err := cmd.Run(); err != nil { 134 if outBuf.Len() > 0 { 135 logger.Printf("[DEBUG] driver.rkt: status output for UUID %s: %q", uuid, elide(outBuf)) 136 } 137 if errBuf.Len() == 0 { 138 return nil, err 139 } 140 logger.Printf("[DEBUG] driver.rkt: status error output for UUID %s: %q", uuid, elide(errBuf)) 141 return nil, fmt.Errorf("%s. stderr: %q", err, elide(errBuf)) 142 } 143 var status rktv1.Pod 144 if err := json.Unmarshal(outBuf.Bytes(), &status); err != nil { 145 return nil, err 146 } 147 return &status, nil 148 } 149 150 // Retrieves a pod manifest 151 func rktGetManifest(uuid string) (*appcschema.PodManifest, error) { 152 statusArgs := []string{ 153 "cat-manifest", 154 uuid, 155 } 156 var outBuf bytes.Buffer 157 cmd := exec.Command(rktCmd, statusArgs...) 158 cmd.Stdout = &outBuf 159 cmd.Stderr = ioutil.Discard 160 if err := cmd.Run(); err != nil { 161 return nil, err 162 } 163 var manifest appcschema.PodManifest 164 if err := json.Unmarshal(outBuf.Bytes(), &manifest); err != nil { 165 return nil, err 166 } 167 return &manifest, nil 168 } 169 170 func rktGetDriverNetwork(uuid string, driverConfigPortMap map[string]string, logger *log.Logger) (*cstructs.DriverNetwork, error) { 171 deadline := time.Now().Add(rktNetworkDeadline) 172 var lastErr error 173 try := 0 174 175 for time.Now().Before(deadline) { 176 try++ 177 if status, err := rktGetStatus(uuid, logger); err == nil { 178 for _, net := range status.Networks { 179 if !net.IP.IsGlobalUnicast() { 180 continue 181 } 182 183 // Get the pod manifest so we can figure out which ports are exposed 184 var portmap map[string]int 185 manifest, err := rktGetManifest(uuid) 186 if err == nil { 187 portmap, err = rktManifestMakePortMap(manifest, driverConfigPortMap) 188 if err != nil { 189 lastErr = fmt.Errorf("could not create manifest-based portmap: %v", err) 190 return nil, lastErr 191 } 192 } else { 193 lastErr = fmt.Errorf("could not get pod manifest: %v", err) 194 return nil, lastErr 195 } 196 197 // This is a successful landing; log if its not the first attempt. 198 if try > 1 { 199 logger.Printf("[DEBUG] driver.rkt: retrieved network info for pod UUID %s on attempt %d", uuid, try) 200 } 201 return &cstructs.DriverNetwork{ 202 PortMap: portmap, 203 IP: status.Networks[0].IP.String(), 204 }, nil 205 } 206 207 if len(status.Networks) == 0 { 208 lastErr = fmt.Errorf("no networks found") 209 } else { 210 lastErr = fmt.Errorf("no good driver networks out of %d returned", len(status.Networks)) 211 } 212 } else { 213 lastErr = fmt.Errorf("getting status failed: %v", err) 214 } 215 216 waitTime := getJitteredNetworkRetryTime() 217 logger.Printf("[DEBUG] driver.rkt: failed getting network info for pod UUID %s attempt %d: %v. Sleeping for %v", uuid, try, lastErr, waitTime) 218 time.Sleep(waitTime) 219 } 220 return nil, fmt.Errorf("timed out, last error: %v", lastErr) 221 } 222 223 // Given a rkt/appc pod manifest and driver portmap configuration, create 224 // a driver portmap. 225 func rktManifestMakePortMap(manifest *appcschema.PodManifest, configPortMap map[string]string) (map[string]int, error) { 226 if len(manifest.Apps) == 0 { 227 return nil, fmt.Errorf("manifest has no apps") 228 } 229 if len(manifest.Apps) != 1 { 230 return nil, fmt.Errorf("manifest has multiple apps!") 231 } 232 app := manifest.Apps[0] 233 if app.App == nil { 234 return nil, fmt.Errorf("specified app has no App object") 235 } 236 237 portMap := make(map[string]int) 238 for svc, name := range configPortMap { 239 for _, port := range app.App.Ports { 240 if port.Name.String() == name { 241 portMap[svc] = int(port.Port) 242 } 243 } 244 } 245 return portMap, nil 246 } 247 248 // rktRemove pod after it has exited. 249 func rktRemove(uuid string) error { 250 errBuf := &bytes.Buffer{} 251 cmd := exec.Command(rktCmd, "rm", uuid) 252 cmd.Stdout = ioutil.Discard 253 cmd.Stderr = errBuf 254 if err := cmd.Run(); err != nil { 255 if msg := errBuf.String(); len(msg) > 0 { 256 return fmt.Errorf("error removing pod: %s", msg) 257 } 258 return err 259 } 260 261 return nil 262 } 263 264 // NewRktDriver is used to create a new rkt driver 265 func NewRktDriver(ctx *DriverContext) Driver { 266 return &RktDriver{DriverContext: *ctx} 267 } 268 269 func (d *RktDriver) FSIsolation() cstructs.FSIsolation { 270 return cstructs.FSIsolationImage 271 } 272 273 // Validate is used to validate the driver configuration 274 func (d *RktDriver) Validate(config map[string]interface{}) error { 275 fd := &fields.FieldData{ 276 Raw: config, 277 Schema: map[string]*fields.FieldSchema{ 278 "image": { 279 Type: fields.TypeString, 280 Required: true, 281 }, 282 "command": { 283 Type: fields.TypeString, 284 }, 285 "args": { 286 Type: fields.TypeArray, 287 }, 288 "trust_prefix": { 289 Type: fields.TypeString, 290 }, 291 "dns_servers": { 292 Type: fields.TypeArray, 293 }, 294 "dns_search_domains": { 295 Type: fields.TypeArray, 296 }, 297 "net": { 298 Type: fields.TypeArray, 299 }, 300 "port_map": { 301 Type: fields.TypeArray, 302 }, 303 "debug": { 304 Type: fields.TypeBool, 305 }, 306 "volumes": { 307 Type: fields.TypeArray, 308 }, 309 "no_overlay": { 310 Type: fields.TypeBool, 311 }, 312 "insecure_options": { 313 Type: fields.TypeArray, 314 }, 315 "group": { 316 Type: fields.TypeString, 317 }, 318 }, 319 } 320 321 if err := fd.Validate(); err != nil { 322 return err 323 } 324 325 return nil 326 } 327 328 func (d *RktDriver) Abilities() DriverAbilities { 329 return DriverAbilities{ 330 SendSignals: false, 331 Exec: true, 332 } 333 } 334 335 func (d *RktDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error { 336 // Only enable if we are root when running on non-windows systems. 337 if runtime.GOOS != "windows" && syscall.Geteuid() != 0 { 338 if d.fingerprintSuccess == nil || *d.fingerprintSuccess { 339 d.logger.Printf("[DEBUG] driver.rkt: must run as root user, disabling") 340 } 341 d.fingerprintSuccess = helper.BoolToPtr(false) 342 resp.RemoveAttribute(rktDriverAttr) 343 return nil 344 } 345 346 outBytes, err := exec.Command(rktCmd, "version").Output() 347 if err != nil { 348 d.fingerprintSuccess = helper.BoolToPtr(false) 349 return nil 350 } 351 out := strings.TrimSpace(string(outBytes)) 352 353 rktMatches := reRktVersion.FindStringSubmatch(out) 354 appcMatches := reAppcVersion.FindStringSubmatch(out) 355 if len(rktMatches) != 2 || len(appcMatches) != 2 { 356 d.fingerprintSuccess = helper.BoolToPtr(false) 357 resp.RemoveAttribute(rktDriverAttr) 358 return fmt.Errorf("Unable to parse Rkt version string: %#v", rktMatches) 359 } 360 361 minVersion, _ := version.NewVersion(minRktVersion) 362 currentVersion, _ := version.NewVersion(rktMatches[1]) 363 if currentVersion.LessThan(minVersion) { 364 // Do not allow ancient rkt versions 365 if d.fingerprintSuccess == nil { 366 // Only log on first failure 367 d.logger.Printf("[WARN] driver.rkt: unsupported rkt version %s; please upgrade to >= %s", 368 currentVersion, minVersion) 369 } 370 d.fingerprintSuccess = helper.BoolToPtr(false) 371 resp.RemoveAttribute(rktDriverAttr) 372 return nil 373 } 374 375 // Output version information when the fingerprinter first sees rkt 376 if info, ok := req.Node.Drivers["rkt"]; ok && info != nil && !info.Detected { 377 d.logger.Printf("[DEBUG] driver.rkt: detect version: %s", strings.Replace(out, "\n", " ", -1)) 378 } 379 resp.AddAttribute(rktDriverAttr, "1") 380 resp.AddAttribute("driver.rkt.version", rktMatches[1]) 381 resp.AddAttribute("driver.rkt.appc.version", appcMatches[1]) 382 resp.Detected = true 383 384 // Advertise if this node supports rkt volumes 385 if d.config.ReadBoolDefault(rktVolumesConfigOption, rktVolumesConfigDefault) { 386 resp.AddAttribute("driver."+rktVolumesConfigOption, "1") 387 } 388 d.fingerprintSuccess = helper.BoolToPtr(true) 389 return nil 390 } 391 392 func (d *RktDriver) Periodic() (bool, time.Duration) { 393 return true, 15 * time.Second 394 } 395 396 func (d *RktDriver) Prestart(ctx *ExecContext, task *structs.Task) (*PrestartResponse, error) { 397 return nil, nil 398 } 399 400 // Run an existing Rkt image. 401 func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) { 402 var driverConfig RktDriverConfig 403 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 404 return nil, err 405 } 406 407 driverConfig.PortMap = mapMergeStrStr(driverConfig.PortMapRaw...) 408 409 // ACI image 410 img := driverConfig.ImageName 411 412 // Global arguments given to both prepare and run-prepared 413 globalArgs := make([]string, 0, 50) 414 415 // Add debug option to rkt command. 416 debug := driverConfig.Debug 417 418 // Add the given trust prefix 419 trustPrefix := driverConfig.TrustPrefix 420 insecure := false 421 if trustPrefix != "" { 422 var outBuf, errBuf bytes.Buffer 423 cmd := exec.Command(rktCmd, "trust", "--skip-fingerprint-review=true", fmt.Sprintf("--prefix=%s", trustPrefix), fmt.Sprintf("--debug=%t", debug)) 424 cmd.Stdout = &outBuf 425 cmd.Stderr = &errBuf 426 if err := cmd.Run(); err != nil { 427 return nil, fmt.Errorf("Error running rkt trust: %s\n\nOutput: %s\n\nError: %s", 428 err, outBuf.String(), errBuf.String()) 429 } 430 d.logger.Printf("[DEBUG] driver.rkt: added trust prefix: %q", trustPrefix) 431 } else { 432 // Disable signature verification if the trust command was not run. 433 insecure = true 434 } 435 436 // if we have a selective insecure_options, prefer them 437 // insecure options are rkt's global argument, so we do this before the actual "run" 438 if len(driverConfig.InsecureOptions) > 0 { 439 globalArgs = append(globalArgs, fmt.Sprintf("--insecure-options=%s", strings.Join(driverConfig.InsecureOptions, ","))) 440 } else if insecure { 441 globalArgs = append(globalArgs, "--insecure-options=all") 442 } 443 444 // debug is rkt's global argument, so add it before the actual "run" 445 globalArgs = append(globalArgs, fmt.Sprintf("--debug=%t", debug)) 446 447 prepareArgs := make([]string, 0, 50) 448 runArgs := make([]string, 0, 50) 449 450 prepareArgs = append(prepareArgs, globalArgs...) 451 prepareArgs = append(prepareArgs, "prepare") 452 runArgs = append(runArgs, globalArgs...) 453 runArgs = append(runArgs, "run-prepared") 454 455 // disable overlayfs 456 if driverConfig.NoOverlay { 457 prepareArgs = append(prepareArgs, "--no-overlay=true") 458 } 459 460 // Convert underscores to dashes in task names for use in volume names #2358 461 sanitizedName := strings.Replace(task.Name, "_", "-", -1) 462 463 // Mount /alloc 464 allocVolName := fmt.Sprintf("%s-%s-alloc", d.DriverContext.allocID, sanitizedName) 465 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", allocVolName, ctx.TaskDir.SharedAllocDir)) 466 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", allocVolName, ctx.TaskEnv.EnvMap[env.AllocDir])) 467 468 // Mount /local 469 localVolName := fmt.Sprintf("%s-%s-local", d.DriverContext.allocID, sanitizedName) 470 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", localVolName, ctx.TaskDir.LocalDir)) 471 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", localVolName, ctx.TaskEnv.EnvMap[env.TaskLocalDir])) 472 473 // Mount /secrets 474 secretsVolName := fmt.Sprintf("%s-%s-secrets", d.DriverContext.allocID, sanitizedName) 475 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", secretsVolName, ctx.TaskDir.SecretsDir)) 476 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", secretsVolName, ctx.TaskEnv.EnvMap[env.SecretsDir])) 477 478 // Mount arbitrary volumes if enabled 479 if len(driverConfig.Volumes) > 0 { 480 if enabled := d.config.ReadBoolDefault(rktVolumesConfigOption, rktVolumesConfigDefault); !enabled { 481 return nil, fmt.Errorf("%s is false; cannot use rkt volumes: %+q", rktVolumesConfigOption, driverConfig.Volumes) 482 } 483 for i, rawvol := range driverConfig.Volumes { 484 parts := strings.Split(rawvol, ":") 485 readOnly := "false" 486 // job spec: 487 // volumes = ["/host/path:/container/path[:readOnly]"] 488 // the third parameter is optional, mount is read-write by default 489 if len(parts) == 3 { 490 if parts[2] == "readOnly" { 491 d.logger.Printf("[DEBUG] Mounting %s:%s as readOnly", parts[0], parts[1]) 492 readOnly = "true" 493 } else { 494 d.logger.Printf("[WARN] Unknown volume parameter '%s' ignored for mount %s", parts[2], parts[0]) 495 } 496 } else if len(parts) != 2 { 497 return nil, fmt.Errorf("invalid rkt volume: %q", rawvol) 498 } 499 volName := fmt.Sprintf("%s-%s-%d", d.DriverContext.allocID, sanitizedName, i) 500 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s,readOnly=%s", volName, parts[0], readOnly)) 501 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", volName, parts[1])) 502 } 503 } 504 505 // Inject environment variables 506 for k, v := range ctx.TaskEnv.Map() { 507 prepareArgs = append(prepareArgs, fmt.Sprintf("--set-env=%s=%s", k, v)) 508 } 509 510 // Image is set here, because the commands that follow apply to it 511 prepareArgs = append(prepareArgs, img) 512 513 // Check if the user has overridden the exec command. 514 if driverConfig.Command != "" { 515 prepareArgs = append(prepareArgs, fmt.Sprintf("--exec=%v", driverConfig.Command)) 516 } 517 518 // Add memory isolator 519 prepareArgs = append(prepareArgs, fmt.Sprintf("--memory=%vM", int64(task.Resources.MemoryMB))) 520 521 // Add CPU isolator 522 prepareArgs = append(prepareArgs, fmt.Sprintf("--cpu=%vm", int64(task.Resources.CPU))) 523 524 // Add DNS servers 525 if len(driverConfig.DNSServers) == 1 && (driverConfig.DNSServers[0] == "host" || driverConfig.DNSServers[0] == "none") { 526 // Special case single item lists with the special values "host" or "none" 527 runArgs = append(runArgs, fmt.Sprintf("--dns=%s", driverConfig.DNSServers[0])) 528 } else { 529 for _, ip := range driverConfig.DNSServers { 530 if err := net.ParseIP(ip); err == nil { 531 msg := fmt.Errorf("invalid ip address for container dns server %q", ip) 532 d.logger.Printf("[DEBUG] driver.rkt: %v", msg) 533 return nil, msg 534 } else { 535 runArgs = append(runArgs, fmt.Sprintf("--dns=%s", ip)) 536 } 537 } 538 } 539 540 // set DNS search domains 541 for _, domain := range driverConfig.DNSSearchDomains { 542 runArgs = append(runArgs, fmt.Sprintf("--dns-search=%s", domain)) 543 } 544 545 // set network 546 network := strings.Join(driverConfig.Net, ",") 547 if network != "" { 548 runArgs = append(runArgs, fmt.Sprintf("--net=%s", network)) 549 } 550 551 // Setup port mapping and exposed ports 552 if len(task.Resources.Networks) == 0 { 553 d.logger.Println("[DEBUG] driver.rkt: No network interfaces are available") 554 if len(driverConfig.PortMap) > 0 { 555 return nil, fmt.Errorf("Trying to map ports but no network interface is available") 556 } 557 } else if network == "host" { 558 // Port mapping is skipped when host networking is used. 559 d.logger.Println("[DEBUG] driver.rkt: Ignoring port_map when using --net=host") 560 } else { 561 // TODO add support for more than one network 562 network := task.Resources.Networks[0] 563 for _, port := range network.ReservedPorts { 564 var containerPort string 565 566 mapped, ok := driverConfig.PortMap[port.Label] 567 if !ok { 568 // If the user doesn't have a mapped port using port_map, driver stops running container. 569 return nil, fmt.Errorf("port_map is not set. When you defined port in the resources, you need to configure port_map.") 570 } 571 containerPort = mapped 572 573 hostPortStr := strconv.Itoa(port.Value) 574 575 d.logger.Printf("[DEBUG] driver.rkt: exposed port %s", containerPort) 576 // Add port option to rkt run arguments. rkt allows multiple port args 577 prepareArgs = append(prepareArgs, fmt.Sprintf("--port=%s:%s", containerPort, hostPortStr)) 578 } 579 580 for _, port := range network.DynamicPorts { 581 // By default we will map the allocated port 1:1 to the container 582 var containerPort string 583 584 if mapped, ok := driverConfig.PortMap[port.Label]; ok { 585 containerPort = mapped 586 } else { 587 // If the user doesn't have mapped a port using port_map, driver stops running container. 588 return nil, fmt.Errorf("port_map is not set. When you defined port in the resources, you need to configure port_map.") 589 } 590 591 hostPortStr := strconv.Itoa(port.Value) 592 593 d.logger.Printf("[DEBUG] driver.rkt: exposed port %s", containerPort) 594 // Add port option to rkt run arguments. rkt allows multiple port args 595 prepareArgs = append(prepareArgs, fmt.Sprintf("--port=%s:%s", containerPort, hostPortStr)) 596 } 597 598 } 599 600 // If a user has been specified for the task, pass it through to the user 601 if task.User != "" { 602 prepareArgs = append(prepareArgs, fmt.Sprintf("--user=%s", task.User)) 603 } 604 605 // There's no task-level parameter for groups so check the driver 606 // config for a custom group 607 if driverConfig.Group != "" { 608 prepareArgs = append(prepareArgs, fmt.Sprintf("--group=%s", driverConfig.Group)) 609 } 610 611 // Add user passed arguments. 612 if len(driverConfig.Args) != 0 { 613 parsed := ctx.TaskEnv.ParseAndReplace(driverConfig.Args) 614 615 // Need to start arguments with "--" 616 if len(parsed) > 0 { 617 prepareArgs = append(prepareArgs, "--") 618 } 619 620 for _, arg := range parsed { 621 prepareArgs = append(prepareArgs, fmt.Sprintf("%v", arg)) 622 } 623 } 624 625 pluginLogFile := filepath.Join(ctx.TaskDir.Dir, fmt.Sprintf("%s-executor.out", task.Name)) 626 executorConfig := &dstructs.ExecutorConfig{ 627 LogFile: pluginLogFile, 628 LogLevel: d.config.LogLevel, 629 } 630 631 execIntf, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig) 632 if err != nil { 633 return nil, err 634 } 635 636 absPath, err := GetAbsolutePath(rktCmd) 637 if err != nil { 638 return nil, err 639 } 640 641 var outBuf, errBuf bytes.Buffer 642 cmd := exec.Command(rktCmd, prepareArgs...) 643 cmd.Stdout = &outBuf 644 cmd.Stderr = &errBuf 645 d.logger.Printf("[DEBUG] driver.rkt: preparing pod %q for task %q with: %v", img, d.taskName, prepareArgs) 646 if err := cmd.Run(); err != nil { 647 return nil, fmt.Errorf("Error preparing rkt pod: %s\n\nOutput: %s\n\nError: %s", 648 err, outBuf.String(), errBuf.String()) 649 } 650 uuid := strings.TrimSpace(outBuf.String()) 651 d.logger.Printf("[DEBUG] driver.rkt: pod %q for task %q prepared. (UUID: %s)", img, d.taskName, uuid) 652 runArgs = append(runArgs, uuid) 653 654 // The task's environment is set via --set-env flags above, but the rkt 655 // command itself needs an evironment with PATH set to find iptables. 656 eb := env.NewEmptyBuilder() 657 filter := strings.Split(d.config.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",") 658 rktEnv := eb.SetHostEnvvars(filter).Build() 659 executorCtx := &executor.ExecutorContext{ 660 TaskEnv: rktEnv, 661 Driver: "rkt", 662 Task: task, 663 TaskDir: ctx.TaskDir.Dir, 664 LogDir: ctx.TaskDir.LogDir, 665 } 666 if err := execIntf.SetContext(executorCtx); err != nil { 667 pluginClient.Kill() 668 return nil, fmt.Errorf("failed to set executor context: %v", err) 669 } 670 671 // Enable ResourceLimits to place the executor in a parent cgroup of 672 // the rkt container. This allows stats collection via the executor to 673 // work just like it does for exec. 674 execCmd := &executor.ExecCommand{ 675 Cmd: absPath, 676 Args: runArgs, 677 ResourceLimits: true, 678 } 679 ps, err := execIntf.LaunchCmd(execCmd) 680 if err != nil { 681 pluginClient.Kill() 682 return nil, err 683 } 684 685 d.logger.Printf("[DEBUG] driver.rkt: started ACI %q (UUID: %s) for task %q with: %v", img, uuid, d.taskName, runArgs) 686 maxKill := d.DriverContext.config.MaxKillTimeout 687 h := &rktHandle{ 688 uuid: uuid, 689 env: rktEnv, 690 taskDir: ctx.TaskDir, 691 pluginClient: pluginClient, 692 executor: execIntf, 693 executorPid: ps.Pid, 694 logger: d.logger, 695 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 696 maxKillTimeout: maxKill, 697 doneCh: make(chan struct{}), 698 waitCh: make(chan *dstructs.WaitResult, 1), 699 } 700 go h.run() 701 702 // Do not attempt to retrieve driver network if one won't exist: 703 // - "host" means the container itself has no networking metadata 704 // - "none" means no network is configured 705 // https://coreos.com/rkt/docs/latest/networking/overview.html#no-loopback-only-networking 706 var driverNetwork *cstructs.DriverNetwork 707 if network != "host" && network != "none" { 708 d.logger.Printf("[DEBUG] driver.rkt: retrieving network information for pod %q (UUID: %s) for task %q", img, uuid, d.taskName) 709 driverNetwork, err = rktGetDriverNetwork(uuid, driverConfig.PortMap, d.logger) 710 if err != nil && !pluginClient.Exited() { 711 d.logger.Printf("[WARN] driver.rkt: network status retrieval for pod %q (UUID: %s) for task %q failed. Last error: %v", img, uuid, d.taskName, err) 712 713 // If a portmap was given, this turns into a fatal error 714 if len(driverConfig.PortMap) != 0 { 715 pluginClient.Kill() 716 return nil, fmt.Errorf("Trying to map ports but driver could not determine network information") 717 } 718 } 719 } 720 721 return &StartResponse{Handle: h, Network: driverNetwork}, nil 722 } 723 724 func (d *RktDriver) Cleanup(*ExecContext, *CreatedResources) error { return nil } 725 726 func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 727 // Parse the handle 728 pidBytes := []byte(strings.TrimPrefix(handleID, "Rkt:")) 729 id := &rktPID{} 730 if err := json.Unmarshal(pidBytes, id); err != nil { 731 return nil, fmt.Errorf("failed to parse Rkt handle '%s': %v", handleID, err) 732 } 733 734 pluginConfig := &plugin.ClientConfig{ 735 Reattach: id.PluginConfig.PluginConfig(), 736 } 737 exec, pluginClient, err := createExecutorWithConfig(pluginConfig, d.config.LogOutput) 738 if err != nil { 739 d.logger.Println("[ERR] driver.rkt: error connecting to plugin so destroying plugin pid and user pid") 740 if e := destroyPlugin(id.PluginConfig.Pid, id.ExecutorPid); e != nil { 741 d.logger.Printf("[ERR] driver.rkt: error destroying plugin and executor pid: %v", e) 742 } 743 return nil, fmt.Errorf("error connecting to plugin: %v", err) 744 } 745 746 // The task's environment is set via --set-env flags in Start, but the rkt 747 // command itself needs an evironment with PATH set to find iptables. 748 eb := env.NewEmptyBuilder() 749 filter := strings.Split(d.config.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",") 750 rktEnv := eb.SetHostEnvvars(filter).Build() 751 752 ver, _ := exec.Version() 753 d.logger.Printf("[DEBUG] driver.rkt: version of executor: %v", ver.Version) 754 // Return a driver handle 755 h := &rktHandle{ 756 uuid: id.UUID, 757 env: rktEnv, 758 taskDir: ctx.TaskDir, 759 pluginClient: pluginClient, 760 executorPid: id.ExecutorPid, 761 executor: exec, 762 logger: d.logger, 763 killTimeout: id.KillTimeout, 764 maxKillTimeout: id.MaxKillTimeout, 765 doneCh: make(chan struct{}), 766 waitCh: make(chan *dstructs.WaitResult, 1), 767 } 768 go h.run() 769 return h, nil 770 } 771 772 func (h *rktHandle) ID() string { 773 // Return a handle to the PID 774 pid := &rktPID{ 775 UUID: h.uuid, 776 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 777 KillTimeout: h.killTimeout, 778 MaxKillTimeout: h.maxKillTimeout, 779 ExecutorPid: h.executorPid, 780 } 781 data, err := json.Marshal(pid) 782 if err != nil { 783 h.logger.Printf("[ERR] driver.rkt: failed to marshal rkt PID to JSON: %s", err) 784 } 785 return fmt.Sprintf("Rkt:%s", string(data)) 786 } 787 788 func (h *rktHandle) WaitCh() chan *dstructs.WaitResult { 789 return h.waitCh 790 } 791 792 func (h *rktHandle) Update(task *structs.Task) error { 793 // Store the updated kill timeout. 794 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 795 h.executor.UpdateTask(task) 796 797 // Update is not possible 798 return nil 799 } 800 801 func (h *rktHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 802 if h.uuid == "" { 803 return nil, 0, fmt.Errorf("unable to find rkt pod UUID") 804 } 805 // enter + UUID + cmd + args... 806 enterArgs := make([]string, 3+len(args)) 807 enterArgs[0] = "enter" 808 enterArgs[1] = h.uuid 809 enterArgs[2] = cmd 810 copy(enterArgs[3:], args) 811 return executor.ExecScript(ctx, h.taskDir.Dir, h.env, nil, rktCmd, enterArgs) 812 } 813 814 func (h *rktHandle) Signal(s os.Signal) error { 815 return fmt.Errorf("Rkt does not support signals") 816 } 817 818 // Kill is used to terminate the task. We send an Interrupt 819 // and then provide a 5 second grace period before doing a Kill. 820 func (h *rktHandle) Kill() error { 821 h.executor.ShutDown() 822 select { 823 case <-h.doneCh: 824 return nil 825 case <-time.After(h.killTimeout): 826 return h.executor.Exit() 827 } 828 } 829 830 func (h *rktHandle) Stats() (*cstructs.TaskResourceUsage, error) { 831 return h.executor.Stats() 832 } 833 834 func (h *rktHandle) run() { 835 ps, werr := h.executor.Wait() 836 close(h.doneCh) 837 if ps.ExitCode == 0 && werr != nil { 838 if e := killProcess(h.executorPid); e != nil { 839 h.logger.Printf("[ERR] driver.rkt: error killing user process: %v", e) 840 } 841 } 842 843 // Exit the executor 844 if err := h.executor.Exit(); err != nil { 845 h.logger.Printf("[ERR] driver.rkt: error killing executor: %v", err) 846 } 847 h.pluginClient.Kill() 848 849 // Remove the pod 850 if err := rktRemove(h.uuid); err != nil { 851 h.logger.Printf("[ERR] driver.rkt: error removing pod (UUID: %q) - must gc manually: %v", h.uuid, err) 852 } else { 853 h.logger.Printf("[DEBUG] driver.rkt: removed pod (UUID: %q)", h.uuid) 854 } 855 856 // Send the results 857 h.waitCh <- dstructs.NewWaitResult(ps.ExitCode, 0, werr) 858 close(h.waitCh) 859 } 860 861 // Create a time with a 0 to 100ms jitter for rktGetDriverNetwork retries 862 func getJitteredNetworkRetryTime() time.Duration { 863 return time.Duration(900+rand.Intn(100)) * time.Millisecond 864 } 865 866 // Conditionally elide a buffer to an arbitrary length 867 func elideToLen(inBuf bytes.Buffer, length int) bytes.Buffer { 868 if inBuf.Len() > length { 869 inBuf.Truncate(length) 870 inBuf.WriteString("...") 871 } 872 return inBuf 873 } 874 875 // Conditionally elide a buffer to an 80 character string 876 func elide(inBuf bytes.Buffer) string { 877 tempBuf := elideToLen(inBuf, 80) 878 return tempBuf.String() 879 }