github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/client/driver/rkt.go (about) 1 package driver 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "log" 8 "net" 9 "os/exec" 10 "path/filepath" 11 "regexp" 12 "runtime" 13 "strings" 14 "syscall" 15 "time" 16 17 "github.com/hashicorp/go-plugin" 18 "github.com/hashicorp/go-version" 19 "github.com/hashicorp/nomad/client/allocdir" 20 "github.com/hashicorp/nomad/client/config" 21 "github.com/hashicorp/nomad/client/driver/executor" 22 cstructs "github.com/hashicorp/nomad/client/driver/structs" 23 "github.com/hashicorp/nomad/client/fingerprint" 24 "github.com/hashicorp/nomad/helper/discover" 25 "github.com/hashicorp/nomad/helper/fields" 26 "github.com/hashicorp/nomad/nomad/structs" 27 "github.com/mitchellh/mapstructure" 28 ) 29 30 var ( 31 reRktVersion = regexp.MustCompile(`rkt [vV]ersion[:]? (\d[.\d]+)`) 32 reAppcVersion = regexp.MustCompile(`appc [vV]ersion[:]? (\d[.\d]+)`) 33 ) 34 35 const ( 36 // minRktVersion is the earliest supported version of rkt. rkt added support 37 // for CPU and memory isolators in 0.14.0. We cannot support an earlier 38 // version to maintain an uniform interface across all drivers 39 minRktVersion = "0.14.0" 40 41 // bytesToMB is the conversion from bytes to megabytes. 42 bytesToMB = 1024 * 1024 43 44 // The key populated in the Node Attributes to indicate the presence of the 45 // Rkt driver 46 rktDriverAttr = "driver.rkt" 47 ) 48 49 // RktDriver is a driver for running images via Rkt 50 // We attempt to chose sane defaults for now, with more configuration available 51 // planned in the future 52 type RktDriver struct { 53 DriverContext 54 fingerprint.StaticFingerprinter 55 } 56 57 type RktDriverConfig struct { 58 ImageName string `mapstructure:"image"` 59 Args []string `mapstructure:"args"` 60 DNSServers []string `mapstructure:"dns_servers"` // DNS Server for containers 61 DNSSearchDomains []string `mapstructure:"dns_search_domains"` // DNS Search domains for containers 62 } 63 64 // rktHandle is returned from Start/Open as a handle to the PID 65 type rktHandle struct { 66 pluginClient *plugin.Client 67 executorPid int 68 executor executor.Executor 69 allocDir *allocdir.AllocDir 70 logger *log.Logger 71 killTimeout time.Duration 72 maxKillTimeout time.Duration 73 waitCh chan *cstructs.WaitResult 74 doneCh chan struct{} 75 } 76 77 // rktPID is a struct to map the pid running the process to the vm image on 78 // disk 79 type rktPID struct { 80 PluginConfig *PluginReattachConfig 81 AllocDir *allocdir.AllocDir 82 ExecutorPid int 83 KillTimeout time.Duration 84 MaxKillTimeout time.Duration 85 } 86 87 // NewRktDriver is used to create a new exec driver 88 func NewRktDriver(ctx *DriverContext) Driver { 89 return &RktDriver{DriverContext: *ctx} 90 } 91 92 // Validate is used to validate the driver configuration 93 func (d *RktDriver) Validate(config map[string]interface{}) error { 94 fd := &fields.FieldData{ 95 Raw: config, 96 Schema: map[string]*fields.FieldSchema{ 97 "image": &fields.FieldSchema{ 98 Type: fields.TypeString, 99 Required: true, 100 }, 101 "args": &fields.FieldSchema{ 102 Type: fields.TypeArray, 103 }, 104 "dns_servers": &fields.FieldSchema{ 105 Type: fields.TypeArray, 106 }, 107 "dns_search_domains": &fields.FieldSchema{ 108 Type: fields.TypeArray, 109 }, 110 }, 111 } 112 113 if err := fd.Validate(); err != nil { 114 return err 115 } 116 117 return nil 118 } 119 120 func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 121 // Get the current status so that we can log any debug messages only if the 122 // state changes 123 _, currentlyEnabled := node.Attributes[rktDriverAttr] 124 125 // Only enable if we are root when running on non-windows systems. 126 if runtime.GOOS != "windows" && syscall.Geteuid() != 0 { 127 if currentlyEnabled { 128 d.logger.Printf("[DEBUG] driver.rkt: must run as root user, disabling") 129 } 130 delete(node.Attributes, rktDriverAttr) 131 return false, nil 132 } 133 134 outBytes, err := exec.Command("rkt", "version").Output() 135 if err != nil { 136 delete(node.Attributes, rktDriverAttr) 137 return false, nil 138 } 139 out := strings.TrimSpace(string(outBytes)) 140 141 rktMatches := reRktVersion.FindStringSubmatch(out) 142 appcMatches := reAppcVersion.FindStringSubmatch(out) 143 if len(rktMatches) != 2 || len(appcMatches) != 2 { 144 delete(node.Attributes, rktDriverAttr) 145 return false, fmt.Errorf("Unable to parse Rkt version string: %#v", rktMatches) 146 } 147 148 node.Attributes[rktDriverAttr] = "1" 149 node.Attributes["driver.rkt.version"] = rktMatches[1] 150 node.Attributes["driver.rkt.appc.version"] = appcMatches[1] 151 152 minVersion, _ := version.NewVersion(minRktVersion) 153 currentVersion, _ := version.NewVersion(node.Attributes["driver.rkt.version"]) 154 if currentVersion.LessThan(minVersion) { 155 // Do not allow rkt < 0.14.0 156 d.logger.Printf("[WARN] driver.rkt: please upgrade rkt to a version >= %s", minVersion) 157 node.Attributes[rktDriverAttr] = "0" 158 } 159 return true, nil 160 } 161 162 // Run an existing Rkt image. 163 func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 164 var driverConfig RktDriverConfig 165 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 166 return nil, err 167 } 168 // Validate that the config is valid. 169 img := driverConfig.ImageName 170 if img == "" { 171 return nil, fmt.Errorf("Missing ACI image for rkt") 172 } 173 174 // Get the tasks local directory. 175 taskName := d.DriverContext.taskName 176 taskDir, ok := ctx.AllocDir.TaskDirs[taskName] 177 if !ok { 178 return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName) 179 } 180 181 // Build the command. 182 var cmdArgs []string 183 184 // Add the given trust prefix 185 trustPrefix, trustCmd := task.Config["trust_prefix"] 186 insecure := false 187 if trustCmd { 188 var outBuf, errBuf bytes.Buffer 189 cmd := exec.Command("rkt", "trust", "--skip-fingerprint-review=true", fmt.Sprintf("--prefix=%s", trustPrefix)) 190 cmd.Stdout = &outBuf 191 cmd.Stderr = &errBuf 192 if err := cmd.Run(); err != nil { 193 return nil, fmt.Errorf("Error running rkt trust: %s\n\nOutput: %s\n\nError: %s", 194 err, outBuf.String(), errBuf.String()) 195 } 196 d.logger.Printf("[DEBUG] driver.rkt: added trust prefix: %q", trustPrefix) 197 } else { 198 // Disble signature verification if the trust command was not run. 199 insecure = true 200 } 201 202 cmdArgs = append(cmdArgs, "run") 203 cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", task.Name, ctx.AllocDir.SharedDir)) 204 cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", task.Name, ctx.AllocDir.SharedDir)) 205 cmdArgs = append(cmdArgs, img) 206 if insecure == true { 207 cmdArgs = append(cmdArgs, "--insecure-options=all") 208 } 209 210 // Inject environment variables 211 for k, v := range d.taskEnv.EnvMap() { 212 cmdArgs = append(cmdArgs, fmt.Sprintf("--set-env=%v=%v", k, v)) 213 } 214 215 // Check if the user has overridden the exec command. 216 if execCmd, ok := task.Config["command"]; ok { 217 cmdArgs = append(cmdArgs, fmt.Sprintf("--exec=%v", execCmd)) 218 } 219 220 if task.Resources.MemoryMB == 0 { 221 return nil, fmt.Errorf("Memory limit cannot be zero") 222 } 223 if task.Resources.CPU == 0 { 224 return nil, fmt.Errorf("CPU limit cannot be zero") 225 } 226 227 // Add memory isolator 228 cmdArgs = append(cmdArgs, fmt.Sprintf("--memory=%vM", int64(task.Resources.MemoryMB)*bytesToMB)) 229 230 // Add CPU isolator 231 cmdArgs = append(cmdArgs, fmt.Sprintf("--cpu=%vm", int64(task.Resources.CPU))) 232 233 // Add DNS servers 234 for _, ip := range driverConfig.DNSServers { 235 if err := net.ParseIP(ip); err == nil { 236 msg := fmt.Errorf("invalid ip address for container dns server %q", ip) 237 d.logger.Printf("[DEBUG] driver.rkt: %v", msg) 238 return nil, msg 239 } else { 240 cmdArgs = append(cmdArgs, fmt.Sprintf("--dns=%s", ip)) 241 } 242 } 243 244 // set DNS search domains 245 for _, domain := range driverConfig.DNSSearchDomains { 246 cmdArgs = append(cmdArgs, fmt.Sprintf("--dns-search=%s", domain)) 247 } 248 249 // Add user passed arguments. 250 if len(driverConfig.Args) != 0 { 251 parsed := d.taskEnv.ParseAndReplace(driverConfig.Args) 252 253 // Need to start arguments with "--" 254 if len(parsed) > 0 { 255 cmdArgs = append(cmdArgs, "--") 256 } 257 258 for _, arg := range parsed { 259 cmdArgs = append(cmdArgs, fmt.Sprintf("%v", arg)) 260 } 261 } 262 263 bin, err := discover.NomadExecutable() 264 if err != nil { 265 return nil, fmt.Errorf("unable to find the nomad binary: %v", err) 266 } 267 268 pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name)) 269 pluginConfig := &plugin.ClientConfig{ 270 Cmd: exec.Command(bin, "executor", pluginLogFile), 271 } 272 273 execIntf, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 274 if err != nil { 275 return nil, err 276 } 277 executorCtx := &executor.ExecutorContext{ 278 TaskEnv: d.taskEnv, 279 Driver: "rkt", 280 AllocDir: ctx.AllocDir, 281 AllocID: ctx.AllocID, 282 Task: task, 283 } 284 285 absPath, err := GetAbsolutePath("rkt") 286 if err != nil { 287 return nil, err 288 } 289 290 ps, err := execIntf.LaunchCmd(&executor.ExecCommand{ 291 Cmd: absPath, 292 Args: cmdArgs, 293 User: task.User, 294 }, executorCtx) 295 if err != nil { 296 pluginClient.Kill() 297 return nil, err 298 } 299 300 d.logger.Printf("[DEBUG] driver.rkt: started ACI %q with: %v", img, cmdArgs) 301 maxKill := d.DriverContext.config.MaxKillTimeout 302 h := &rktHandle{ 303 pluginClient: pluginClient, 304 executor: execIntf, 305 executorPid: ps.Pid, 306 allocDir: ctx.AllocDir, 307 logger: d.logger, 308 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 309 maxKillTimeout: maxKill, 310 doneCh: make(chan struct{}), 311 waitCh: make(chan *cstructs.WaitResult, 1), 312 } 313 if h.executor.SyncServices(consulContext(d.config, "")); err != nil { 314 h.logger.Printf("[ERR] driver.rkt: error registering services for task: %q: %v", task.Name, err) 315 } 316 go h.run() 317 return h, nil 318 } 319 320 func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 321 // Parse the handle 322 pidBytes := []byte(strings.TrimPrefix(handleID, "Rkt:")) 323 id := &rktPID{} 324 if err := json.Unmarshal(pidBytes, id); err != nil { 325 return nil, fmt.Errorf("failed to parse Rkt handle '%s': %v", handleID, err) 326 } 327 328 pluginConfig := &plugin.ClientConfig{ 329 Reattach: id.PluginConfig.PluginConfig(), 330 } 331 exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 332 if err != nil { 333 d.logger.Println("[ERROR] driver.rkt: error connecting to plugin so destroying plugin pid and user pid") 334 if e := destroyPlugin(id.PluginConfig.Pid, id.ExecutorPid); e != nil { 335 d.logger.Printf("[ERROR] driver.rkt: error destroying plugin and executor pid: %v", e) 336 } 337 return nil, fmt.Errorf("error connecting to plugin: %v", err) 338 } 339 340 ver, _ := exec.Version() 341 d.logger.Printf("[DEBUG] driver.rkt: version of executor: %v", ver.Version) 342 // Return a driver handle 343 h := &rktHandle{ 344 pluginClient: pluginClient, 345 executorPid: id.ExecutorPid, 346 allocDir: id.AllocDir, 347 executor: exec, 348 logger: d.logger, 349 killTimeout: id.KillTimeout, 350 maxKillTimeout: id.MaxKillTimeout, 351 doneCh: make(chan struct{}), 352 waitCh: make(chan *cstructs.WaitResult, 1), 353 } 354 if h.executor.SyncServices(consulContext(d.config, "")); err != nil { 355 h.logger.Printf("[ERR] driver.rkt: error registering services: %v", err) 356 } 357 go h.run() 358 return h, nil 359 } 360 361 func (h *rktHandle) ID() string { 362 // Return a handle to the PID 363 pid := &rktPID{ 364 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 365 KillTimeout: h.killTimeout, 366 MaxKillTimeout: h.maxKillTimeout, 367 ExecutorPid: h.executorPid, 368 AllocDir: h.allocDir, 369 } 370 data, err := json.Marshal(pid) 371 if err != nil { 372 h.logger.Printf("[ERR] driver.rkt: failed to marshal rkt PID to JSON: %s", err) 373 } 374 return fmt.Sprintf("Rkt:%s", string(data)) 375 } 376 377 func (h *rktHandle) WaitCh() chan *cstructs.WaitResult { 378 return h.waitCh 379 } 380 381 func (h *rktHandle) Update(task *structs.Task) error { 382 // Store the updated kill timeout. 383 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 384 h.executor.UpdateTask(task) 385 386 // Update is not possible 387 return nil 388 } 389 390 // Kill is used to terminate the task. We send an Interrupt 391 // and then provide a 5 second grace period before doing a Kill. 392 func (h *rktHandle) Kill() error { 393 h.executor.ShutDown() 394 select { 395 case <-h.doneCh: 396 return nil 397 case <-time.After(h.killTimeout): 398 return h.executor.Exit() 399 } 400 } 401 402 func (h *rktHandle) run() { 403 ps, err := h.executor.Wait() 404 close(h.doneCh) 405 if ps.ExitCode == 0 && err != nil { 406 if e := killProcess(h.executorPid); e != nil { 407 h.logger.Printf("[ERROR] driver.rkt: error killing user process: %v", e) 408 } 409 if e := h.allocDir.UnmountAll(); e != nil { 410 h.logger.Printf("[ERROR] driver.rkt: unmounting dev,proc and alloc dirs failed: %v", e) 411 } 412 } 413 h.waitCh <- cstructs.NewWaitResult(ps.ExitCode, 0, err) 414 close(h.waitCh) 415 // Remove services 416 if err := h.executor.DeregisterServices(); err != nil { 417 h.logger.Printf("[ERR] driver.rkt: failed to deregister services: %v", err) 418 } 419 420 if err := h.executor.Exit(); err != nil { 421 h.logger.Printf("[ERR] driver.rkt: error killing executor: %v", err) 422 } 423 h.pluginClient.Kill() 424 }