container_linux.go 6.9 KB

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