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 }