driver.go 12 KB

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