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  }