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