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