github.com/huiliang/nomad@v0.2.1-0.20151124023127-7a8b664699ff/client/driver/executor/exec_linux.go (about)

     1  package executor
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"os/exec"
    10  	"os/user"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    14  	"syscall"
    15  
    16  	"github.com/hashicorp/go-multierror"
    17  	"github.com/hashicorp/nomad/client/allocdir"
    18  	"github.com/hashicorp/nomad/client/driver/args"
    19  	"github.com/hashicorp/nomad/client/driver/environment"
    20  	"github.com/hashicorp/nomad/client/driver/spawn"
    21  	"github.com/hashicorp/nomad/nomad/structs"
    22  
    23  	"github.com/opencontainers/runc/libcontainer/cgroups"
    24  	cgroupFs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
    25  	"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
    26  	cgroupConfig "github.com/opencontainers/runc/libcontainer/configs"
    27  
    28  	cstructs "github.com/hashicorp/nomad/client/driver/structs"
    29  )
    30  
    31  var (
    32  	// A mapping of directories on the host OS to attempt to embed inside each
    33  	// task's chroot.
    34  	chrootEnv = map[string]string{
    35  		"/bin":     "/bin",
    36  		"/etc":     "/etc",
    37  		"/lib":     "/lib",
    38  		"/lib32":   "/lib32",
    39  		"/lib64":   "/lib64",
    40  		"/usr/bin": "/usr/bin",
    41  		"/usr/lib": "/usr/lib",
    42  	}
    43  )
    44  
    45  func NewExecutor() Executor {
    46  	return NewLinuxExecutor()
    47  }
    48  
    49  func NewLinuxExecutor() Executor {
    50  	return &LinuxExecutor{}
    51  }
    52  
    53  // Linux executor is designed to run on linux kernel 2.8+.
    54  type LinuxExecutor struct {
    55  	cmd  exec.Cmd
    56  	user *user.User
    57  
    58  	// Isolation configurations.
    59  	groups   *cgroupConfig.Cgroup
    60  	taskName string
    61  	taskDir  string
    62  	allocDir string
    63  
    64  	// Spawn process.
    65  	spawn *spawn.Spawner
    66  }
    67  
    68  func (e *LinuxExecutor) Command() *exec.Cmd {
    69  	return &e.cmd
    70  }
    71  
    72  func (e *LinuxExecutor) Limit(resources *structs.Resources) error {
    73  	if resources == nil {
    74  		return errNoResources
    75  	}
    76  
    77  	return e.configureCgroups(resources)
    78  }
    79  
    80  // execLinuxID contains the necessary information to reattach to an executed
    81  // process and cleanup the created cgroups.
    82  type ExecLinuxID struct {
    83  	Groups  *cgroupConfig.Cgroup
    84  	Spawn   *spawn.Spawner
    85  	TaskDir string
    86  }
    87  
    88  func (e *LinuxExecutor) Open(id string) error {
    89  	// De-serialize the ID.
    90  	dec := json.NewDecoder(strings.NewReader(id))
    91  	var execID ExecLinuxID
    92  	if err := dec.Decode(&execID); err != nil {
    93  		return fmt.Errorf("Failed to parse id: %v", err)
    94  	}
    95  
    96  	// Setup the executor.
    97  	e.groups = execID.Groups
    98  	e.spawn = execID.Spawn
    99  	e.taskDir = execID.TaskDir
   100  	return e.spawn.Valid()
   101  }
   102  
   103  func (e *LinuxExecutor) ID() (string, error) {
   104  	if e.groups == nil || e.spawn == nil || e.taskDir == "" {
   105  		return "", fmt.Errorf("LinuxExecutor not properly initialized.")
   106  	}
   107  
   108  	// Build the ID.
   109  	id := ExecLinuxID{
   110  		Groups:  e.groups,
   111  		Spawn:   e.spawn,
   112  		TaskDir: e.taskDir,
   113  	}
   114  
   115  	var buffer bytes.Buffer
   116  	enc := json.NewEncoder(&buffer)
   117  	if err := enc.Encode(id); err != nil {
   118  		return "", fmt.Errorf("Failed to serialize id: %v", err)
   119  	}
   120  
   121  	return buffer.String(), nil
   122  }
   123  
   124  // runAs takes a user id as a string and looks up the user, and sets the command
   125  // to execute as that user.
   126  func (e *LinuxExecutor) runAs(userid string) error {
   127  	u, err := user.Lookup(userid)
   128  	if err != nil {
   129  		return fmt.Errorf("Failed to identify user %v: %v", userid, err)
   130  	}
   131  
   132  	// Convert the uid and gid
   133  	uid, err := strconv.ParseUint(u.Uid, 10, 32)
   134  	if err != nil {
   135  		return fmt.Errorf("Unable to convert userid to uint32: %s", err)
   136  	}
   137  	gid, err := strconv.ParseUint(u.Gid, 10, 32)
   138  	if err != nil {
   139  		return fmt.Errorf("Unable to convert groupid to uint32: %s", err)
   140  	}
   141  
   142  	// Set the command to run as that user and group.
   143  	if e.cmd.SysProcAttr == nil {
   144  		e.cmd.SysProcAttr = &syscall.SysProcAttr{}
   145  	}
   146  	if e.cmd.SysProcAttr.Credential == nil {
   147  		e.cmd.SysProcAttr.Credential = &syscall.Credential{}
   148  	}
   149  	e.cmd.SysProcAttr.Credential.Uid = uint32(uid)
   150  	e.cmd.SysProcAttr.Credential.Gid = uint32(gid)
   151  
   152  	return nil
   153  }
   154  
   155  func (e *LinuxExecutor) Start() error {
   156  	// Run as "nobody" user so we don't leak root privilege to the spawned
   157  	// process.
   158  	if err := e.runAs("nobody"); err != nil {
   159  		return err
   160  	}
   161  
   162  	// Parse the commands arguments and replace instances of Nomad environment
   163  	// variables.
   164  	envVars, err := environment.ParseFromList(e.cmd.Env)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	e.cmd.Path = args.ReplaceEnv(e.cmd.Path, envVars.Map())
   170  	e.cmd.Args = args.ParseAndReplace(e.cmd.Args, envVars.Map())
   171  
   172  	spawnState := filepath.Join(e.allocDir, fmt.Sprintf("%s_%s", e.taskName, "exit_status"))
   173  	e.spawn = spawn.NewSpawner(spawnState)
   174  	e.spawn.SetCommand(&e.cmd)
   175  	e.spawn.SetChroot(e.taskDir)
   176  	e.spawn.SetLogs(&spawn.Logs{
   177  		Stdout: filepath.Join(e.taskDir, allocdir.TaskLocal, fmt.Sprintf("%v.stdout", e.taskName)),
   178  		Stderr: filepath.Join(e.taskDir, allocdir.TaskLocal, fmt.Sprintf("%v.stderr", e.taskName)),
   179  		Stdin:  os.DevNull,
   180  	})
   181  
   182  	enterCgroup := func(pid int) error {
   183  		// Join the spawn-daemon to the cgroup.
   184  		manager := e.getCgroupManager(e.groups)
   185  
   186  		// Apply will place the spawn dameon into the created cgroups.
   187  		if err := manager.Apply(pid); err != nil {
   188  			return fmt.Errorf("Failed to join spawn-daemon to the cgroup (%+v): %v", e.groups, err)
   189  		}
   190  
   191  		return nil
   192  	}
   193  
   194  	return e.spawn.Spawn(enterCgroup)
   195  }
   196  
   197  // Wait waits til the user process exits and returns an error on non-zero exit
   198  // codes. Wait also cleans up the task directory and created cgroups.
   199  func (e *LinuxExecutor) Wait() *cstructs.WaitResult {
   200  	errs := new(multierror.Error)
   201  	res := e.spawn.Wait()
   202  	if res.Err != nil {
   203  		errs = multierror.Append(errs, res.Err)
   204  	}
   205  
   206  	if err := e.destroyCgroup(); err != nil {
   207  		errs = multierror.Append(errs, err)
   208  	}
   209  
   210  	if err := e.cleanTaskDir(); err != nil {
   211  		errs = multierror.Append(errs, err)
   212  	}
   213  
   214  	res.Err = errs.ErrorOrNil()
   215  	return res
   216  }
   217  
   218  func (e *LinuxExecutor) Shutdown() error {
   219  	return e.ForceStop()
   220  }
   221  
   222  // ForceStop immediately exits the user process and cleans up both the task
   223  // directory and the cgroups.
   224  func (e *LinuxExecutor) ForceStop() error {
   225  	errs := new(multierror.Error)
   226  	if err := e.destroyCgroup(); err != nil {
   227  		errs = multierror.Append(errs, err)
   228  	}
   229  
   230  	if err := e.cleanTaskDir(); err != nil {
   231  		errs = multierror.Append(errs, err)
   232  	}
   233  
   234  	return errs.ErrorOrNil()
   235  }
   236  
   237  // Task Directory related functions.
   238  
   239  // ConfigureTaskDir creates the necessary directory structure for a proper
   240  // chroot. cleanTaskDir should be called after.
   241  func (e *LinuxExecutor) ConfigureTaskDir(taskName string, alloc *allocdir.AllocDir) error {
   242  	e.taskName = taskName
   243  	e.allocDir = alloc.AllocDir
   244  
   245  	taskDir, ok := alloc.TaskDirs[taskName]
   246  	if !ok {
   247  		fmt.Errorf("Couldn't find task directory for task %v", taskName)
   248  	}
   249  	e.taskDir = taskDir
   250  
   251  	if err := alloc.MountSharedDir(taskName); err != nil {
   252  		return err
   253  	}
   254  
   255  	if err := alloc.Embed(taskName, chrootEnv); err != nil {
   256  		return err
   257  	}
   258  
   259  	// Mount dev
   260  	dev := filepath.Join(taskDir, "dev")
   261  	if !e.pathExists(dev) {
   262  		if err := os.Mkdir(dev, 0777); err != nil {
   263  			return fmt.Errorf("Mkdir(%v) failed: %v", dev, err)
   264  		}
   265  
   266  		if err := syscall.Mount("", dev, "devtmpfs", syscall.MS_RDONLY, ""); err != nil {
   267  			return fmt.Errorf("Couldn't mount /dev to %v: %v", dev, err)
   268  		}
   269  	}
   270  
   271  	// Mount proc
   272  	proc := filepath.Join(taskDir, "proc")
   273  	if !e.pathExists(proc) {
   274  		if err := os.Mkdir(proc, 0777); err != nil {
   275  			return fmt.Errorf("Mkdir(%v) failed: %v", proc, err)
   276  		}
   277  
   278  		if err := syscall.Mount("", proc, "proc", syscall.MS_RDONLY, ""); err != nil {
   279  			return fmt.Errorf("Couldn't mount /proc to %v: %v", proc, err)
   280  		}
   281  	}
   282  
   283  	// Set the tasks AllocDir environment variable.
   284  	env, err := environment.ParseFromList(e.cmd.Env)
   285  	if err != nil {
   286  		return err
   287  	}
   288  	env.SetAllocDir(filepath.Join("/", allocdir.SharedAllocName))
   289  	env.SetTaskLocalDir(filepath.Join("/", allocdir.TaskLocal))
   290  	e.cmd.Env = env.List()
   291  
   292  	return nil
   293  }
   294  
   295  // pathExists is a helper function to check if the path exists.
   296  func (e *LinuxExecutor) pathExists(path string) bool {
   297  	if _, err := os.Stat(path); err != nil {
   298  		if os.IsNotExist(err) {
   299  			return false
   300  		}
   301  	}
   302  	return true
   303  }
   304  
   305  // cleanTaskDir is an idempotent operation to clean the task directory and
   306  // should be called when tearing down the task.
   307  func (e *LinuxExecutor) cleanTaskDir() error {
   308  	// Unmount dev.
   309  	errs := new(multierror.Error)
   310  	dev := filepath.Join(e.taskDir, "dev")
   311  	if e.pathExists(dev) {
   312  		if err := syscall.Unmount(dev, 0); err != nil {
   313  			errs = multierror.Append(errs, fmt.Errorf("Failed to unmount dev (%v): %v", dev, err))
   314  		}
   315  
   316  		if err := os.RemoveAll(dev); err != nil {
   317  			errs = multierror.Append(errs, fmt.Errorf("Failed to delete dev directory (%v): %v", dev, err))
   318  		}
   319  	}
   320  
   321  	// Unmount proc.
   322  	proc := filepath.Join(e.taskDir, "proc")
   323  	if e.pathExists(proc) {
   324  		if err := syscall.Unmount(proc, 0); err != nil {
   325  			errs = multierror.Append(errs, fmt.Errorf("Failed to unmount proc (%v): %v", proc, err))
   326  		}
   327  
   328  		if err := os.RemoveAll(proc); err != nil {
   329  			errs = multierror.Append(errs, fmt.Errorf("Failed to delete proc directory (%v): %v", dev, err))
   330  		}
   331  	}
   332  
   333  	return errs.ErrorOrNil()
   334  }
   335  
   336  // Cgroup related functions.
   337  
   338  // configureCgroups converts a Nomad Resources specification into the equivalent
   339  // cgroup configuration. It returns an error if the resources are invalid.
   340  func (e *LinuxExecutor) configureCgroups(resources *structs.Resources) error {
   341  	e.groups = &cgroupConfig.Cgroup{}
   342  	e.groups.Name = structs.GenerateUUID()
   343  
   344  	// TODO: verify this is needed for things like network access
   345  	e.groups.AllowAllDevices = true
   346  
   347  	if resources.MemoryMB > 0 {
   348  		// Total amount of memory allowed to consume
   349  		e.groups.Memory = int64(resources.MemoryMB * 1024 * 1024)
   350  		// Disable swap to avoid issues on the machine
   351  		e.groups.MemorySwap = int64(-1)
   352  	}
   353  
   354  	if resources.CPU < 2 {
   355  		return fmt.Errorf("resources.CPU must be equal to or greater than 2: %v", resources.CPU)
   356  	}
   357  
   358  	// Set the relative CPU shares for this cgroup.
   359  	e.groups.CpuShares = int64(resources.CPU)
   360  
   361  	if resources.IOPS != 0 {
   362  		// Validate it is in an acceptable range.
   363  		if resources.IOPS < 10 || resources.IOPS > 1000 {
   364  			return fmt.Errorf("resources.IOPS must be between 10 and 1000: %d", resources.IOPS)
   365  		}
   366  
   367  		e.groups.BlkioWeight = uint16(resources.IOPS)
   368  	}
   369  
   370  	return nil
   371  }
   372  
   373  // destroyCgroup kills all processes in the cgroup and removes the cgroup
   374  // configuration from the host.
   375  func (e *LinuxExecutor) destroyCgroup() error {
   376  	if e.groups == nil {
   377  		return errors.New("Can't destroy: cgroup configuration empty")
   378  	}
   379  
   380  	manager := e.getCgroupManager(e.groups)
   381  	pids, err := manager.GetPids()
   382  	if err != nil {
   383  		return fmt.Errorf("Failed to get pids in the cgroup %v: %v", e.groups.Name, err)
   384  	}
   385  
   386  	errs := new(multierror.Error)
   387  	for _, pid := range pids {
   388  		process, err := os.FindProcess(pid)
   389  		if err != nil {
   390  			multierror.Append(errs, fmt.Errorf("Failed to find Pid %v: %v", pid, err))
   391  			continue
   392  		}
   393  
   394  		if err := process.Kill(); err != nil {
   395  			multierror.Append(errs, fmt.Errorf("Failed to kill Pid %v: %v", pid, err))
   396  			continue
   397  		}
   398  	}
   399  
   400  	// Remove the cgroup.
   401  	if err := manager.Destroy(); err != nil {
   402  		multierror.Append(errs, fmt.Errorf("Failed to delete the cgroup directories: %v", err))
   403  	}
   404  
   405  	if len(errs.Errors) != 0 {
   406  		return fmt.Errorf("Failed to destroy cgroup: %v", errs)
   407  	}
   408  
   409  	return nil
   410  }
   411  
   412  // getCgroupManager returns the correct libcontainer cgroup manager.
   413  func (e *LinuxExecutor) getCgroupManager(groups *cgroupConfig.Cgroup) cgroups.Manager {
   414  	var manager cgroups.Manager
   415  	manager = &cgroupFs.Manager{Cgroups: groups}
   416  	if systemd.UseSystemd() {
   417  		manager = &systemd.Manager{Cgroups: groups}
   418  	}
   419  	return manager
   420  }