github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/client/driver/rkt.go (about) 1 package driver 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "log" 8 "net" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "regexp" 13 "runtime" 14 "strconv" 15 "strings" 16 "syscall" 17 "time" 18 19 "github.com/hashicorp/go-plugin" 20 "github.com/hashicorp/go-version" 21 "github.com/hashicorp/nomad/client/allocdir" 22 "github.com/hashicorp/nomad/client/config" 23 "github.com/hashicorp/nomad/client/driver/executor" 24 dstructs "github.com/hashicorp/nomad/client/driver/structs" 25 cstructs "github.com/hashicorp/nomad/client/structs" 26 "github.com/hashicorp/nomad/helper" 27 "github.com/hashicorp/nomad/helper/fields" 28 "github.com/hashicorp/nomad/nomad/structs" 29 "github.com/mitchellh/mapstructure" 30 ) 31 32 var ( 33 reRktVersion = regexp.MustCompile(`rkt [vV]ersion[:]? (\d[.\d]+)`) 34 reAppcVersion = regexp.MustCompile(`appc [vV]ersion[:]? (\d[.\d]+)`) 35 ) 36 37 const ( 38 // minRktVersion is the earliest supported version of rkt. rkt added support 39 // for CPU and memory isolators in 0.14.0. We cannot support an earlier 40 // version to maintain an uniform interface across all drivers 41 minRktVersion = "1.0.0" 42 43 // The key populated in the Node Attributes to indicate the presence of the 44 // Rkt driver 45 rktDriverAttr = "driver.rkt" 46 47 // rktVolumesConfigOption is the key for enabling the use of custom 48 // bind volumes. 49 rktVolumesConfigOption = "rkt.volumes.enabled" 50 rktVolumesConfigDefault = true 51 52 // rktCmd is the command rkt is installed as. 53 rktCmd = "rkt" 54 ) 55 56 // RktDriver is a driver for running images via Rkt 57 // We attempt to chose sane defaults for now, with more configuration available 58 // planned in the future 59 type RktDriver struct { 60 DriverContext 61 62 // A tri-state boolean to know if the fingerprinting has happened and 63 // whether it has been successful 64 fingerprintSuccess *bool 65 } 66 67 type RktDriverConfig struct { 68 ImageName string `mapstructure:"image"` 69 Command string `mapstructure:"command"` 70 Args []string `mapstructure:"args"` 71 TrustPrefix string `mapstructure:"trust_prefix"` 72 DNSServers []string `mapstructure:"dns_servers"` // DNS Server for containers 73 DNSSearchDomains []string `mapstructure:"dns_search_domains"` // DNS Search domains for containers 74 Net []string `mapstructure:"net"` // Networks for the containers 75 PortMapRaw []map[string]string `mapstructure:"port_map"` // 76 PortMap map[string]string `mapstructure:"-"` // A map of host port and the port name defined in the image manifest file 77 Volumes []string `mapstructure:"volumes"` // Host-Volumes to mount in, syntax: /path/to/host/directory:/destination/path/in/container 78 79 Debug bool `mapstructure:"debug"` // Enable debug option for rkt command 80 } 81 82 // rktHandle is returned from Start/Open as a handle to the PID 83 type rktHandle struct { 84 pluginClient *plugin.Client 85 executorPid int 86 executor executor.Executor 87 logger *log.Logger 88 killTimeout time.Duration 89 maxKillTimeout time.Duration 90 waitCh chan *dstructs.WaitResult 91 doneCh chan struct{} 92 } 93 94 // rktPID is a struct to map the pid running the process to the vm image on 95 // disk 96 type rktPID struct { 97 PluginConfig *PluginReattachConfig 98 ExecutorPid int 99 KillTimeout time.Duration 100 MaxKillTimeout time.Duration 101 } 102 103 // NewRktDriver is used to create a new exec driver 104 func NewRktDriver(ctx *DriverContext) Driver { 105 return &RktDriver{DriverContext: *ctx} 106 } 107 108 func (d *RktDriver) FSIsolation() cstructs.FSIsolation { 109 return cstructs.FSIsolationImage 110 } 111 112 // Validate is used to validate the driver configuration 113 func (d *RktDriver) Validate(config map[string]interface{}) error { 114 fd := &fields.FieldData{ 115 Raw: config, 116 Schema: map[string]*fields.FieldSchema{ 117 "image": &fields.FieldSchema{ 118 Type: fields.TypeString, 119 Required: true, 120 }, 121 "command": &fields.FieldSchema{ 122 Type: fields.TypeString, 123 }, 124 "args": &fields.FieldSchema{ 125 Type: fields.TypeArray, 126 }, 127 "trust_prefix": &fields.FieldSchema{ 128 Type: fields.TypeString, 129 }, 130 "dns_servers": &fields.FieldSchema{ 131 Type: fields.TypeArray, 132 }, 133 "dns_search_domains": &fields.FieldSchema{ 134 Type: fields.TypeArray, 135 }, 136 "net": &fields.FieldSchema{ 137 Type: fields.TypeArray, 138 }, 139 "port_map": &fields.FieldSchema{ 140 Type: fields.TypeArray, 141 }, 142 "debug": &fields.FieldSchema{ 143 Type: fields.TypeBool, 144 }, 145 "volumes": &fields.FieldSchema{ 146 Type: fields.TypeArray, 147 }, 148 }, 149 } 150 151 if err := fd.Validate(); err != nil { 152 return err 153 } 154 155 return nil 156 } 157 158 func (d *RktDriver) Abilities() DriverAbilities { 159 return DriverAbilities{ 160 SendSignals: false, 161 } 162 } 163 164 func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 165 // Only enable if we are root when running on non-windows systems. 166 if runtime.GOOS != "windows" && syscall.Geteuid() != 0 { 167 if d.fingerprintSuccess == nil || *d.fingerprintSuccess { 168 d.logger.Printf("[DEBUG] driver.rkt: must run as root user, disabling") 169 } 170 delete(node.Attributes, rktDriverAttr) 171 d.fingerprintSuccess = helper.BoolToPtr(false) 172 return false, nil 173 } 174 175 outBytes, err := exec.Command(rktCmd, "version").Output() 176 if err != nil { 177 delete(node.Attributes, rktDriverAttr) 178 d.fingerprintSuccess = helper.BoolToPtr(false) 179 return false, nil 180 } 181 out := strings.TrimSpace(string(outBytes)) 182 183 rktMatches := reRktVersion.FindStringSubmatch(out) 184 appcMatches := reAppcVersion.FindStringSubmatch(out) 185 if len(rktMatches) != 2 || len(appcMatches) != 2 { 186 delete(node.Attributes, rktDriverAttr) 187 d.fingerprintSuccess = helper.BoolToPtr(false) 188 return false, fmt.Errorf("Unable to parse Rkt version string: %#v", rktMatches) 189 } 190 191 node.Attributes[rktDriverAttr] = "1" 192 node.Attributes["driver.rkt.version"] = rktMatches[1] 193 node.Attributes["driver.rkt.appc.version"] = appcMatches[1] 194 195 minVersion, _ := version.NewVersion(minRktVersion) 196 currentVersion, _ := version.NewVersion(node.Attributes["driver.rkt.version"]) 197 if currentVersion.LessThan(minVersion) { 198 // Do not allow ancient rkt versions 199 d.logger.Printf("[WARN] driver.rkt: please upgrade rkt to a version >= %s", minVersion) 200 node.Attributes[rktDriverAttr] = "0" 201 } 202 203 // Advertise if this node supports rkt volumes 204 if d.config.ReadBoolDefault(rktVolumesConfigOption, rktVolumesConfigDefault) { 205 node.Attributes["driver."+rktVolumesConfigOption] = "1" 206 } 207 d.fingerprintSuccess = helper.BoolToPtr(true) 208 return true, nil 209 } 210 211 func (d *RktDriver) Periodic() (bool, time.Duration) { 212 return true, 15 * time.Second 213 } 214 215 func (d *RktDriver) Prestart(ctx *ExecContext, task *structs.Task) (*CreatedResources, error) { 216 return nil, nil 217 } 218 219 // Run an existing Rkt image. 220 func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 221 var driverConfig RktDriverConfig 222 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 223 return nil, err 224 } 225 226 driverConfig.PortMap = mapMergeStrStr(driverConfig.PortMapRaw...) 227 228 // ACI image 229 img := driverConfig.ImageName 230 231 // Build the command. 232 var cmdArgs []string 233 234 // Add debug option to rkt command. 235 debug := driverConfig.Debug 236 237 // Add the given trust prefix 238 trustPrefix := driverConfig.TrustPrefix 239 insecure := false 240 if trustPrefix != "" { 241 var outBuf, errBuf bytes.Buffer 242 cmd := exec.Command(rktCmd, "trust", "--skip-fingerprint-review=true", fmt.Sprintf("--prefix=%s", trustPrefix), fmt.Sprintf("--debug=%t", debug)) 243 cmd.Stdout = &outBuf 244 cmd.Stderr = &errBuf 245 if err := cmd.Run(); err != nil { 246 return nil, fmt.Errorf("Error running rkt trust: %s\n\nOutput: %s\n\nError: %s", 247 err, outBuf.String(), errBuf.String()) 248 } 249 d.logger.Printf("[DEBUG] driver.rkt: added trust prefix: %q", trustPrefix) 250 } else { 251 // Disble signature verification if the trust command was not run. 252 insecure = true 253 } 254 cmdArgs = append(cmdArgs, "run") 255 256 // Convert underscores to dashes in task names for use in volume names #2358 257 sanitizedName := strings.Replace(task.Name, "_", "-", -1) 258 259 // Mount /alloc 260 allocVolName := fmt.Sprintf("%s-%s-alloc", d.DriverContext.allocID, sanitizedName) 261 cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", allocVolName, ctx.TaskDir.SharedAllocDir)) 262 cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", allocVolName, allocdir.SharedAllocContainerPath)) 263 264 // Mount /local 265 localVolName := fmt.Sprintf("%s-%s-local", d.DriverContext.allocID, sanitizedName) 266 cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", localVolName, ctx.TaskDir.LocalDir)) 267 cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", localVolName, allocdir.TaskLocalContainerPath)) 268 269 // Mount /secrets 270 secretsVolName := fmt.Sprintf("%s-%s-secrets", d.DriverContext.allocID, sanitizedName) 271 cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", secretsVolName, ctx.TaskDir.SecretsDir)) 272 cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", secretsVolName, allocdir.TaskSecretsContainerPath)) 273 274 // Mount arbitrary volumes if enabled 275 if len(driverConfig.Volumes) > 0 { 276 if enabled := d.config.ReadBoolDefault(rktVolumesConfigOption, rktVolumesConfigDefault); !enabled { 277 return nil, fmt.Errorf("%s is false; cannot use rkt volumes: %+q", rktVolumesConfigOption, driverConfig.Volumes) 278 } 279 for i, rawvol := range driverConfig.Volumes { 280 parts := strings.Split(rawvol, ":") 281 if len(parts) != 2 { 282 return nil, fmt.Errorf("invalid rkt volume: %q", rawvol) 283 } 284 volName := fmt.Sprintf("%s-%s-%d", d.DriverContext.allocID, sanitizedName, i) 285 cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", volName, parts[0])) 286 cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", volName, parts[1])) 287 } 288 } 289 290 cmdArgs = append(cmdArgs, img) 291 if insecure { 292 cmdArgs = append(cmdArgs, "--insecure-options=all") 293 } 294 cmdArgs = append(cmdArgs, fmt.Sprintf("--debug=%t", debug)) 295 296 // Inject environment variables 297 for k, v := range d.taskEnv.EnvMap() { 298 cmdArgs = append(cmdArgs, fmt.Sprintf("--set-env=%v=%q", k, v)) 299 } 300 301 // Check if the user has overridden the exec command. 302 if driverConfig.Command != "" { 303 cmdArgs = append(cmdArgs, fmt.Sprintf("--exec=%v", driverConfig.Command)) 304 } 305 306 // Add memory isolator 307 cmdArgs = append(cmdArgs, fmt.Sprintf("--memory=%vM", int64(task.Resources.MemoryMB))) 308 309 // Add CPU isolator 310 cmdArgs = append(cmdArgs, fmt.Sprintf("--cpu=%vm", int64(task.Resources.CPU))) 311 312 // Add DNS servers 313 if len(driverConfig.DNSServers) == 1 && (driverConfig.DNSServers[0] == "host" || driverConfig.DNSServers[0] == "none") { 314 // Special case single item lists with the special values "host" or "none" 315 cmdArgs = append(cmdArgs, fmt.Sprintf("--dns=%s", driverConfig.DNSServers[0])) 316 } else { 317 for _, ip := range driverConfig.DNSServers { 318 if err := net.ParseIP(ip); err == nil { 319 msg := fmt.Errorf("invalid ip address for container dns server %q", ip) 320 d.logger.Printf("[DEBUG] driver.rkt: %v", msg) 321 return nil, msg 322 } else { 323 cmdArgs = append(cmdArgs, fmt.Sprintf("--dns=%s", ip)) 324 } 325 } 326 } 327 328 // set DNS search domains 329 for _, domain := range driverConfig.DNSSearchDomains { 330 cmdArgs = append(cmdArgs, fmt.Sprintf("--dns-search=%s", domain)) 331 } 332 333 // set network 334 network := strings.Join(driverConfig.Net, ",") 335 if network != "" { 336 cmdArgs = append(cmdArgs, fmt.Sprintf("--net=%s", network)) 337 } 338 339 // Setup port mapping and exposed ports 340 if len(task.Resources.Networks) == 0 { 341 d.logger.Println("[DEBUG] driver.rkt: No network interfaces are available") 342 if len(driverConfig.PortMap) > 0 { 343 return nil, fmt.Errorf("Trying to map ports but no network interface is available") 344 } 345 } else { 346 // TODO add support for more than one network 347 network := task.Resources.Networks[0] 348 for _, port := range network.ReservedPorts { 349 var containerPort string 350 351 mapped, ok := driverConfig.PortMap[port.Label] 352 if !ok { 353 // If the user doesn't have a mapped port using port_map, driver stops running container. 354 return nil, fmt.Errorf("port_map is not set. When you defined port in the resources, you need to configure port_map.") 355 } 356 containerPort = mapped 357 358 hostPortStr := strconv.Itoa(port.Value) 359 360 d.logger.Printf("[DEBUG] driver.rkt: exposed port %s", containerPort) 361 // Add port option to rkt run arguments. rkt allows multiple port args 362 cmdArgs = append(cmdArgs, fmt.Sprintf("--port=%s:%s", containerPort, hostPortStr)) 363 } 364 365 for _, port := range network.DynamicPorts { 366 // By default we will map the allocated port 1:1 to the container 367 var containerPort string 368 369 if mapped, ok := driverConfig.PortMap[port.Label]; ok { 370 containerPort = mapped 371 } else { 372 // If the user doesn't have mapped a port using port_map, driver stops running container. 373 return nil, fmt.Errorf("port_map is not set. When you defined port in the resources, you need to configure port_map.") 374 } 375 376 hostPortStr := strconv.Itoa(port.Value) 377 378 d.logger.Printf("[DEBUG] driver.rkt: exposed port %s", containerPort) 379 // Add port option to rkt run arguments. rkt allows multiple port args 380 cmdArgs = append(cmdArgs, fmt.Sprintf("--port=%s:%s", containerPort, hostPortStr)) 381 } 382 383 } 384 385 // Add user passed arguments. 386 if len(driverConfig.Args) != 0 { 387 parsed := d.taskEnv.ParseAndReplace(driverConfig.Args) 388 389 // Need to start arguments with "--" 390 if len(parsed) > 0 { 391 cmdArgs = append(cmdArgs, "--") 392 } 393 394 for _, arg := range parsed { 395 cmdArgs = append(cmdArgs, fmt.Sprintf("%v", arg)) 396 } 397 } 398 399 // Set the host environment variables. 400 filter := strings.Split(d.config.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",") 401 d.taskEnv.AppendHostEnvvars(filter) 402 403 pluginLogFile := filepath.Join(ctx.TaskDir.Dir, fmt.Sprintf("%s-executor.out", task.Name)) 404 executorConfig := &dstructs.ExecutorConfig{ 405 LogFile: pluginLogFile, 406 LogLevel: d.config.LogLevel, 407 } 408 409 execIntf, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig) 410 if err != nil { 411 return nil, err 412 } 413 executorCtx := &executor.ExecutorContext{ 414 TaskEnv: d.taskEnv, 415 Driver: "rkt", 416 AllocID: d.DriverContext.allocID, 417 Task: task, 418 TaskDir: ctx.TaskDir.Dir, 419 LogDir: ctx.TaskDir.LogDir, 420 } 421 if err := execIntf.SetContext(executorCtx); err != nil { 422 pluginClient.Kill() 423 return nil, fmt.Errorf("failed to set executor context: %v", err) 424 } 425 426 absPath, err := GetAbsolutePath(rktCmd) 427 if err != nil { 428 return nil, err 429 } 430 431 execCmd := &executor.ExecCommand{ 432 Cmd: absPath, 433 Args: cmdArgs, 434 User: task.User, 435 } 436 ps, err := execIntf.LaunchCmd(execCmd) 437 if err != nil { 438 pluginClient.Kill() 439 return nil, err 440 } 441 442 d.logger.Printf("[DEBUG] driver.rkt: started ACI %q with: %v", img, cmdArgs) 443 maxKill := d.DriverContext.config.MaxKillTimeout 444 h := &rktHandle{ 445 pluginClient: pluginClient, 446 executor: execIntf, 447 executorPid: ps.Pid, 448 logger: d.logger, 449 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 450 maxKillTimeout: maxKill, 451 doneCh: make(chan struct{}), 452 waitCh: make(chan *dstructs.WaitResult, 1), 453 } 454 if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { 455 h.logger.Printf("[ERR] driver.rkt: error registering services for task: %q: %v", task.Name, err) 456 } 457 go h.run() 458 return h, nil 459 } 460 461 func (d *RktDriver) Cleanup(*ExecContext, *CreatedResources) error { return nil } 462 463 func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 464 // Parse the handle 465 pidBytes := []byte(strings.TrimPrefix(handleID, "Rkt:")) 466 id := &rktPID{} 467 if err := json.Unmarshal(pidBytes, id); err != nil { 468 return nil, fmt.Errorf("failed to parse Rkt handle '%s': %v", handleID, err) 469 } 470 471 pluginConfig := &plugin.ClientConfig{ 472 Reattach: id.PluginConfig.PluginConfig(), 473 } 474 exec, pluginClient, err := createExecutorWithConfig(pluginConfig, d.config.LogOutput) 475 if err != nil { 476 d.logger.Println("[ERROR] driver.rkt: error connecting to plugin so destroying plugin pid and user pid") 477 if e := destroyPlugin(id.PluginConfig.Pid, id.ExecutorPid); e != nil { 478 d.logger.Printf("[ERROR] driver.rkt: error destroying plugin and executor pid: %v", e) 479 } 480 return nil, fmt.Errorf("error connecting to plugin: %v", err) 481 } 482 483 ver, _ := exec.Version() 484 d.logger.Printf("[DEBUG] driver.rkt: version of executor: %v", ver.Version) 485 // Return a driver handle 486 h := &rktHandle{ 487 pluginClient: pluginClient, 488 executorPid: id.ExecutorPid, 489 executor: exec, 490 logger: d.logger, 491 killTimeout: id.KillTimeout, 492 maxKillTimeout: id.MaxKillTimeout, 493 doneCh: make(chan struct{}), 494 waitCh: make(chan *dstructs.WaitResult, 1), 495 } 496 if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { 497 h.logger.Printf("[ERR] driver.rkt: error registering services: %v", err) 498 } 499 go h.run() 500 return h, nil 501 } 502 503 func (h *rktHandle) ID() string { 504 // Return a handle to the PID 505 pid := &rktPID{ 506 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 507 KillTimeout: h.killTimeout, 508 MaxKillTimeout: h.maxKillTimeout, 509 ExecutorPid: h.executorPid, 510 } 511 data, err := json.Marshal(pid) 512 if err != nil { 513 h.logger.Printf("[ERR] driver.rkt: failed to marshal rkt PID to JSON: %s", err) 514 } 515 return fmt.Sprintf("Rkt:%s", string(data)) 516 } 517 518 func (h *rktHandle) WaitCh() chan *dstructs.WaitResult { 519 return h.waitCh 520 } 521 522 func (h *rktHandle) Update(task *structs.Task) error { 523 // Store the updated kill timeout. 524 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 525 h.executor.UpdateTask(task) 526 527 // Update is not possible 528 return nil 529 } 530 531 func (h *rktHandle) Signal(s os.Signal) error { 532 return fmt.Errorf("Rkt does not support signals") 533 } 534 535 // Kill is used to terminate the task. We send an Interrupt 536 // and then provide a 5 second grace period before doing a Kill. 537 func (h *rktHandle) Kill() error { 538 h.executor.ShutDown() 539 select { 540 case <-h.doneCh: 541 return nil 542 case <-time.After(h.killTimeout): 543 return h.executor.Exit() 544 } 545 } 546 547 func (h *rktHandle) Stats() (*cstructs.TaskResourceUsage, error) { 548 return nil, DriverStatsNotImplemented 549 } 550 551 func (h *rktHandle) run() { 552 ps, werr := h.executor.Wait() 553 close(h.doneCh) 554 if ps.ExitCode == 0 && werr != nil { 555 if e := killProcess(h.executorPid); e != nil { 556 h.logger.Printf("[ERROR] driver.rkt: error killing user process: %v", e) 557 } 558 } 559 // Remove services 560 if err := h.executor.DeregisterServices(); err != nil { 561 h.logger.Printf("[ERR] driver.rkt: failed to deregister services: %v", err) 562 } 563 564 // Exit the executor 565 if err := h.executor.Exit(); err != nil { 566 h.logger.Printf("[ERR] driver.rkt: error killing executor: %v", err) 567 } 568 h.pluginClient.Kill() 569 570 // Send the results 571 h.waitCh <- dstructs.NewWaitResult(ps.ExitCode, 0, werr) 572 close(h.waitCh) 573 }