github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/client/driver/lxc.go (about)

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