github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/client/driver/executor/executor_linux.go (about)

     1  package executor
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/user"
     7  	"path/filepath"
     8  	"strconv"
     9  	"syscall"
    10  
    11  	"github.com/hashicorp/go-multierror"
    12  	"github.com/opencontainers/runc/libcontainer/cgroups"
    13  	cgroupFs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
    14  	"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
    15  	cgroupConfig "github.com/opencontainers/runc/libcontainer/configs"
    16  
    17  	"github.com/hashicorp/nomad/client/allocdir"
    18  	"github.com/hashicorp/nomad/nomad/structs"
    19  )
    20  
    21  var (
    22  	// A mapping of directories on the host OS to attempt to embed inside each
    23  	// task's chroot.
    24  	chrootEnv = map[string]string{
    25  		"/bin":       "/bin",
    26  		"/etc":       "/etc",
    27  		"/lib":       "/lib",
    28  		"/lib32":     "/lib32",
    29  		"/lib64":     "/lib64",
    30  		"/usr/bin":   "/usr/bin",
    31  		"/usr/lib":   "/usr/lib",
    32  		"/usr/share": "/usr/share",
    33  	}
    34  )
    35  
    36  // configureIsolation configures chroot and creates cgroups
    37  func (e *UniversalExecutor) configureIsolation() error {
    38  	if e.ctx.FSIsolation {
    39  		if err := e.configureChroot(); err != nil {
    40  			return err
    41  		}
    42  	}
    43  
    44  	if e.ctx.ResourceLimits {
    45  		if err := e.configureCgroups(e.ctx.TaskResources); err != nil {
    46  			return fmt.Errorf("error creating cgroups: %v", err)
    47  		}
    48  		if err := e.applyLimits(os.Getpid()); err != nil {
    49  			if er := DestroyCgroup(e.groups); er != nil {
    50  				e.logger.Printf("[ERR] executor: error destroying cgroup: %v", er)
    51  			}
    52  			if er := e.removeChrootMounts(); er != nil {
    53  				e.logger.Printf("[ERR] executor: error removing chroot: %v", er)
    54  			}
    55  			return fmt.Errorf("error entering the plugin process in the cgroup: %v:", err)
    56  		}
    57  	}
    58  	return nil
    59  }
    60  
    61  // applyLimits puts a process in a pre-configured cgroup
    62  func (e *UniversalExecutor) applyLimits(pid int) error {
    63  	if !e.ctx.ResourceLimits {
    64  		return nil
    65  	}
    66  
    67  	// Entering the process in the cgroup
    68  	manager := getCgroupManager(e.groups)
    69  	if err := manager.Apply(pid); err != nil {
    70  		e.logger.Printf("[ERR] executor: unable to join cgroup: %v", err)
    71  		if err := e.Exit(); err != nil {
    72  			e.logger.Printf("[ERR] executor: unable to kill process: %v", err)
    73  		}
    74  		return err
    75  	}
    76  
    77  	return nil
    78  }
    79  
    80  // configureCgroups converts a Nomad Resources specification into the equivalent
    81  // cgroup configuration. It returns an error if the resources are invalid.
    82  func (e *UniversalExecutor) configureCgroups(resources *structs.Resources) error {
    83  	e.groups = &cgroupConfig.Cgroup{}
    84  	e.groups.Resources = &cgroupConfig.Resources{}
    85  	e.groups.Name = structs.GenerateUUID()
    86  
    87  	// TODO: verify this is needed for things like network access
    88  	e.groups.Resources.AllowAllDevices = true
    89  
    90  	if resources.MemoryMB > 0 {
    91  		// Total amount of memory allowed to consume
    92  		e.groups.Resources.Memory = int64(resources.MemoryMB * 1024 * 1024)
    93  		// Disable swap to avoid issues on the machine
    94  		e.groups.Resources.MemorySwap = int64(-1)
    95  	}
    96  
    97  	if resources.CPU < 2 {
    98  		return fmt.Errorf("resources.CPU must be equal to or greater than 2: %v", resources.CPU)
    99  	}
   100  
   101  	// Set the relative CPU shares for this cgroup.
   102  	e.groups.Resources.CpuShares = int64(resources.CPU)
   103  
   104  	if resources.IOPS != 0 {
   105  		// Validate it is in an acceptable range.
   106  		if resources.IOPS < 10 || resources.IOPS > 1000 {
   107  			return fmt.Errorf("resources.IOPS must be between 10 and 1000: %d", resources.IOPS)
   108  		}
   109  
   110  		e.groups.Resources.BlkioWeight = uint16(resources.IOPS)
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  // runAs takes a user id as a string and looks up the user, and sets the command
   117  // to execute as that user.
   118  func (e *UniversalExecutor) runAs(userid string) error {
   119  	u, err := user.Lookup(userid)
   120  	if err != nil {
   121  		return fmt.Errorf("Failed to identify user %v: %v", userid, err)
   122  	}
   123  
   124  	// Convert the uid and gid
   125  	uid, err := strconv.ParseUint(u.Uid, 10, 32)
   126  	if err != nil {
   127  		return fmt.Errorf("Unable to convert userid to uint32: %s", err)
   128  	}
   129  	gid, err := strconv.ParseUint(u.Gid, 10, 32)
   130  	if err != nil {
   131  		return fmt.Errorf("Unable to convert groupid to uint32: %s", err)
   132  	}
   133  
   134  	// Set the command to run as that user and group.
   135  	if e.cmd.SysProcAttr == nil {
   136  		e.cmd.SysProcAttr = &syscall.SysProcAttr{}
   137  	}
   138  	if e.cmd.SysProcAttr.Credential == nil {
   139  		e.cmd.SysProcAttr.Credential = &syscall.Credential{}
   140  	}
   141  	e.cmd.SysProcAttr.Credential.Uid = uint32(uid)
   142  	e.cmd.SysProcAttr.Credential.Gid = uint32(gid)
   143  
   144  	return nil
   145  }
   146  
   147  // configureChroot configures a chroot
   148  func (e *UniversalExecutor) configureChroot() error {
   149  	allocDir := e.ctx.AllocDir
   150  	if err := allocDir.MountSharedDir(e.ctx.TaskName); err != nil {
   151  		return err
   152  	}
   153  
   154  	if err := allocDir.Embed(e.ctx.TaskName, chrootEnv); err != nil {
   155  		return err
   156  	}
   157  
   158  	// Set the tasks AllocDir environment variable.
   159  	e.ctx.TaskEnv.SetAllocDir(filepath.Join("/", allocdir.SharedAllocName)).SetTaskLocalDir(filepath.Join("/", allocdir.TaskLocal)).Build()
   160  
   161  	if e.cmd.SysProcAttr == nil {
   162  		e.cmd.SysProcAttr = &syscall.SysProcAttr{}
   163  	}
   164  	e.cmd.SysProcAttr.Chroot = e.taskDir
   165  	e.cmd.Dir = "/"
   166  
   167  	if err := allocDir.MountSpecialDirs(e.taskDir); err != nil {
   168  		return err
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  // cleanTaskDir is an idempotent operation to clean the task directory and
   175  // should be called when tearing down the task.
   176  func (e *UniversalExecutor) removeChrootMounts() error {
   177  	// Prevent a race between Wait/ForceStop
   178  	e.lock.Lock()
   179  	defer e.lock.Unlock()
   180  	return e.ctx.AllocDir.UnmountAll()
   181  }
   182  
   183  // destroyCgroup kills all processes in the cgroup and removes the cgroup
   184  // configuration from the host.
   185  func DestroyCgroup(groups *cgroupConfig.Cgroup) error {
   186  	merrs := new(multierror.Error)
   187  	if groups == nil {
   188  		return fmt.Errorf("Can't destroy: cgroup configuration empty")
   189  	}
   190  
   191  	manager := getCgroupManager(groups)
   192  	if pids, perr := manager.GetPids(); perr == nil {
   193  		for _, pid := range pids {
   194  			proc, err := os.FindProcess(pid)
   195  			if err != nil {
   196  				merrs.Errors = append(merrs.Errors, fmt.Errorf("error finding process %v: %v", pid, err))
   197  			} else {
   198  				if e := proc.Kill(); e != nil {
   199  					merrs.Errors = append(merrs.Errors, fmt.Errorf("error killing process %v: %v", pid, e))
   200  				}
   201  			}
   202  		}
   203  	}
   204  
   205  	// Remove the cgroup.
   206  	if err := manager.Destroy(); err != nil {
   207  		multierror.Append(merrs, fmt.Errorf("Failed to delete the cgroup directories: %v", err))
   208  	}
   209  
   210  	if len(merrs.Errors) != 0 {
   211  		return fmt.Errorf("errors while destroying cgroup: %v", merrs)
   212  	}
   213  	return nil
   214  }
   215  
   216  // getCgroupManager returns the correct libcontainer cgroup manager.
   217  func getCgroupManager(groups *cgroupConfig.Cgroup) cgroups.Manager {
   218  	var manager cgroups.Manager
   219  	manager = &cgroupFs.Manager{Cgroups: groups}
   220  	if systemd.UseSystemd() {
   221  		manager = &systemd.Manager{Cgroups: groups}
   222  	}
   223  	return manager
   224  }