github.com/dougm/docker@v1.5.0/daemon/execdriver/lxc/driver.go (about)

     1  package lxc
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"syscall"
    16  	"time"
    17  
    18  	"github.com/kr/pty"
    19  
    20  	log "github.com/Sirupsen/logrus"
    21  	"github.com/docker/docker/daemon/execdriver"
    22  	"github.com/docker/docker/pkg/term"
    23  	"github.com/docker/docker/utils"
    24  	"github.com/docker/libcontainer/cgroups"
    25  	"github.com/docker/libcontainer/mount/nodes"
    26  )
    27  
    28  const DriverName = "lxc"
    29  
    30  var ErrExec = errors.New("Unsupported: Exec is not supported by the lxc driver")
    31  
    32  type driver struct {
    33  	root       string // root path for the driver to use
    34  	initPath   string
    35  	apparmor   bool
    36  	sharedRoot bool
    37  }
    38  
    39  func NewDriver(root, initPath string, apparmor bool) (*driver, error) {
    40  	// setup unconfined symlink
    41  	if err := linkLxcStart(root); err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	return &driver{
    46  		apparmor:   apparmor,
    47  		root:       root,
    48  		initPath:   initPath,
    49  		sharedRoot: rootIsShared(),
    50  	}, nil
    51  }
    52  
    53  func (d *driver) Name() string {
    54  	version := d.version()
    55  	return fmt.Sprintf("%s-%s", DriverName, version)
    56  }
    57  
    58  func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
    59  	var (
    60  		term execdriver.Terminal
    61  		err  error
    62  	)
    63  
    64  	if c.ProcessConfig.Tty {
    65  		term, err = NewTtyConsole(&c.ProcessConfig, pipes)
    66  	} else {
    67  		term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes)
    68  	}
    69  	c.ProcessConfig.Terminal = term
    70  
    71  	c.Mounts = append(c.Mounts, execdriver.Mount{
    72  		Source:      d.initPath,
    73  		Destination: c.InitPath,
    74  		Writable:    false,
    75  		Private:     true,
    76  	})
    77  
    78  	if err := d.generateEnvConfig(c); err != nil {
    79  		return execdriver.ExitStatus{ExitCode: -1}, err
    80  	}
    81  	configPath, err := d.generateLXCConfig(c)
    82  	if err != nil {
    83  		return execdriver.ExitStatus{ExitCode: -1}, err
    84  	}
    85  	params := []string{
    86  		"lxc-start",
    87  		"-n", c.ID,
    88  		"-f", configPath,
    89  	}
    90  	if c.Network.ContainerID != "" {
    91  		params = append(params,
    92  			"--share-net", c.Network.ContainerID,
    93  		)
    94  	}
    95  
    96  	params = append(params,
    97  		"--",
    98  		c.InitPath,
    99  	)
   100  	if c.Network.Interface != nil {
   101  		params = append(params,
   102  			"-g", c.Network.Interface.Gateway,
   103  			"-i", fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen),
   104  		)
   105  	}
   106  	params = append(params,
   107  		"-mtu", strconv.Itoa(c.Network.Mtu),
   108  	)
   109  
   110  	if c.ProcessConfig.User != "" {
   111  		params = append(params, "-u", c.ProcessConfig.User)
   112  	}
   113  
   114  	if c.ProcessConfig.Privileged {
   115  		if d.apparmor {
   116  			params[0] = path.Join(d.root, "lxc-start-unconfined")
   117  
   118  		}
   119  		params = append(params, "-privileged")
   120  	}
   121  
   122  	if c.WorkingDir != "" {
   123  		params = append(params, "-w", c.WorkingDir)
   124  	}
   125  
   126  	params = append(params, "--", c.ProcessConfig.Entrypoint)
   127  	params = append(params, c.ProcessConfig.Arguments...)
   128  
   129  	if d.sharedRoot {
   130  		// lxc-start really needs / to be non-shared, or all kinds of stuff break
   131  		// when lxc-start unmount things and those unmounts propagate to the main
   132  		// mount namespace.
   133  		// What we really want is to clone into a new namespace and then
   134  		// mount / MS_REC|MS_SLAVE, but since we can't really clone or fork
   135  		// without exec in go we have to do this horrible shell hack...
   136  		shellString :=
   137  			"mount --make-rslave /; exec " +
   138  				utils.ShellQuoteArguments(params)
   139  
   140  		params = []string{
   141  			"unshare", "-m", "--", "/bin/sh", "-c", shellString,
   142  		}
   143  	}
   144  
   145  	var (
   146  		name = params[0]
   147  		arg  = params[1:]
   148  	)
   149  	aname, err := exec.LookPath(name)
   150  	if err != nil {
   151  		aname = name
   152  	}
   153  	c.ProcessConfig.Path = aname
   154  	c.ProcessConfig.Args = append([]string{name}, arg...)
   155  
   156  	if err := nodes.CreateDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil {
   157  		return execdriver.ExitStatus{ExitCode: -1}, err
   158  	}
   159  
   160  	if err := c.ProcessConfig.Start(); err != nil {
   161  		return execdriver.ExitStatus{ExitCode: -1}, err
   162  	}
   163  
   164  	var (
   165  		waitErr  error
   166  		waitLock = make(chan struct{})
   167  	)
   168  
   169  	go func() {
   170  		if err := c.ProcessConfig.Wait(); err != nil {
   171  			if _, ok := err.(*exec.ExitError); !ok { // Do not propagate the error if it's simply a status code != 0
   172  				waitErr = err
   173  			}
   174  		}
   175  		close(waitLock)
   176  	}()
   177  
   178  	// Poll lxc for RUNNING status
   179  	pid, err := d.waitForStart(c, waitLock)
   180  	if err != nil {
   181  		if c.ProcessConfig.Process != nil {
   182  			c.ProcessConfig.Process.Kill()
   183  			c.ProcessConfig.Wait()
   184  		}
   185  		return execdriver.ExitStatus{ExitCode: -1}, err
   186  	}
   187  
   188  	c.ContainerPid = pid
   189  
   190  	if startCallback != nil {
   191  		startCallback(&c.ProcessConfig, pid)
   192  	}
   193  
   194  	<-waitLock
   195  
   196  	return execdriver.ExitStatus{ExitCode: getExitCode(c)}, waitErr
   197  }
   198  
   199  /// Return the exit code of the process
   200  // if the process has not exited -1 will be returned
   201  func getExitCode(c *execdriver.Command) int {
   202  	if c.ProcessConfig.ProcessState == nil {
   203  		return -1
   204  	}
   205  	return c.ProcessConfig.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
   206  }
   207  
   208  func (d *driver) Kill(c *execdriver.Command, sig int) error {
   209  	return KillLxc(c.ID, sig)
   210  }
   211  
   212  func (d *driver) Pause(c *execdriver.Command) error {
   213  	_, err := exec.LookPath("lxc-freeze")
   214  	if err == nil {
   215  		output, errExec := exec.Command("lxc-freeze", "-n", c.ID).CombinedOutput()
   216  		if errExec != nil {
   217  			return fmt.Errorf("Err: %s Output: %s", errExec, output)
   218  		}
   219  	}
   220  
   221  	return err
   222  }
   223  
   224  func (d *driver) Unpause(c *execdriver.Command) error {
   225  	_, err := exec.LookPath("lxc-unfreeze")
   226  	if err == nil {
   227  		output, errExec := exec.Command("lxc-unfreeze", "-n", c.ID).CombinedOutput()
   228  		if errExec != nil {
   229  			return fmt.Errorf("Err: %s Output: %s", errExec, output)
   230  		}
   231  	}
   232  
   233  	return err
   234  }
   235  
   236  func (d *driver) Terminate(c *execdriver.Command) error {
   237  	return KillLxc(c.ID, 9)
   238  }
   239  
   240  func (d *driver) version() string {
   241  	var (
   242  		version string
   243  		output  []byte
   244  		err     error
   245  	)
   246  	if _, errPath := exec.LookPath("lxc-version"); errPath == nil {
   247  		output, err = exec.Command("lxc-version").CombinedOutput()
   248  	} else {
   249  		output, err = exec.Command("lxc-start", "--version").CombinedOutput()
   250  	}
   251  	if err == nil {
   252  		version = strings.TrimSpace(string(output))
   253  		if parts := strings.SplitN(version, ":", 2); len(parts) == 2 {
   254  			version = strings.TrimSpace(parts[1])
   255  		}
   256  	}
   257  	return version
   258  }
   259  
   260  func KillLxc(id string, sig int) error {
   261  	var (
   262  		err    error
   263  		output []byte
   264  	)
   265  	_, err = exec.LookPath("lxc-kill")
   266  	if err == nil {
   267  		output, err = exec.Command("lxc-kill", "-n", id, strconv.Itoa(sig)).CombinedOutput()
   268  	} else {
   269  		output, err = exec.Command("lxc-stop", "-k", "-n", id, strconv.Itoa(sig)).CombinedOutput()
   270  	}
   271  	if err != nil {
   272  		return fmt.Errorf("Err: %s Output: %s", err, output)
   273  	}
   274  	return nil
   275  }
   276  
   277  // wait for the process to start and return the pid for the process
   278  func (d *driver) waitForStart(c *execdriver.Command, waitLock chan struct{}) (int, error) {
   279  	var (
   280  		err    error
   281  		output []byte
   282  	)
   283  	// We wait for the container to be fully running.
   284  	// Timeout after 5 seconds. In case of broken pipe, just retry.
   285  	// Note: The container can run and finish correctly before
   286  	// the end of this loop
   287  	for now := time.Now(); time.Since(now) < 5*time.Second; {
   288  		select {
   289  		case <-waitLock:
   290  			// If the process dies while waiting for it, just return
   291  			return -1, nil
   292  		default:
   293  		}
   294  
   295  		output, err = d.getInfo(c.ID)
   296  		if err == nil {
   297  			info, err := parseLxcInfo(string(output))
   298  			if err != nil {
   299  				return -1, err
   300  			}
   301  			if info.Running {
   302  				return info.Pid, nil
   303  			}
   304  		}
   305  		time.Sleep(50 * time.Millisecond)
   306  	}
   307  	return -1, execdriver.ErrNotRunning
   308  }
   309  
   310  func (d *driver) getInfo(id string) ([]byte, error) {
   311  	return exec.Command("lxc-info", "-n", id).CombinedOutput()
   312  }
   313  
   314  type info struct {
   315  	ID     string
   316  	driver *driver
   317  }
   318  
   319  func (i *info) IsRunning() bool {
   320  	var running bool
   321  
   322  	output, err := i.driver.getInfo(i.ID)
   323  	if err != nil {
   324  		log.Errorf("Error getting info for lxc container %s: %s (%s)", i.ID, err, output)
   325  		return false
   326  	}
   327  	if strings.Contains(string(output), "RUNNING") {
   328  		running = true
   329  	}
   330  	return running
   331  }
   332  
   333  func (d *driver) Info(id string) execdriver.Info {
   334  	return &info{
   335  		ID:     id,
   336  		driver: d,
   337  	}
   338  }
   339  
   340  func (d *driver) GetPidsForContainer(id string) ([]int, error) {
   341  	pids := []int{}
   342  
   343  	// cpu is chosen because it is the only non optional subsystem in cgroups
   344  	subsystem := "cpu"
   345  	cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem)
   346  	if err != nil {
   347  		return pids, err
   348  	}
   349  
   350  	cgroupDir, err := cgroups.GetThisCgroupDir(subsystem)
   351  	if err != nil {
   352  		return pids, err
   353  	}
   354  
   355  	filename := filepath.Join(cgroupRoot, cgroupDir, id, "tasks")
   356  	if _, err := os.Stat(filename); os.IsNotExist(err) {
   357  		// With more recent lxc versions use, cgroup will be in lxc/
   358  		filename = filepath.Join(cgroupRoot, cgroupDir, "lxc", id, "tasks")
   359  	}
   360  
   361  	output, err := ioutil.ReadFile(filename)
   362  	if err != nil {
   363  		return pids, err
   364  	}
   365  	for _, p := range strings.Split(string(output), "\n") {
   366  		if len(p) == 0 {
   367  			continue
   368  		}
   369  		pid, err := strconv.Atoi(p)
   370  		if err != nil {
   371  			return pids, fmt.Errorf("Invalid pid '%s': %s", p, err)
   372  		}
   373  		pids = append(pids, pid)
   374  	}
   375  	return pids, nil
   376  }
   377  
   378  func linkLxcStart(root string) error {
   379  	sourcePath, err := exec.LookPath("lxc-start")
   380  	if err != nil {
   381  		return err
   382  	}
   383  	targetPath := path.Join(root, "lxc-start-unconfined")
   384  
   385  	if _, err := os.Lstat(targetPath); err != nil && !os.IsNotExist(err) {
   386  		return err
   387  	} else if err == nil {
   388  		if err := os.Remove(targetPath); err != nil {
   389  			return err
   390  		}
   391  	}
   392  	return os.Symlink(sourcePath, targetPath)
   393  }
   394  
   395  // TODO: This can be moved to the mountinfo reader in the mount pkg
   396  func rootIsShared() bool {
   397  	if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil {
   398  		for _, line := range strings.Split(string(data), "\n") {
   399  			cols := strings.Split(line, " ")
   400  			if len(cols) >= 6 && cols[4] == "/" {
   401  				return strings.HasPrefix(cols[6], "shared")
   402  			}
   403  		}
   404  	}
   405  
   406  	// No idea, probably safe to assume so
   407  	return true
   408  }
   409  
   410  func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) {
   411  	root := path.Join(d.root, "containers", c.ID, "config.lxc")
   412  
   413  	fo, err := os.Create(root)
   414  	if err != nil {
   415  		return "", err
   416  	}
   417  	defer fo.Close()
   418  
   419  	if err := LxcTemplateCompiled.Execute(fo, struct {
   420  		*execdriver.Command
   421  		AppArmor bool
   422  	}{
   423  		Command:  c,
   424  		AppArmor: d.apparmor,
   425  	}); err != nil {
   426  		return "", err
   427  	}
   428  
   429  	return root, nil
   430  }
   431  
   432  func (d *driver) generateEnvConfig(c *execdriver.Command) error {
   433  	data, err := json.Marshal(c.ProcessConfig.Env)
   434  	if err != nil {
   435  		return err
   436  	}
   437  	p := path.Join(d.root, "containers", c.ID, "config.env")
   438  	c.Mounts = append(c.Mounts, execdriver.Mount{
   439  		Source:      p,
   440  		Destination: "/.dockerenv",
   441  		Writable:    false,
   442  		Private:     true,
   443  	})
   444  
   445  	return ioutil.WriteFile(p, data, 0600)
   446  }
   447  
   448  // Clean not implemented for lxc
   449  func (d *driver) Clean(id string) error {
   450  	return nil
   451  }
   452  
   453  type TtyConsole struct {
   454  	MasterPty *os.File
   455  	SlavePty  *os.File
   456  }
   457  
   458  func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) {
   459  	// lxc is special in that we cannot create the master outside of the container without
   460  	// opening the slave because we have nothing to provide to the cmd.  We have to open both then do
   461  	// the crazy setup on command right now instead of passing the console path to lxc and telling it
   462  	// to open up that console.  we save a couple of openfiles in the native driver because we can do
   463  	// this.
   464  	ptyMaster, ptySlave, err := pty.Open()
   465  	if err != nil {
   466  		return nil, err
   467  	}
   468  
   469  	tty := &TtyConsole{
   470  		MasterPty: ptyMaster,
   471  		SlavePty:  ptySlave,
   472  	}
   473  
   474  	if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil {
   475  		tty.Close()
   476  		return nil, err
   477  	}
   478  
   479  	processConfig.Console = tty.SlavePty.Name()
   480  
   481  	return tty, nil
   482  }
   483  
   484  func (t *TtyConsole) Master() *os.File {
   485  	return t.MasterPty
   486  }
   487  
   488  func (t *TtyConsole) Resize(h, w int) error {
   489  	return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
   490  }
   491  
   492  func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error {
   493  	command.Stdout = t.SlavePty
   494  	command.Stderr = t.SlavePty
   495  
   496  	go func() {
   497  		if wb, ok := pipes.Stdout.(interface {
   498  			CloseWriters() error
   499  		}); ok {
   500  			defer wb.CloseWriters()
   501  		}
   502  
   503  		io.Copy(pipes.Stdout, t.MasterPty)
   504  	}()
   505  
   506  	if pipes.Stdin != nil {
   507  		command.Stdin = t.SlavePty
   508  		command.SysProcAttr.Setctty = true
   509  
   510  		go func() {
   511  			io.Copy(t.MasterPty, pipes.Stdin)
   512  
   513  			pipes.Stdin.Close()
   514  		}()
   515  	}
   516  	return nil
   517  }
   518  
   519  func (t *TtyConsole) Close() error {
   520  	t.SlavePty.Close()
   521  	return t.MasterPty.Close()
   522  }
   523  
   524  func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
   525  	return -1, ErrExec
   526  }
   527  
   528  func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
   529  	return nil, fmt.Errorf("container stats are not supported with LXC")
   530  
   531  }