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  }