github.com/bigcommerce/nomad@v0.9.3-bc/drivers/rkt/driver.go (about) 1 package rkt 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "math/rand" 10 "net" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "regexp" 15 "strconv" 16 "strings" 17 "sync" 18 "syscall" 19 "time" 20 21 appcschema "github.com/appc/spec/schema" 22 "github.com/hashicorp/consul-template/signals" 23 hclog "github.com/hashicorp/go-hclog" 24 version "github.com/hashicorp/go-version" 25 "github.com/hashicorp/nomad/client/config" 26 "github.com/hashicorp/nomad/client/taskenv" 27 "github.com/hashicorp/nomad/drivers/shared/eventer" 28 "github.com/hashicorp/nomad/drivers/shared/executor" 29 "github.com/hashicorp/nomad/helper" 30 "github.com/hashicorp/nomad/helper/pluginutils/hclutils" 31 "github.com/hashicorp/nomad/helper/pluginutils/loader" 32 "github.com/hashicorp/nomad/plugins/base" 33 "github.com/hashicorp/nomad/plugins/drivers" 34 "github.com/hashicorp/nomad/plugins/shared/hclspec" 35 pstructs "github.com/hashicorp/nomad/plugins/shared/structs" 36 ) 37 38 const ( 39 // pluginName is the name of the plugin 40 pluginName = "rkt" 41 42 // fingerprintPeriod is the interval at which the driver will send fingerprint responses 43 fingerprintPeriod = 30 * time.Second 44 45 // minRktVersion is the earliest supported version of rkt. rkt added support 46 // for CPU and memory isolators in 0.14.0. We cannot support an earlier 47 // version to maintain an uniform interface across all drivers 48 minRktVersion = "1.27.0" 49 50 // rktCmd is the command rkt is installed as. 51 rktCmd = "rkt" 52 53 // networkDeadline is how long to wait for container network 54 // information to become available. 55 networkDeadline = 1 * time.Minute 56 57 // taskHandleVersion is the version of task handle which this driver sets 58 // and understands how to decode driver state 59 taskHandleVersion = 1 60 ) 61 62 var ( 63 // PluginID is the rawexec plugin metadata registered in the plugin 64 // catalog. 65 PluginID = loader.PluginID{ 66 Name: pluginName, 67 PluginType: base.PluginTypeDriver, 68 } 69 70 // PluginConfig is the rawexec factory function registered in the 71 // plugin catalog. 72 PluginConfig = &loader.InternalPluginConfig{ 73 Config: map[string]interface{}{}, 74 Factory: func(l hclog.Logger) interface{} { return NewRktDriver(l) }, 75 } 76 ) 77 78 // PluginLoader maps pre-0.9 client driver options to post-0.9 plugin options. 79 func PluginLoader(opts map[string]string) (map[string]interface{}, error) { 80 conf := map[string]interface{}{} 81 if v, err := strconv.ParseBool(opts["driver.rkt.volumes.enabled"]); err == nil { 82 conf["volumes_enabled"] = v 83 } 84 return conf, nil 85 } 86 87 var ( 88 // pluginInfo is the response returned for the PluginInfo RPC 89 pluginInfo = &base.PluginInfoResponse{ 90 Type: base.PluginTypeDriver, 91 PluginApiVersions: []string{drivers.ApiVersion010}, 92 PluginVersion: "0.1.0", 93 Name: pluginName, 94 } 95 96 // configSpec is the hcl specification returned by the ConfigSchema RPC 97 configSpec = hclspec.NewObject(map[string]*hclspec.Spec{ 98 "volumes_enabled": hclspec.NewDefault( 99 hclspec.NewAttr("volumes_enabled", "bool", false), 100 hclspec.NewLiteral("true"), 101 ), 102 }) 103 104 // taskConfigSpec is the hcl specification for the driver config section of 105 // a taskConfig within a job. It is returned in the TaskConfigSchema RPC 106 taskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{ 107 "image": hclspec.NewAttr("image", "string", true), 108 "command": hclspec.NewAttr("command", "string", false), 109 "args": hclspec.NewAttr("args", "list(string)", false), 110 "trust_prefix": hclspec.NewAttr("trust_prefix", "string", false), 111 "dns_servers": hclspec.NewAttr("dns_servers", "list(string)", false), 112 "dns_search_domains": hclspec.NewAttr("dns_search_domains", "list(string)", false), 113 "net": hclspec.NewAttr("net", "list(string)", false), 114 "port_map": hclspec.NewAttr("port_map", "list(map(string))", false), 115 "volumes": hclspec.NewAttr("volumes", "list(string)", false), 116 "insecure_options": hclspec.NewAttr("insecure_options", "list(string)", false), 117 "no_overlay": hclspec.NewAttr("no_overlay", "bool", false), 118 "debug": hclspec.NewAttr("debug", "bool", false), 119 "group": hclspec.NewAttr("group", "string", false), 120 }) 121 122 // capabilities is returned by the Capabilities RPC and indicates what 123 // optional features this driver supports 124 capabilities = &drivers.Capabilities{ 125 SendSignals: true, 126 Exec: true, 127 FSIsolation: drivers.FSIsolationImage, 128 } 129 130 reRktVersion = regexp.MustCompile(`rkt [vV]ersion[:]? (\d[.\d]+)`) 131 reAppcVersion = regexp.MustCompile(`appc [vV]ersion[:]? (\d[.\d]+)`) 132 ) 133 134 // Config is the client configuration for the driver 135 type Config struct { 136 // VolumesEnabled allows tasks to bind host paths (volumes) inside their 137 // container. Binding relative paths is always allowed and will be resolved 138 // relative to the allocation's directory. 139 VolumesEnabled bool `codec:"volumes_enabled"` 140 } 141 142 // TaskConfig is the driver configuration of a taskConfig within a job 143 type TaskConfig struct { 144 ImageName string `codec:"image"` 145 Command string `codec:"command"` 146 Args []string `codec:"args"` 147 TrustPrefix string `codec:"trust_prefix"` 148 DNSServers []string `codec:"dns_servers"` // DNS Server for containers 149 DNSSearchDomains []string `codec:"dns_search_domains"` // DNS Search domains for containers 150 Net []string `codec:"net"` // Networks for the containers 151 PortMap hclutils.MapStrStr `codec:"port_map"` // A map of host port and the port name defined in the image manifest file 152 Volumes []string `codec:"volumes"` // Host-Volumes to mount in, syntax: /path/to/host/directory:/destination/path/in/container[:readOnly] 153 InsecureOptions []string `codec:"insecure_options"` // list of args for --insecure-options 154 155 NoOverlay bool `codec:"no_overlay"` // disable overlayfs for rkt run 156 Debug bool `codec:"debug"` // Enable debug option for rkt command 157 Group string `codec:"group"` // Group override for the container 158 } 159 160 // TaskState is the state which is encoded in the handle returned in 161 // StartTask. This information is needed to rebuild the taskConfig state and handler 162 // during recovery. 163 type TaskState struct { 164 ReattachConfig *pstructs.ReattachConfig 165 TaskConfig *drivers.TaskConfig 166 Pid int 167 StartedAt time.Time 168 UUID string 169 } 170 171 // Driver is a driver for running images via Rkt We attempt to chose sane 172 // defaults for now, with more configuration available planned in the future. 173 type Driver struct { 174 // eventer is used to handle multiplexing of TaskEvents calls such that an 175 // event can be broadcast to all callers 176 eventer *eventer.Eventer 177 178 // config is the driver configuration set by the SetConfig RPC 179 config *Config 180 181 // nomadConfig is the client config from nomad 182 nomadConfig *base.ClientDriverConfig 183 184 // tasks is the in memory datastore mapping taskIDs to rktTaskHandles 185 tasks *taskStore 186 187 // ctx is the context for the driver. It is passed to other subsystems to 188 // coordinate shutdown 189 ctx context.Context 190 191 // signalShutdown is called when the driver is shutting down and cancels the 192 // ctx passed to any subsystems 193 signalShutdown context.CancelFunc 194 195 // logger will log to the Nomad agent 196 logger hclog.Logger 197 198 // A tri-state boolean to know if the fingerprinting has happened and 199 // whether it has been successful 200 fingerprintSuccess *bool 201 fingerprintLock sync.Mutex 202 } 203 204 func NewRktDriver(logger hclog.Logger) drivers.DriverPlugin { 205 ctx, cancel := context.WithCancel(context.Background()) 206 logger = logger.Named(pluginName) 207 return &Driver{ 208 eventer: eventer.NewEventer(ctx, logger), 209 config: &Config{}, 210 tasks: newTaskStore(), 211 ctx: ctx, 212 signalShutdown: cancel, 213 logger: logger, 214 } 215 } 216 217 func (d *Driver) PluginInfo() (*base.PluginInfoResponse, error) { 218 return pluginInfo, nil 219 } 220 221 func (d *Driver) ConfigSchema() (*hclspec.Spec, error) { 222 return configSpec, nil 223 } 224 225 func (d *Driver) SetConfig(cfg *base.Config) error { 226 var config Config 227 if len(cfg.PluginConfig) != 0 { 228 if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil { 229 return err 230 } 231 } 232 233 d.config = &config 234 if cfg.AgentConfig != nil { 235 d.nomadConfig = cfg.AgentConfig.Driver 236 } 237 return nil 238 } 239 240 func (d *Driver) TaskConfigSchema() (*hclspec.Spec, error) { 241 return taskConfigSpec, nil 242 } 243 244 func (d *Driver) Capabilities() (*drivers.Capabilities, error) { 245 return capabilities, nil 246 } 247 248 func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) { 249 ch := make(chan *drivers.Fingerprint) 250 go d.handleFingerprint(ctx, ch) 251 return ch, nil 252 } 253 254 func (d *Driver) handleFingerprint(ctx context.Context, ch chan *drivers.Fingerprint) { 255 defer close(ch) 256 ticker := time.NewTimer(0) 257 for { 258 select { 259 case <-ctx.Done(): 260 return 261 case <-d.ctx.Done(): 262 return 263 case <-ticker.C: 264 ticker.Reset(fingerprintPeriod) 265 ch <- d.buildFingerprint() 266 } 267 } 268 } 269 270 // setFingerprintSuccess marks the driver as having fingerprinted successfully 271 func (d *Driver) setFingerprintSuccess() { 272 d.fingerprintLock.Lock() 273 d.fingerprintSuccess = helper.BoolToPtr(true) 274 d.fingerprintLock.Unlock() 275 } 276 277 // setFingerprintFailure marks the driver as having failed fingerprinting 278 func (d *Driver) setFingerprintFailure() { 279 d.fingerprintLock.Lock() 280 d.fingerprintSuccess = helper.BoolToPtr(false) 281 d.fingerprintLock.Unlock() 282 } 283 284 // fingerprintSuccessful returns true if the driver has 285 // never fingerprinted or has successfully fingerprinted 286 func (d *Driver) fingerprintSuccessful() bool { 287 d.fingerprintLock.Lock() 288 defer d.fingerprintLock.Unlock() 289 return d.fingerprintSuccess == nil || *d.fingerprintSuccess 290 } 291 292 func (d *Driver) buildFingerprint() *drivers.Fingerprint { 293 fingerprint := &drivers.Fingerprint{ 294 Attributes: map[string]*pstructs.Attribute{}, 295 Health: drivers.HealthStateHealthy, 296 HealthDescription: drivers.DriverHealthy, 297 } 298 299 // Only enable if we are root 300 if syscall.Geteuid() != 0 { 301 if d.fingerprintSuccessful() { 302 d.logger.Debug("must run as root user, disabling") 303 } 304 d.setFingerprintFailure() 305 fingerprint.Health = drivers.HealthStateUndetected 306 fingerprint.HealthDescription = drivers.DriverRequiresRootMessage 307 return fingerprint 308 } 309 310 outBytes, err := exec.Command(rktCmd, "version").Output() 311 if err != nil { 312 fingerprint.Health = drivers.HealthStateUndetected 313 fingerprint.HealthDescription = fmt.Sprintf("Failed to execute %s version: %v", rktCmd, err) 314 d.setFingerprintFailure() 315 return fingerprint 316 } 317 out := strings.TrimSpace(string(outBytes)) 318 319 rktMatches := reRktVersion.FindStringSubmatch(out) 320 appcMatches := reAppcVersion.FindStringSubmatch(out) 321 if len(rktMatches) != 2 || len(appcMatches) != 2 { 322 fingerprint.Health = drivers.HealthStateUndetected 323 fingerprint.HealthDescription = "Unable to parse rkt version string" 324 d.setFingerprintFailure() 325 return fingerprint 326 } 327 328 minVersion, _ := version.NewVersion(minRktVersion) 329 currentVersion, _ := version.NewVersion(rktMatches[1]) 330 if currentVersion.LessThan(minVersion) { 331 // Do not allow ancient rkt versions 332 fingerprint.Health = drivers.HealthStateUndetected 333 fingerprint.HealthDescription = fmt.Sprintf("Unsuported rkt version %s", currentVersion) 334 if d.fingerprintSuccessful() { 335 d.logger.Warn("unsupported rkt version please upgrade to >= "+minVersion.String(), 336 "rkt_version", currentVersion) 337 } 338 d.setFingerprintFailure() 339 return fingerprint 340 } 341 342 fingerprint.Attributes["driver.rkt"] = pstructs.NewBoolAttribute(true) 343 fingerprint.Attributes["driver.rkt.version"] = pstructs.NewStringAttribute(rktMatches[1]) 344 fingerprint.Attributes["driver.rkt.appc.version"] = pstructs.NewStringAttribute(appcMatches[1]) 345 if d.config.VolumesEnabled { 346 fingerprint.Attributes["driver.rkt.volumes.enabled"] = pstructs.NewBoolAttribute(true) 347 } 348 d.setFingerprintSuccess() 349 return fingerprint 350 351 } 352 353 func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error { 354 if handle == nil { 355 return fmt.Errorf("error: handle cannot be nil") 356 } 357 358 // COMPAT(0.10): pre 0.9 upgrade path check 359 if handle.Version == 0 { 360 return d.recoverPre09Task(handle) 361 } 362 363 // If already attached to handle there's nothing to recover. 364 if _, ok := d.tasks.Get(handle.Config.ID); ok { 365 d.logger.Trace("nothing to recover; task already exists", 366 "task_id", handle.Config.ID, 367 "task_name", handle.Config.Name, 368 ) 369 return nil 370 } 371 372 var taskState TaskState 373 if err := handle.GetDriverState(&taskState); err != nil { 374 d.logger.Error("failed to decode taskConfig state from handle", "error", err, "task_id", handle.Config.ID) 375 return fmt.Errorf("failed to decode taskConfig state from handle: %v", err) 376 } 377 378 plugRC, err := pstructs.ReattachConfigToGoPlugin(taskState.ReattachConfig) 379 if err != nil { 380 d.logger.Error("failed to build ReattachConfig from taskConfig state", "error", err, "task_id", handle.Config.ID) 381 return fmt.Errorf("failed to build ReattachConfig from taskConfig state: %v", err) 382 } 383 384 execImpl, pluginClient, err := executor.ReattachToExecutor(plugRC, 385 d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID)) 386 if err != nil { 387 d.logger.Error("failed to reattach to executor", "error", err, "task_id", handle.Config.ID) 388 return fmt.Errorf("failed to reattach to executor: %v", err) 389 } 390 391 // The taskConfig's environment is set via --set-env flags in Start, but the rkt 392 // command itself needs an environment with PATH set to find iptables. 393 // TODO (preetha) need to figure out how to read env.blacklist 394 eb := taskenv.NewEmptyBuilder() 395 filter := strings.Split(config.DefaultEnvBlacklist, ",") 396 rktEnv := eb.SetHostEnvvars(filter).Build() 397 398 h := &taskHandle{ 399 exec: execImpl, 400 env: rktEnv, 401 pid: taskState.Pid, 402 uuid: taskState.UUID, 403 pluginClient: pluginClient, 404 taskConfig: taskState.TaskConfig, 405 procState: drivers.TaskStateRunning, 406 startedAt: taskState.StartedAt, 407 exitResult: &drivers.ExitResult{}, 408 } 409 410 d.tasks.Set(taskState.TaskConfig.ID, h) 411 412 go h.run() 413 return nil 414 } 415 416 func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) { 417 if _, ok := d.tasks.Get(cfg.ID); ok { 418 return nil, nil, fmt.Errorf("taskConfig with ID '%s' already started", cfg.ID) 419 } 420 421 var driverConfig TaskConfig 422 423 if err := cfg.DecodeDriverConfig(&driverConfig); err != nil { 424 return nil, nil, fmt.Errorf("failed to decode driver config: %v", err) 425 } 426 427 handle := drivers.NewTaskHandle(taskHandleVersion) 428 handle.Config = cfg 429 430 // todo(preetha) - port map in client v1 is a slice of maps that get merged. figure out if the caller will do this 431 //driverConfig.PortMap 432 433 // ACI image 434 img := driverConfig.ImageName 435 436 // Global arguments given to both prepare and run-prepared 437 globalArgs := make([]string, 0, 50) 438 439 // Add debug option to rkt command. 440 debug := driverConfig.Debug 441 442 // Add the given trust prefix 443 trustPrefix := driverConfig.TrustPrefix 444 insecure := false 445 if trustPrefix != "" { 446 var outBuf, errBuf bytes.Buffer 447 cmd := exec.Command(rktCmd, "trust", "--skip-fingerprint-review=true", fmt.Sprintf("--prefix=%s", trustPrefix), fmt.Sprintf("--debug=%t", debug)) 448 cmd.Stdout = &outBuf 449 cmd.Stderr = &errBuf 450 if err := cmd.Run(); err != nil { 451 return nil, nil, fmt.Errorf("Error running rkt trust: %s\n\nOutput: %s\n\nError: %s", 452 err, outBuf.String(), errBuf.String()) 453 } 454 d.logger.Debug("added trust prefix", "trust_prefix", trustPrefix, "task_name", cfg.Name) 455 } else { 456 // Disable signature verification if the trust command was not run. 457 insecure = true 458 } 459 460 // if we have a selective insecure_options, prefer them 461 // insecure options are rkt's global argument, so we do this before the actual "run" 462 if len(driverConfig.InsecureOptions) > 0 { 463 globalArgs = append(globalArgs, fmt.Sprintf("--insecure-options=%s", strings.Join(driverConfig.InsecureOptions, ","))) 464 } else if insecure { 465 globalArgs = append(globalArgs, "--insecure-options=all") 466 } 467 468 // debug is rkt's global argument, so add it before the actual "run" 469 globalArgs = append(globalArgs, fmt.Sprintf("--debug=%t", debug)) 470 471 prepareArgs := make([]string, 0, 50) 472 runArgs := make([]string, 0, 50) 473 474 prepareArgs = append(prepareArgs, globalArgs...) 475 prepareArgs = append(prepareArgs, "prepare") 476 runArgs = append(runArgs, globalArgs...) 477 runArgs = append(runArgs, "run-prepared") 478 479 // disable overlayfs 480 if driverConfig.NoOverlay { 481 prepareArgs = append(prepareArgs, "--no-overlay=true") 482 } 483 484 // Convert underscores to dashes in taskConfig names for use in volume names #2358 485 sanitizedName := strings.Replace(cfg.Name, "_", "-", -1) 486 487 // Mount /alloc 488 allocVolName := fmt.Sprintf("%s-%s-alloc", cfg.AllocID, sanitizedName) 489 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", allocVolName, cfg.TaskDir().SharedAllocDir)) 490 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", allocVolName, "/alloc")) 491 492 // Mount /local 493 localVolName := fmt.Sprintf("%s-%s-local", cfg.AllocID, sanitizedName) 494 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", localVolName, cfg.TaskDir().LocalDir)) 495 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", localVolName, "/local")) 496 497 // Mount /secrets 498 secretsVolName := fmt.Sprintf("%s-%s-secrets", cfg.AllocID, sanitizedName) 499 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", secretsVolName, cfg.TaskDir().SecretsDir)) 500 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", secretsVolName, "/secrets")) 501 502 // Mount arbitrary volumes if enabled 503 if len(driverConfig.Volumes) > 0 { 504 if !d.config.VolumesEnabled { 505 return nil, nil, fmt.Errorf("volumes_enabled is false; cannot use rkt volumes: %+q", driverConfig.Volumes) 506 } 507 for i, rawvol := range driverConfig.Volumes { 508 parts := strings.Split(rawvol, ":") 509 readOnly := "false" 510 // job spec: 511 // volumes = ["/host/path:/container/path[:readOnly]"] 512 // the third parameter is optional, mount is read-write by default 513 if len(parts) == 3 { 514 if parts[2] == "readOnly" { 515 d.logger.Debug("mounting volume as readOnly", "volume", strings.Join(parts[:2], parts[1])) 516 readOnly = "true" 517 } else { 518 d.logger.Warn("unknown volume parameter ignored for mount", "parameter", parts[2], "mount", parts[0]) 519 } 520 } else if len(parts) != 2 { 521 return nil, nil, fmt.Errorf("invalid rkt volume: %q", rawvol) 522 } 523 volName := fmt.Sprintf("%s-%s-%d", cfg.AllocID, sanitizedName, i) 524 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s,readOnly=%s", volName, parts[0], readOnly)) 525 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", volName, parts[1])) 526 } 527 } 528 529 // Mount task volumes, always do 530 for i, vol := range cfg.Mounts { 531 volName := fmt.Sprintf("%s-%s-taskmounts-%d", cfg.AllocID, sanitizedName, i) 532 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s,readOnly=%v", volName, vol.HostPath, vol.Readonly)) 533 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", volName, vol.TaskPath)) 534 } 535 536 // Mount task devices, always do 537 for i, vol := range cfg.Devices { 538 volName := fmt.Sprintf("%s-%s-taskdevices-%d", cfg.AllocID, sanitizedName, i) 539 readOnly := !strings.Contains(vol.Permissions, "w") 540 prepareArgs = append(prepareArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s,readOnly=%v", volName, vol.HostPath, readOnly)) 541 prepareArgs = append(prepareArgs, fmt.Sprintf("--mount=volume=%s,target=%s", volName, vol.TaskPath)) 542 } 543 544 // Inject environment variables 545 for k, v := range cfg.Env { 546 prepareArgs = append(prepareArgs, fmt.Sprintf("--set-env=%s=%s", k, v)) 547 } 548 549 // Image is set here, because the commands that follow apply to it 550 prepareArgs = append(prepareArgs, img) 551 552 // Check if the user has overridden the exec command. 553 if driverConfig.Command != "" { 554 prepareArgs = append(prepareArgs, fmt.Sprintf("--exec=%v", driverConfig.Command)) 555 } 556 557 // Add memory isolator 558 prepareArgs = append(prepareArgs, fmt.Sprintf("--memory=%v", cfg.Resources.LinuxResources.MemoryLimitBytes)) 559 560 // Add CPU isolator 561 prepareArgs = append(prepareArgs, fmt.Sprintf("--cpu=%v", cfg.Resources.LinuxResources.CPUShares)) 562 563 // Add DNS servers 564 if len(driverConfig.DNSServers) == 1 && (driverConfig.DNSServers[0] == "host" || driverConfig.DNSServers[0] == "none") { 565 // Special case single item lists with the special values "host" or "none" 566 runArgs = append(runArgs, fmt.Sprintf("--dns=%s", driverConfig.DNSServers[0])) 567 } else { 568 for _, ip := range driverConfig.DNSServers { 569 if err := net.ParseIP(ip); err == nil { 570 wrappedErr := fmt.Errorf("invalid ip address for container dns server %q", ip) 571 d.logger.Debug("error parsing DNS server", "error", wrappedErr) 572 return nil, nil, wrappedErr 573 } 574 runArgs = append(runArgs, fmt.Sprintf("--dns=%s", ip)) 575 } 576 } 577 578 // set DNS search domains 579 for _, domain := range driverConfig.DNSSearchDomains { 580 runArgs = append(runArgs, fmt.Sprintf("--dns-search=%s", domain)) 581 } 582 583 // set network 584 network := strings.Join(driverConfig.Net, ",") 585 if network != "" { 586 runArgs = append(runArgs, fmt.Sprintf("--net=%s", network)) 587 } 588 589 // Setup port mapping and exposed ports 590 if len(cfg.Resources.NomadResources.Networks) == 0 { 591 d.logger.Debug("no network interfaces are available") 592 if len(driverConfig.PortMap) > 0 { 593 return nil, nil, fmt.Errorf("Trying to map ports but no network interface is available") 594 } 595 } else if network == "host" { 596 // Port mapping is skipped when host networking is used. 597 d.logger.Debug("Ignoring port_map when using --net=host", "task_name", cfg.Name) 598 } else { 599 network := cfg.Resources.NomadResources.Networks[0] 600 for _, port := range network.ReservedPorts { 601 var containerPort string 602 603 mapped, ok := driverConfig.PortMap[port.Label] 604 if !ok { 605 // If the user doesn't have a mapped port using port_map, driver stops running container. 606 return nil, nil, fmt.Errorf("port_map is not set. When you defined port in the resources, you need to configure port_map.") 607 } 608 containerPort = mapped 609 610 hostPortStr := strconv.Itoa(port.Value) 611 612 d.logger.Debug("driver.rkt: exposed port", "containerPort", containerPort) 613 // Add port option to rkt run arguments. rkt allows multiple port args 614 prepareArgs = append(prepareArgs, fmt.Sprintf("--port=%s:%s", containerPort, hostPortStr)) 615 } 616 617 for _, port := range network.DynamicPorts { 618 // By default we will map the allocated port 1:1 to the container 619 var containerPort string 620 621 if mapped, ok := driverConfig.PortMap[port.Label]; ok { 622 containerPort = mapped 623 } else { 624 // If the user doesn't have mapped a port using port_map, driver stops running container. 625 return nil, nil, fmt.Errorf("port_map is not set. When you defined port in the resources, you need to configure port_map.") 626 } 627 628 hostPortStr := strconv.Itoa(port.Value) 629 630 d.logger.Debug("exposed port", "containerPort", containerPort, "task_name", cfg.Name) 631 // Add port option to rkt run arguments. rkt allows multiple port args 632 prepareArgs = append(prepareArgs, fmt.Sprintf("--port=%s:%s", containerPort, hostPortStr)) 633 } 634 635 } 636 637 // If a user has been specified for the taskConfig, pass it through to the user 638 if cfg.User != "" { 639 prepareArgs = append(prepareArgs, fmt.Sprintf("--user=%s", cfg.User)) 640 } 641 642 // There's no taskConfig-level parameter for groups so check the driver 643 // config for a custom group 644 if driverConfig.Group != "" { 645 prepareArgs = append(prepareArgs, fmt.Sprintf("--group=%s", driverConfig.Group)) 646 } 647 648 // Add user passed arguments. 649 if len(driverConfig.Args) != 0 { 650 651 // Need to start arguments with "--" 652 prepareArgs = append(prepareArgs, "--") 653 654 for _, arg := range driverConfig.Args { 655 prepareArgs = append(prepareArgs, fmt.Sprintf("%v", arg)) 656 } 657 } 658 659 pluginLogFile := filepath.Join(cfg.TaskDir().Dir, fmt.Sprintf("%s-executor.out", cfg.Name)) 660 executorConfig := &executor.ExecutorConfig{ 661 LogFile: pluginLogFile, 662 LogLevel: "debug", 663 } 664 665 execImpl, pluginClient, err := executor.CreateExecutor( 666 d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID), 667 d.nomadConfig, executorConfig) 668 if err != nil { 669 return nil, nil, err 670 } 671 672 absPath, err := GetAbsolutePath(rktCmd) 673 if err != nil { 674 return nil, nil, err 675 } 676 677 var outBuf, errBuf bytes.Buffer 678 cmd := exec.Command(rktCmd, prepareArgs...) 679 cmd.Stdout = &outBuf 680 cmd.Stderr = &errBuf 681 d.logger.Debug("preparing taskConfig", "pod", img, "task_name", cfg.Name, "args", prepareArgs) 682 if err := cmd.Run(); err != nil { 683 return nil, nil, fmt.Errorf("Error preparing rkt pod: %s\n\nOutput: %s\n\nError: %s", 684 err, outBuf.String(), errBuf.String()) 685 } 686 uuid := strings.TrimSpace(outBuf.String()) 687 d.logger.Debug("taskConfig prepared", "pod", img, "task_name", cfg.Name, "uuid", uuid) 688 runArgs = append(runArgs, uuid) 689 690 // The taskConfig's environment is set via --set-env flags above, but the rkt 691 // command itself needs an environment with PATH set to find iptables. 692 693 // TODO (preetha) need to figure out how to pass env.blacklist from client config 694 eb := taskenv.NewEmptyBuilder() 695 filter := strings.Split(config.DefaultEnvBlacklist, ",") 696 rktEnv := eb.SetHostEnvvars(filter).Build() 697 698 // Enable ResourceLimits to place the executor in a parent cgroup of 699 // the rkt container. This allows stats collection via the executor to 700 // work just like it does for exec. 701 execCmd := &executor.ExecCommand{ 702 Cmd: absPath, 703 Args: runArgs, 704 ResourceLimits: true, 705 Resources: cfg.Resources, 706 707 // Use rktEnv, the environment needed for running rkt, not the task env 708 Env: rktEnv.List(), 709 710 TaskDir: cfg.TaskDir().Dir, 711 StdoutPath: cfg.StdoutPath, 712 StderrPath: cfg.StderrPath, 713 } 714 ps, err := execImpl.Launch(execCmd) 715 if err != nil { 716 pluginClient.Kill() 717 return nil, nil, err 718 } 719 720 d.logger.Debug("started taskConfig", "aci", img, "uuid", uuid, "task_name", cfg.Name, "args", runArgs) 721 h := &taskHandle{ 722 exec: execImpl, 723 env: rktEnv, 724 pid: ps.Pid, 725 uuid: uuid, 726 pluginClient: pluginClient, 727 taskConfig: cfg, 728 procState: drivers.TaskStateRunning, 729 startedAt: time.Now().Round(time.Millisecond), 730 logger: d.logger, 731 } 732 733 rktDriverState := TaskState{ 734 ReattachConfig: pstructs.ReattachConfigFromGoPlugin(pluginClient.ReattachConfig()), 735 Pid: ps.Pid, 736 TaskConfig: cfg, 737 StartedAt: h.startedAt, 738 UUID: uuid, 739 } 740 741 if err := handle.SetDriverState(&rktDriverState); err != nil { 742 d.logger.Error("failed to start task, error setting driver state", "error", err, "task_name", cfg.Name) 743 execImpl.Shutdown("", 0) 744 pluginClient.Kill() 745 return nil, nil, fmt.Errorf("failed to set driver state: %v", err) 746 } 747 748 d.tasks.Set(cfg.ID, h) 749 go h.run() 750 751 // Do not attempt to retrieve driver network if one won't exist: 752 // - "host" means the container itself has no networking metadata 753 // - "none" means no network is configured 754 // https://coreos.com/rkt/docs/latest/networking/overview.html#no-loopback-only-networking 755 var driverNetwork *drivers.DriverNetwork 756 if network != "host" && network != "none" { 757 d.logger.Debug("retrieving network information for pod", "pod", img, "UUID", uuid, "task_name", cfg.Name) 758 driverNetwork, err = rktGetDriverNetwork(uuid, driverConfig.PortMap, d.logger) 759 if err != nil && !pluginClient.Exited() { 760 d.logger.Warn("network status retrieval for pod failed", "pod", img, "UUID", uuid, "task_name", cfg.Name, "error", err) 761 762 // If a portmap was given, this turns into a fatal error 763 if len(driverConfig.PortMap) != 0 { 764 pluginClient.Kill() 765 return nil, nil, fmt.Errorf("Trying to map ports but driver could not determine network information") 766 } 767 } 768 } 769 770 return handle, driverNetwork, nil 771 772 } 773 774 func (d *Driver) WaitTask(ctx context.Context, taskID string) (<-chan *drivers.ExitResult, error) { 775 handle, ok := d.tasks.Get(taskID) 776 if !ok { 777 return nil, drivers.ErrTaskNotFound 778 } 779 780 ch := make(chan *drivers.ExitResult) 781 go d.handleWait(ctx, handle, ch) 782 783 return ch, nil 784 } 785 786 func (d *Driver) StopTask(taskID string, timeout time.Duration, signal string) error { 787 handle, ok := d.tasks.Get(taskID) 788 if !ok { 789 return drivers.ErrTaskNotFound 790 } 791 792 if err := handle.exec.Shutdown(signal, timeout); err != nil { 793 if handle.pluginClient.Exited() { 794 return nil 795 } 796 return fmt.Errorf("executor Shutdown failed: %v", err) 797 } 798 799 return nil 800 } 801 802 func (d *Driver) DestroyTask(taskID string, force bool) error { 803 handle, ok := d.tasks.Get(taskID) 804 if !ok { 805 return drivers.ErrTaskNotFound 806 } 807 808 if handle.IsRunning() && !force { 809 return fmt.Errorf("cannot destroy running task") 810 } 811 812 if !handle.pluginClient.Exited() { 813 if handle.IsRunning() { 814 if err := handle.exec.Shutdown("", 0); err != nil { 815 handle.logger.Error("destroying executor failed", "err", err) 816 } 817 } 818 819 handle.pluginClient.Kill() 820 } 821 822 d.tasks.Delete(taskID) 823 return nil 824 } 825 826 func (d *Driver) InspectTask(taskID string) (*drivers.TaskStatus, error) { 827 handle, ok := d.tasks.Get(taskID) 828 if !ok { 829 return nil, drivers.ErrTaskNotFound 830 } 831 832 return handle.TaskStatus(), nil 833 } 834 835 func (d *Driver) TaskStats(ctx context.Context, taskID string, interval time.Duration) (<-chan *drivers.TaskResourceUsage, error) { 836 handle, ok := d.tasks.Get(taskID) 837 if !ok { 838 return nil, drivers.ErrTaskNotFound 839 } 840 841 return handle.exec.Stats(ctx, interval) 842 } 843 844 func (d *Driver) TaskEvents(ctx context.Context) (<-chan *drivers.TaskEvent, error) { 845 return d.eventer.TaskEvents(ctx) 846 } 847 848 func (d *Driver) SignalTask(taskID string, signal string) error { 849 handle, ok := d.tasks.Get(taskID) 850 if !ok { 851 return drivers.ErrTaskNotFound 852 } 853 854 sig := os.Interrupt 855 if s, ok := signals.SignalLookup[signal]; ok { 856 sig = s 857 } else { 858 d.logger.Warn("unknown signal to send to task, using SIGINT instead", "signal", signal, "task_id", handle.taskConfig.ID, "task_name", handle.taskConfig.Name) 859 860 } 861 return handle.exec.Signal(sig) 862 } 863 864 func (d *Driver) ExecTask(taskID string, cmdArgs []string, timeout time.Duration) (*drivers.ExecTaskResult, error) { 865 if len(cmdArgs) == 0 { 866 return nil, fmt.Errorf("error cmd must have at least one value") 867 } 868 handle, ok := d.tasks.Get(taskID) 869 if !ok { 870 return nil, drivers.ErrTaskNotFound 871 } 872 // enter + UUID + cmd + args... 873 cmd := cmdArgs[0] 874 args := cmdArgs[1:] 875 enterArgs := make([]string, 3+len(args)) 876 enterArgs[0] = "enter" 877 enterArgs[1] = handle.uuid 878 enterArgs[2] = handle.env.ReplaceEnv(cmd) 879 copy(enterArgs[3:], handle.env.ParseAndReplace(args)) 880 out, exitCode, err := handle.exec.Exec(time.Now().Add(timeout), rktCmd, enterArgs) 881 if err != nil { 882 return nil, err 883 } 884 885 return &drivers.ExecTaskResult{ 886 Stdout: out, 887 ExitResult: &drivers.ExitResult{ 888 ExitCode: exitCode, 889 }, 890 }, nil 891 892 } 893 894 var _ drivers.ExecTaskStreamingRawDriver = (*Driver)(nil) 895 896 func (d *Driver) ExecTaskStreamingRaw(ctx context.Context, 897 taskID string, 898 command []string, 899 tty bool, 900 stream drivers.ExecTaskStream) error { 901 902 if len(command) == 0 { 903 return fmt.Errorf("error cmd must have at least one value") 904 } 905 handle, ok := d.tasks.Get(taskID) 906 if !ok { 907 return drivers.ErrTaskNotFound 908 } 909 910 enterCmd := []string{rktCmd, "enter", handle.uuid, handle.env.ReplaceEnv(command[0])} 911 enterCmd = append(enterCmd, handle.env.ParseAndReplace(command[1:])...) 912 913 return handle.exec.ExecStreaming(ctx, enterCmd, tty, stream) 914 } 915 916 // GetAbsolutePath returns the absolute path of the passed binary by resolving 917 // it in the path and following symlinks. 918 func GetAbsolutePath(bin string) (string, error) { 919 lp, err := exec.LookPath(bin) 920 if err != nil { 921 return "", fmt.Errorf("failed to resolve path to %q executable: %v", bin, err) 922 } 923 924 return filepath.EvalSymlinks(lp) 925 } 926 927 func rktGetDriverNetwork(uuid string, driverConfigPortMap map[string]string, logger hclog.Logger) (*drivers.DriverNetwork, error) { 928 deadline := time.Now().Add(networkDeadline) 929 var lastErr error 930 try := 0 931 932 for time.Now().Before(deadline) { 933 try++ 934 if status, err := rktGetStatus(uuid, logger); err == nil { 935 for _, net := range status.Networks { 936 if !net.IP.IsGlobalUnicast() { 937 continue 938 } 939 940 // Get the pod manifest so we can figure out which ports are exposed 941 var portmap map[string]int 942 manifest, err := rktGetManifest(uuid) 943 if err == nil { 944 portmap, err = rktManifestMakePortMap(manifest, driverConfigPortMap) 945 if err != nil { 946 lastErr = fmt.Errorf("could not create manifest-based portmap: %v", err) 947 return nil, lastErr 948 } 949 } else { 950 lastErr = fmt.Errorf("could not get pod manifest: %v", err) 951 return nil, lastErr 952 } 953 954 // This is a successful landing; log if its not the first attempt. 955 if try > 1 { 956 logger.Debug("retrieved network info for pod", "uuid", uuid, "attempt", try) 957 } 958 return &drivers.DriverNetwork{ 959 PortMap: portmap, 960 IP: status.Networks[0].IP.String(), 961 }, nil 962 } 963 964 if len(status.Networks) == 0 { 965 lastErr = fmt.Errorf("no networks found") 966 } else { 967 lastErr = fmt.Errorf("no good driver networks out of %d returned", len(status.Networks)) 968 } 969 } else { 970 lastErr = fmt.Errorf("getting status failed: %v", err) 971 } 972 973 waitTime := getJitteredNetworkRetryTime() 974 logger.Debug("failed getting network info for pod, sleeping", "uuid", uuid, "attempt", try, "err", lastErr, "wait", waitTime) 975 time.Sleep(waitTime) 976 } 977 return nil, fmt.Errorf("timed out, last error: %v", lastErr) 978 } 979 980 // Given a rkt/appc pod manifest and driver portmap configuration, create 981 // a driver portmap. 982 func rktManifestMakePortMap(manifest *appcschema.PodManifest, configPortMap map[string]string) (map[string]int, error) { 983 if len(manifest.Apps) == 0 { 984 return nil, fmt.Errorf("manifest has no apps") 985 } 986 if len(manifest.Apps) != 1 { 987 return nil, fmt.Errorf("manifest has multiple apps!") 988 } 989 app := manifest.Apps[0] 990 if app.App == nil { 991 return nil, fmt.Errorf("specified app has no App object") 992 } 993 994 portMap := make(map[string]int) 995 for svc, name := range configPortMap { 996 for _, port := range app.App.Ports { 997 if port.Name.String() == name { 998 portMap[svc] = int(port.Port) 999 } 1000 } 1001 } 1002 return portMap, nil 1003 } 1004 1005 // Retrieve pod status for the pod with the given UUID. 1006 func rktGetStatus(uuid string, logger hclog.Logger) (*Pod, error) { 1007 statusArgs := []string{ 1008 "status", 1009 "--format=json", 1010 uuid, 1011 } 1012 var outBuf, errBuf bytes.Buffer 1013 cmd := exec.Command(rktCmd, statusArgs...) 1014 cmd.Stdout = &outBuf 1015 cmd.Stderr = &errBuf 1016 if err := cmd.Run(); err != nil { 1017 if outBuf.Len() > 0 { 1018 logger.Debug("status output for UUID", "uuid", uuid, "error", elide(outBuf)) 1019 } 1020 if errBuf.Len() == 0 { 1021 return nil, err 1022 } 1023 logger.Debug("status error output", "uuid", uuid, "error", elide(errBuf)) 1024 return nil, fmt.Errorf("%s. stderr: %q", err, elide(errBuf)) 1025 } 1026 var status Pod 1027 if err := json.Unmarshal(outBuf.Bytes(), &status); err != nil { 1028 return nil, err 1029 } 1030 return &status, nil 1031 } 1032 1033 // Retrieves a pod manifest 1034 func rktGetManifest(uuid string) (*appcschema.PodManifest, error) { 1035 statusArgs := []string{ 1036 "cat-manifest", 1037 uuid, 1038 } 1039 var outBuf bytes.Buffer 1040 cmd := exec.Command(rktCmd, statusArgs...) 1041 cmd.Stdout = &outBuf 1042 cmd.Stderr = ioutil.Discard 1043 if err := cmd.Run(); err != nil { 1044 return nil, err 1045 } 1046 var manifest appcschema.PodManifest 1047 if err := json.Unmarshal(outBuf.Bytes(), &manifest); err != nil { 1048 return nil, err 1049 } 1050 return &manifest, nil 1051 } 1052 1053 // Create a time with a 0 to 100ms jitter for rktGetDriverNetwork retries 1054 func getJitteredNetworkRetryTime() time.Duration { 1055 return time.Duration(900+rand.Intn(100)) * time.Millisecond 1056 } 1057 1058 // Conditionally elide a buffer to an arbitrary length 1059 func elideToLen(inBuf bytes.Buffer, length int) bytes.Buffer { 1060 if inBuf.Len() > length { 1061 inBuf.Truncate(length) 1062 inBuf.WriteString("...") 1063 } 1064 return inBuf 1065 } 1066 1067 // Conditionally elide a buffer to an 80 character string 1068 func elide(inBuf bytes.Buffer) string { 1069 tempBuf := elideToLen(inBuf, 80) 1070 return tempBuf.String() 1071 } 1072 1073 func (d *Driver) handleWait(ctx context.Context, handle *taskHandle, ch chan *drivers.ExitResult) { 1074 defer close(ch) 1075 var result *drivers.ExitResult 1076 ps, err := handle.exec.Wait(ctx) 1077 if err != nil { 1078 result = &drivers.ExitResult{ 1079 Err: fmt.Errorf("executor: error waiting on process: %v", err), 1080 } 1081 } else { 1082 result = &drivers.ExitResult{ 1083 ExitCode: ps.ExitCode, 1084 Signal: ps.Signal, 1085 } 1086 } 1087 1088 select { 1089 case <-ctx.Done(): 1090 case <-d.ctx.Done(): 1091 case ch <- result: 1092 } 1093 } 1094 1095 func (d *Driver) Shutdown() { 1096 d.signalShutdown() 1097 }