container_linux.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package libcontainerd
  2. import (
  3. "encoding/json"
  4. "io"
  5. "io/ioutil"
  6. "os"
  7. "path/filepath"
  8. "sync"
  9. "syscall"
  10. "time"
  11. "github.com/Sirupsen/logrus"
  12. containerd "github.com/docker/containerd/api/grpc/types"
  13. "github.com/docker/docker/pkg/ioutils"
  14. "github.com/opencontainers/runtime-spec/specs-go"
  15. "github.com/tonistiigi/fifo"
  16. "golang.org/x/net/context"
  17. )
  18. type container struct {
  19. containerCommon
  20. // Platform specific fields are below here.
  21. pauseMonitor
  22. oom bool
  23. runtime string
  24. runtimeArgs []string
  25. }
  26. type runtime struct {
  27. path string
  28. args []string
  29. }
  30. // WithRuntime sets the runtime to be used for the created container
  31. func WithRuntime(path string, args []string) CreateOption {
  32. return runtime{path, args}
  33. }
  34. func (rt runtime) Apply(p interface{}) error {
  35. if pr, ok := p.(*container); ok {
  36. pr.runtime = rt.path
  37. pr.runtimeArgs = rt.args
  38. }
  39. return nil
  40. }
  41. func (ctr *container) clean() error {
  42. if os.Getenv("LIBCONTAINERD_NOCLEAN") == "1" {
  43. return nil
  44. }
  45. if _, err := os.Lstat(ctr.dir); err != nil {
  46. if os.IsNotExist(err) {
  47. return nil
  48. }
  49. return err
  50. }
  51. if err := os.RemoveAll(ctr.dir); err != nil {
  52. return err
  53. }
  54. return nil
  55. }
  56. // cleanProcess removes the fifos used by an additional process.
  57. // Caller needs to lock container ID before calling this method.
  58. func (ctr *container) cleanProcess(id string) {
  59. if p, ok := ctr.processes[id]; ok {
  60. for _, i := range []int{syscall.Stdin, syscall.Stdout, syscall.Stderr} {
  61. if err := os.Remove(p.fifo(i)); err != nil {
  62. logrus.Warnf("libcontainerd: failed to remove %v for process %v: %v", p.fifo(i), id, err)
  63. }
  64. }
  65. }
  66. delete(ctr.processes, id)
  67. }
  68. func (ctr *container) spec() (*specs.Spec, error) {
  69. var spec specs.Spec
  70. dt, err := ioutil.ReadFile(filepath.Join(ctr.dir, configFilename))
  71. if err != nil {
  72. return nil, err
  73. }
  74. if err := json.Unmarshal(dt, &spec); err != nil {
  75. return nil, err
  76. }
  77. return &spec, nil
  78. }
  79. func (ctr *container) start(checkpoint string, checkpointDir string, attachStdio StdioCallback) error {
  80. spec, err := ctr.spec()
  81. if err != nil {
  82. return nil
  83. }
  84. ctx, cancel := context.WithCancel(context.Background())
  85. defer cancel()
  86. ready := make(chan struct{})
  87. iopipe, err := ctr.openFifos(spec.Process.Terminal)
  88. if err != nil {
  89. return err
  90. }
  91. var stdinOnce sync.Once
  92. // we need to delay stdin closure after container start or else "stdin close"
  93. // event will be rejected by containerd.
  94. // stdin closure happens in attachStdio
  95. stdin := iopipe.Stdin
  96. iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error {
  97. var err error
  98. stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed
  99. err = stdin.Close()
  100. go func() {
  101. select {
  102. case <-ready:
  103. if err := ctr.sendCloseStdin(); err != nil {
  104. logrus.Warnf("failed to close stdin: %+v", err)
  105. }
  106. case <-ctx.Done():
  107. }
  108. }()
  109. })
  110. return err
  111. })
  112. r := &containerd.CreateContainerRequest{
  113. Id: ctr.containerID,
  114. BundlePath: ctr.dir,
  115. Stdin: ctr.fifo(syscall.Stdin),
  116. Stdout: ctr.fifo(syscall.Stdout),
  117. Stderr: ctr.fifo(syscall.Stderr),
  118. Checkpoint: checkpoint,
  119. CheckpointDir: checkpointDir,
  120. // check to see if we are running in ramdisk to disable pivot root
  121. NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
  122. Runtime: ctr.runtime,
  123. RuntimeArgs: ctr.runtimeArgs,
  124. }
  125. ctr.client.appendContainer(ctr)
  126. if err := attachStdio(*iopipe); err != nil {
  127. ctr.closeFifos(iopipe)
  128. return err
  129. }
  130. resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r)
  131. if err != nil {
  132. ctr.closeFifos(iopipe)
  133. return err
  134. }
  135. ctr.systemPid = systemPid(resp.Container)
  136. close(ready)
  137. return ctr.client.backend.StateChanged(ctr.containerID, StateInfo{
  138. CommonStateInfo: CommonStateInfo{
  139. State: StateStart,
  140. Pid: ctr.systemPid,
  141. }})
  142. }
  143. func (ctr *container) newProcess(friendlyName string) *process {
  144. return &process{
  145. dir: ctr.dir,
  146. processCommon: processCommon{
  147. containerID: ctr.containerID,
  148. friendlyName: friendlyName,
  149. client: ctr.client,
  150. },
  151. }
  152. }
  153. func (ctr *container) handleEvent(e *containerd.Event) error {
  154. ctr.client.lock(ctr.containerID)
  155. defer ctr.client.unlock(ctr.containerID)
  156. switch e.Type {
  157. case StateExit, StatePause, StateResume, StateOOM:
  158. st := StateInfo{
  159. CommonStateInfo: CommonStateInfo{
  160. State: e.Type,
  161. ExitCode: e.Status,
  162. },
  163. OOMKilled: e.Type == StateExit && ctr.oom,
  164. }
  165. if e.Type == StateOOM {
  166. ctr.oom = true
  167. }
  168. if e.Type == StateExit && e.Pid != InitFriendlyName {
  169. st.ProcessID = e.Pid
  170. st.State = StateExitProcess
  171. }
  172. // Remove process from list if we have exited
  173. switch st.State {
  174. case StateExit:
  175. ctr.clean()
  176. ctr.client.deleteContainer(e.Id)
  177. case StateExitProcess:
  178. ctr.cleanProcess(st.ProcessID)
  179. }
  180. ctr.client.q.append(e.Id, func() {
  181. if err := ctr.client.backend.StateChanged(e.Id, st); err != nil {
  182. logrus.Errorf("libcontainerd: backend.StateChanged(): %v", err)
  183. }
  184. if e.Type == StatePause || e.Type == StateResume {
  185. ctr.pauseMonitor.handle(e.Type)
  186. }
  187. if e.Type == StateExit {
  188. if en := ctr.client.getExitNotifier(e.Id); en != nil {
  189. en.close()
  190. }
  191. }
  192. })
  193. default:
  194. logrus.Debugf("libcontainerd: event unhandled: %+v", e)
  195. }
  196. return nil
  197. }
  198. // discardFifos attempts to fully read the container fifos to unblock processes
  199. // that may be blocked on the writer side.
  200. func (ctr *container) discardFifos() {
  201. ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
  202. for _, i := range []int{syscall.Stdout, syscall.Stderr} {
  203. f, err := fifo.OpenFifo(ctx, ctr.fifo(i), syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
  204. if err != nil {
  205. logrus.Warnf("error opening fifo %v for discarding: %+v", f, err)
  206. continue
  207. }
  208. go func() {
  209. io.Copy(ioutil.Discard, f)
  210. }()
  211. }
  212. }