github.com/quite/nomad@v0.8.6/client/driver/lxc.go (about)

     1  //+build linux,lxc
     2  
     3  package driver
     4  
     5  import (
     6  	"context"
     7  	"encoding/json"
     8  	"fmt"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    14  	"syscall"
    15  	"time"
    16  
    17  	"github.com/hashicorp/nomad/client/fingerprint"
    18  	"github.com/hashicorp/nomad/client/stats"
    19  	"github.com/hashicorp/nomad/helper/fields"
    20  	"github.com/hashicorp/nomad/nomad/structs"
    21  	"github.com/mitchellh/mapstructure"
    22  
    23  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    24  	cstructs "github.com/hashicorp/nomad/client/structs"
    25  	lxc "gopkg.in/lxc/go-lxc.v2"
    26  )
    27  
    28  const (
    29  	// lxcConfigOption is the key for enabling the LXC driver in the
    30  	// Config.Options map.
    31  	lxcConfigOption = "driver.lxc.enable"
    32  
    33  	// lxcVolumesConfigOption is the key for enabling the use of
    34  	// custom bind volumes to arbitrary host paths
    35  	lxcVolumesConfigOption  = "lxc.volumes.enabled"
    36  	lxcVolumesConfigDefault = true
    37  
    38  	// containerMonitorIntv is the interval at which the driver checks if the
    39  	// container is still alive
    40  	containerMonitorIntv = 2 * time.Second
    41  )
    42  
    43  var (
    44  	LXCMeasuredCpuStats = []string{"System Mode", "User Mode", "Percent"}
    45  
    46  	LXCMeasuredMemStats = []string{"RSS", "Cache", "Swap", "Max Usage", "Kernel Usage", "Kernel Max Usage"}
    47  )
    48  
    49  // Add the lxc driver to the list of builtin drivers
    50  func init() {
    51  	BuiltinDrivers["lxc"] = NewLxcDriver
    52  }
    53  
    54  // LxcDriver allows users to run LXC Containers
    55  type LxcDriver struct {
    56  	DriverContext
    57  	fingerprint.StaticFingerprinter
    58  }
    59  
    60  // LxcDriverConfig is the configuration of the LXC Container
    61  type LxcDriverConfig struct {
    62  	Template             string
    63  	Distro               string
    64  	Release              string
    65  	Arch                 string
    66  	ImageVariant         string   `mapstructure:"image_variant"`
    67  	ImageServer          string   `mapstructure:"image_server"`
    68  	GPGKeyID             string   `mapstructure:"gpg_key_id"`
    69  	GPGKeyServer         string   `mapstructure:"gpg_key_server"`
    70  	DisableGPGValidation bool     `mapstructure:"disable_gpg"`
    71  	FlushCache           bool     `mapstructure:"flush_cache"`
    72  	ForceCache           bool     `mapstructure:"force_cache"`
    73  	TemplateArgs         []string `mapstructure:"template_args"`
    74  	LogLevel             string   `mapstructure:"log_level"`
    75  	Verbosity            string
    76  	Volumes              []string `mapstructure:"volumes"`
    77  }
    78  
    79  // NewLxcDriver returns a new instance of the LXC driver
    80  func NewLxcDriver(ctx *DriverContext) Driver {
    81  	return &LxcDriver{DriverContext: *ctx}
    82  }
    83  
    84  // Validate validates the lxc driver configuration
    85  func (d *LxcDriver) Validate(config map[string]interface{}) error {
    86  	fd := &fields.FieldData{
    87  		Raw: config,
    88  		Schema: map[string]*fields.FieldSchema{
    89  			"template": {
    90  				Type:     fields.TypeString,
    91  				Required: true,
    92  			},
    93  			"distro": {
    94  				Type:     fields.TypeString,
    95  				Required: false,
    96  			},
    97  			"release": {
    98  				Type:     fields.TypeString,
    99  				Required: false,
   100  			},
   101  			"arch": {
   102  				Type:     fields.TypeString,
   103  				Required: false,
   104  			},
   105  			"image_variant": {
   106  				Type:     fields.TypeString,
   107  				Required: false,
   108  			},
   109  			"image_server": {
   110  				Type:     fields.TypeString,
   111  				Required: false,
   112  			},
   113  			"gpg_key_id": {
   114  				Type:     fields.TypeString,
   115  				Required: false,
   116  			},
   117  			"gpg_key_server": {
   118  				Type:     fields.TypeString,
   119  				Required: false,
   120  			},
   121  			"disable_gpg": {
   122  				Type:     fields.TypeString,
   123  				Required: false,
   124  			},
   125  			"flush_cache": {
   126  				Type:     fields.TypeString,
   127  				Required: false,
   128  			},
   129  			"force_cache": {
   130  				Type:     fields.TypeString,
   131  				Required: false,
   132  			},
   133  			"template_args": {
   134  				Type:     fields.TypeArray,
   135  				Required: false,
   136  			},
   137  			"log_level": {
   138  				Type:     fields.TypeString,
   139  				Required: false,
   140  			},
   141  			"verbosity": {
   142  				Type:     fields.TypeString,
   143  				Required: false,
   144  			},
   145  			"volumes": {
   146  				Type:     fields.TypeArray,
   147  				Required: false,
   148  			},
   149  		},
   150  	}
   151  
   152  	if err := fd.Validate(); err != nil {
   153  		return err
   154  	}
   155  
   156  	volumes, _ := fd.GetOk("volumes")
   157  	for _, volDesc := range volumes.([]interface{}) {
   158  		volStr := volDesc.(string)
   159  		paths := strings.Split(volStr, ":")
   160  		if len(paths) != 2 {
   161  			return fmt.Errorf("invalid volume bind mount entry: '%s'", volStr)
   162  		}
   163  		if len(paths[0]) == 0 || len(paths[1]) == 0 {
   164  			return fmt.Errorf("invalid volume bind mount entry: '%s'", volStr)
   165  		}
   166  		if paths[1][0] == '/' {
   167  			return fmt.Errorf("unsupported absolute container mount point: '%s'", paths[1])
   168  		}
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  func (d *LxcDriver) Abilities() DriverAbilities {
   175  	return DriverAbilities{
   176  		SendSignals: false,
   177  		Exec:        false,
   178  	}
   179  }
   180  
   181  func (d *LxcDriver) FSIsolation() cstructs.FSIsolation {
   182  	return cstructs.FSIsolationImage
   183  }
   184  
   185  // Fingerprint fingerprints the lxc driver configuration
   186  func (d *LxcDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error {
   187  	cfg := req.Config
   188  
   189  	enabled := cfg.ReadBoolDefault(lxcConfigOption, true)
   190  	if !enabled && !cfg.DevMode {
   191  		return nil
   192  	}
   193  	version := lxc.Version()
   194  	if version == "" {
   195  		return nil
   196  	}
   197  	resp.AddAttribute("driver.lxc.version", version)
   198  	resp.AddAttribute("driver.lxc", "1")
   199  	resp.Detected = true
   200  
   201  	// Advertise if this node supports lxc volumes
   202  	if d.config.ReadBoolDefault(lxcVolumesConfigOption, lxcVolumesConfigDefault) {
   203  		resp.AddAttribute("driver."+lxcVolumesConfigOption, "1")
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  func (d *LxcDriver) Prestart(*ExecContext, *structs.Task) (*PrestartResponse, error) {
   210  	return nil, nil
   211  }
   212  
   213  // Start starts the LXC Driver
   214  func (d *LxcDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) {
   215  	sresp, err, errCleanup := d.startWithCleanup(ctx, task)
   216  	if err != nil {
   217  		if cleanupErr := errCleanup(); cleanupErr != nil {
   218  			d.logger.Printf("[ERR] error occurred while cleaning up from error in Start: %v", cleanupErr)
   219  		}
   220  	}
   221  	return sresp, err
   222  }
   223  
   224  func (d *LxcDriver) startWithCleanup(ctx *ExecContext, task *structs.Task) (*StartResponse, error, func() error) {
   225  	noCleanup := func() error { return nil }
   226  	var driverConfig LxcDriverConfig
   227  	if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
   228  		return nil, err, noCleanup
   229  	}
   230  	lxcPath := lxc.DefaultConfigPath()
   231  	if path := d.config.Read("driver.lxc.path"); path != "" {
   232  		lxcPath = path
   233  	}
   234  
   235  	containerName := fmt.Sprintf("%s-%s", task.Name, d.DriverContext.allocID)
   236  	c, err := lxc.NewContainer(containerName, lxcPath)
   237  	if err != nil {
   238  		return nil, fmt.Errorf("unable to initialize container: %v", err), noCleanup
   239  	}
   240  
   241  	var verbosity lxc.Verbosity
   242  	switch driverConfig.Verbosity {
   243  	case "verbose":
   244  		verbosity = lxc.Verbose
   245  	case "", "quiet":
   246  		verbosity = lxc.Quiet
   247  	default:
   248  		return nil, fmt.Errorf("lxc driver config 'verbosity' can only be either quiet or verbose"), noCleanup
   249  	}
   250  	c.SetVerbosity(verbosity)
   251  
   252  	var logLevel lxc.LogLevel
   253  	switch driverConfig.LogLevel {
   254  	case "trace":
   255  		logLevel = lxc.TRACE
   256  	case "debug":
   257  		logLevel = lxc.DEBUG
   258  	case "info":
   259  		logLevel = lxc.INFO
   260  	case "warn":
   261  		logLevel = lxc.WARN
   262  	case "", "error":
   263  		logLevel = lxc.ERROR
   264  	default:
   265  		return nil, fmt.Errorf("lxc driver config 'log_level' can only be trace, debug, info, warn or error"), noCleanup
   266  	}
   267  	c.SetLogLevel(logLevel)
   268  
   269  	logFile := filepath.Join(ctx.TaskDir.Dir, fmt.Sprintf("%v-lxc.log", task.Name))
   270  	c.SetLogFile(logFile)
   271  
   272  	options := lxc.TemplateOptions{
   273  		Template:             driverConfig.Template,
   274  		Distro:               driverConfig.Distro,
   275  		Release:              driverConfig.Release,
   276  		Arch:                 driverConfig.Arch,
   277  		FlushCache:           driverConfig.FlushCache,
   278  		DisableGPGValidation: driverConfig.DisableGPGValidation,
   279  		ExtraArgs:            driverConfig.TemplateArgs,
   280  	}
   281  
   282  	if err := c.Create(options); err != nil {
   283  		return nil, fmt.Errorf("unable to create container: %v", err), noCleanup
   284  	}
   285  
   286  	// Set the network type to none
   287  	if err := c.SetConfigItem("lxc.network.type", "none"); err != nil {
   288  		return nil, fmt.Errorf("error setting network type configuration: %v", err), c.Destroy
   289  	}
   290  
   291  	// Bind mount the shared alloc dir and task local dir in the container
   292  	mounts := []string{
   293  		fmt.Sprintf("%s local none rw,bind,create=dir", ctx.TaskDir.LocalDir),
   294  		fmt.Sprintf("%s alloc none rw,bind,create=dir", ctx.TaskDir.SharedAllocDir),
   295  		fmt.Sprintf("%s secrets none rw,bind,create=dir", ctx.TaskDir.SecretsDir),
   296  	}
   297  
   298  	volumesEnabled := d.config.ReadBoolDefault(lxcVolumesConfigOption, lxcVolumesConfigDefault)
   299  
   300  	for _, volDesc := range driverConfig.Volumes {
   301  		// the format was checked in Validate()
   302  		paths := strings.Split(volDesc, ":")
   303  
   304  		if filepath.IsAbs(paths[0]) {
   305  			if !volumesEnabled {
   306  				return nil, fmt.Errorf("absolute bind-mount volume in config but '%v' is false", lxcVolumesConfigOption), c.Destroy
   307  			}
   308  		} else {
   309  			// Relative source paths are treated as relative to alloc dir
   310  			paths[0] = filepath.Join(ctx.TaskDir.Dir, paths[0])
   311  		}
   312  
   313  		mounts = append(mounts, fmt.Sprintf("%s %s none rw,bind,create=dir", paths[0], paths[1]))
   314  	}
   315  
   316  	for _, mnt := range mounts {
   317  		if err := c.SetConfigItem("lxc.mount.entry", mnt); err != nil {
   318  			return nil, fmt.Errorf("error setting bind mount %q error: %v", mnt, err), c.Destroy
   319  		}
   320  	}
   321  
   322  	// Start the container
   323  	if err := c.Start(); err != nil {
   324  		return nil, fmt.Errorf("unable to start container: %v", err), c.Destroy
   325  	}
   326  
   327  	stopAndDestroyCleanup := func() error {
   328  		if err := c.Stop(); err != nil {
   329  			return err
   330  		}
   331  		return c.Destroy()
   332  	}
   333  
   334  	// Set the resource limits
   335  	if err := c.SetMemoryLimit(lxc.ByteSize(task.Resources.MemoryMB) * lxc.MB); err != nil {
   336  		return nil, fmt.Errorf("unable to set memory limits: %v", err), stopAndDestroyCleanup
   337  	}
   338  	if err := c.SetCgroupItem("cpu.shares", strconv.Itoa(task.Resources.CPU)); err != nil {
   339  		return nil, fmt.Errorf("unable to set cpu shares: %v", err), stopAndDestroyCleanup
   340  	}
   341  
   342  	h := lxcDriverHandle{
   343  		container:      c,
   344  		initPid:        c.InitPid(),
   345  		lxcPath:        lxcPath,
   346  		logger:         d.logger,
   347  		killTimeout:    GetKillTimeout(task.KillTimeout, d.DriverContext.config.MaxKillTimeout),
   348  		maxKillTimeout: d.DriverContext.config.MaxKillTimeout,
   349  		totalCpuStats:  stats.NewCpuStats(),
   350  		userCpuStats:   stats.NewCpuStats(),
   351  		systemCpuStats: stats.NewCpuStats(),
   352  		waitCh:         make(chan *dstructs.WaitResult, 1),
   353  		doneCh:         make(chan bool, 1),
   354  	}
   355  
   356  	go h.run()
   357  
   358  	return &StartResponse{Handle: &h}, nil, noCleanup
   359  }
   360  
   361  func (d *LxcDriver) Cleanup(*ExecContext, *CreatedResources) error { return nil }
   362  
   363  // Open creates the driver to monitor an existing LXC container
   364  func (d *LxcDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   365  	pid := &lxcPID{}
   366  	if err := json.Unmarshal([]byte(handleID), pid); err != nil {
   367  		return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err)
   368  	}
   369  
   370  	var container *lxc.Container
   371  	containers := lxc.Containers(pid.LxcPath)
   372  	for _, c := range containers {
   373  		if c.Name() == pid.ContainerName {
   374  			container = &c
   375  			break
   376  		}
   377  	}
   378  
   379  	if container == nil {
   380  		return nil, fmt.Errorf("container %v not found", pid.ContainerName)
   381  	}
   382  
   383  	handle := lxcDriverHandle{
   384  		container:      container,
   385  		initPid:        container.InitPid(),
   386  		lxcPath:        pid.LxcPath,
   387  		logger:         d.logger,
   388  		killTimeout:    pid.KillTimeout,
   389  		maxKillTimeout: d.DriverContext.config.MaxKillTimeout,
   390  		totalCpuStats:  stats.NewCpuStats(),
   391  		userCpuStats:   stats.NewCpuStats(),
   392  		systemCpuStats: stats.NewCpuStats(),
   393  		waitCh:         make(chan *dstructs.WaitResult, 1),
   394  		doneCh:         make(chan bool, 1),
   395  	}
   396  	go handle.run()
   397  
   398  	return &handle, nil
   399  }
   400  
   401  // lxcDriverHandle allows controlling the lifecycle of an lxc container
   402  type lxcDriverHandle struct {
   403  	container *lxc.Container
   404  	initPid   int
   405  	lxcPath   string
   406  
   407  	logger *log.Logger
   408  
   409  	killTimeout    time.Duration
   410  	maxKillTimeout time.Duration
   411  
   412  	totalCpuStats  *stats.CpuStats
   413  	userCpuStats   *stats.CpuStats
   414  	systemCpuStats *stats.CpuStats
   415  
   416  	waitCh chan *dstructs.WaitResult
   417  	doneCh chan bool
   418  }
   419  
   420  type lxcPID struct {
   421  	ContainerName string
   422  	InitPid       int
   423  	LxcPath       string
   424  	KillTimeout   time.Duration
   425  }
   426  
   427  func (h *lxcDriverHandle) ID() string {
   428  	pid := lxcPID{
   429  		ContainerName: h.container.Name(),
   430  		InitPid:       h.initPid,
   431  		LxcPath:       h.lxcPath,
   432  		KillTimeout:   h.killTimeout,
   433  	}
   434  	data, err := json.Marshal(pid)
   435  	if err != nil {
   436  		h.logger.Printf("[ERR] driver.lxc: failed to marshal lxc PID to JSON: %v", err)
   437  	}
   438  	return string(data)
   439  }
   440  
   441  func (h *lxcDriverHandle) WaitCh() chan *dstructs.WaitResult {
   442  	return h.waitCh
   443  }
   444  
   445  func (h *lxcDriverHandle) Update(task *structs.Task) error {
   446  	h.killTimeout = GetKillTimeout(task.KillTimeout, h.killTimeout)
   447  	return nil
   448  }
   449  
   450  func (h *lxcDriverHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) {
   451  	return nil, 0, fmt.Errorf("lxc driver cannot execute commands")
   452  }
   453  
   454  func (h *lxcDriverHandle) Kill() error {
   455  	name := h.container.Name()
   456  
   457  	h.logger.Printf("[INFO] driver.lxc: shutting down container %q", name)
   458  	if err := h.container.Shutdown(h.killTimeout); err != nil {
   459  		h.logger.Printf("[INFO] driver.lxc: shutting down container %q failed: %v", name, err)
   460  		if err := h.container.Stop(); err != nil {
   461  			h.logger.Printf("[ERR] driver.lxc: error stopping container %q: %v", name, err)
   462  		}
   463  	}
   464  
   465  	close(h.doneCh)
   466  	return nil
   467  }
   468  
   469  func (h *lxcDriverHandle) Signal(s os.Signal) error {
   470  	return fmt.Errorf("LXC does not support signals")
   471  }
   472  
   473  func (h *lxcDriverHandle) Stats() (*cstructs.TaskResourceUsage, error) {
   474  	cpuStats, err := h.container.CPUStats()
   475  	if err != nil {
   476  		return nil, nil
   477  	}
   478  	total, err := h.container.CPUTime()
   479  	if err != nil {
   480  		return nil, nil
   481  	}
   482  
   483  	t := time.Now()
   484  
   485  	// Get the cpu stats
   486  	system := cpuStats["system"]
   487  	user := cpuStats["user"]
   488  	cs := &cstructs.CpuStats{
   489  		SystemMode: h.systemCpuStats.Percent(float64(system)),
   490  		UserMode:   h.systemCpuStats.Percent(float64(user)),
   491  		Percent:    h.totalCpuStats.Percent(float64(total)),
   492  		TotalTicks: float64(user + system),
   493  		Measured:   LXCMeasuredCpuStats,
   494  	}
   495  
   496  	// Get the Memory Stats
   497  	memData := map[string]uint64{
   498  		"rss":   0,
   499  		"cache": 0,
   500  		"swap":  0,
   501  	}
   502  	rawMemStats := h.container.CgroupItem("memory.stat")
   503  	for _, rawMemStat := range rawMemStats {
   504  		key, val, err := keysToVal(rawMemStat)
   505  		if err != nil {
   506  			h.logger.Printf("[ERR] driver.lxc: error getting stat for line %q", rawMemStat)
   507  			continue
   508  		}
   509  		if _, ok := memData[key]; ok {
   510  			memData[key] = val
   511  
   512  		}
   513  	}
   514  	ms := &cstructs.MemoryStats{
   515  		RSS:      memData["rss"],
   516  		Cache:    memData["cache"],
   517  		Swap:     memData["swap"],
   518  		Measured: LXCMeasuredMemStats,
   519  	}
   520  
   521  	mu := h.container.CgroupItem("memory.max_usage_in_bytes")
   522  	for _, rawMemMaxUsage := range mu {
   523  		val, err := strconv.ParseUint(rawMemMaxUsage, 10, 64)
   524  		if err != nil {
   525  			h.logger.Printf("[ERR] driver.lxc: unable to get max memory usage: %v", err)
   526  			continue
   527  		}
   528  		ms.MaxUsage = val
   529  	}
   530  	ku := h.container.CgroupItem("memory.kmem.usage_in_bytes")
   531  	for _, rawKernelUsage := range ku {
   532  		val, err := strconv.ParseUint(rawKernelUsage, 10, 64)
   533  		if err != nil {
   534  			h.logger.Printf("[ERR] driver.lxc: unable to get kernel memory usage: %v", err)
   535  			continue
   536  		}
   537  		ms.KernelUsage = val
   538  	}
   539  
   540  	mku := h.container.CgroupItem("memory.kmem.max_usage_in_bytes")
   541  	for _, rawMaxKernelUsage := range mku {
   542  		val, err := strconv.ParseUint(rawMaxKernelUsage, 10, 64)
   543  		if err != nil {
   544  			h.logger.Printf("[ERR] driver.lxc: unable to get max kernel memory usage: %v", err)
   545  			continue
   546  		}
   547  		ms.KernelMaxUsage = val
   548  	}
   549  
   550  	taskResUsage := cstructs.TaskResourceUsage{
   551  		ResourceUsage: &cstructs.ResourceUsage{
   552  			CpuStats:    cs,
   553  			MemoryStats: ms,
   554  		},
   555  		Timestamp: t.UTC().UnixNano(),
   556  	}
   557  
   558  	return &taskResUsage, nil
   559  }
   560  
   561  func (h *lxcDriverHandle) run() {
   562  	defer close(h.waitCh)
   563  	timer := time.NewTimer(containerMonitorIntv)
   564  	for {
   565  		select {
   566  		case <-timer.C:
   567  			process, err := os.FindProcess(h.initPid)
   568  			if err != nil {
   569  				h.waitCh <- &dstructs.WaitResult{Err: err}
   570  				return
   571  			}
   572  			if err := process.Signal(syscall.Signal(0)); err != nil {
   573  				h.waitCh <- &dstructs.WaitResult{}
   574  				return
   575  			}
   576  			timer.Reset(containerMonitorIntv)
   577  		case <-h.doneCh:
   578  			h.waitCh <- &dstructs.WaitResult{}
   579  			return
   580  		}
   581  	}
   582  }
   583  
   584  func keysToVal(line string) (string, uint64, error) {
   585  	tokens := strings.Split(line, " ")
   586  	if len(tokens) != 2 {
   587  		return "", 0, fmt.Errorf("line isn't a k/v pair")
   588  	}
   589  	key := tokens[0]
   590  	val, err := strconv.ParseUint(tokens[1], 10, 64)
   591  	return key, val, err
   592  }