123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- package libcontainerd
- import (
- "encoding/json"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "syscall"
- "time"
- "github.com/Sirupsen/logrus"
- containerd "github.com/docker/containerd/api/grpc/types"
- "github.com/docker/docker/restartmanager"
- "github.com/opencontainers/specs/specs-go"
- "golang.org/x/net/context"
- )
- type container struct {
- containerCommon
- // Platform specific fields are below here.
- pauseMonitor
- oom bool
- runtime string
- runtimeArgs []string
- }
- type runtime struct {
- path string
- args []string
- }
- // WithRuntime sets the runtime to be used for the created container
- func WithRuntime(path string, args []string) CreateOption {
- return runtime{path, args}
- }
- func (rt runtime) Apply(p interface{}) error {
- if pr, ok := p.(*container); ok {
- pr.runtime = rt.path
- pr.runtimeArgs = rt.args
- }
- return nil
- }
- func (ctr *container) clean() error {
- if os.Getenv("LIBCONTAINERD_NOCLEAN") == "1" {
- return nil
- }
- if _, err := os.Lstat(ctr.dir); err != nil {
- if os.IsNotExist(err) {
- return nil
- }
- return err
- }
- if err := os.RemoveAll(ctr.dir); err != nil {
- return err
- }
- return nil
- }
- // cleanProcess removes the fifos used by an additional process.
- // Caller needs to lock container ID before calling this method.
- func (ctr *container) cleanProcess(id string) {
- if p, ok := ctr.processes[id]; ok {
- for _, i := range []int{syscall.Stdin, syscall.Stdout, syscall.Stderr} {
- if err := os.Remove(p.fifo(i)); err != nil {
- logrus.Warnf("libcontainerd: failed to remove %v for process %v: %v", p.fifo(i), id, err)
- }
- }
- }
- delete(ctr.processes, id)
- }
- func (ctr *container) spec() (*specs.Spec, error) {
- var spec specs.Spec
- dt, err := ioutil.ReadFile(filepath.Join(ctr.dir, configFilename))
- if err != nil {
- return nil, err
- }
- if err := json.Unmarshal(dt, &spec); err != nil {
- return nil, err
- }
- return &spec, nil
- }
- func (ctr *container) start() error {
- spec, err := ctr.spec()
- if err != nil {
- return nil
- }
- iopipe, err := ctr.openFifos(spec.Process.Terminal)
- if err != nil {
- return err
- }
- r := &containerd.CreateContainerRequest{
- Id: ctr.containerID,
- BundlePath: ctr.dir,
- Stdin: ctr.fifo(syscall.Stdin),
- Stdout: ctr.fifo(syscall.Stdout),
- Stderr: ctr.fifo(syscall.Stderr),
- // check to see if we are running in ramdisk to disable pivot root
- NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
- Runtime: ctr.runtime,
- RuntimeArgs: ctr.runtimeArgs,
- }
- ctr.client.appendContainer(ctr)
- resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r)
- if err != nil {
- ctr.closeFifos(iopipe)
- return err
- }
- ctr.startedAt = time.Now()
- if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil {
- return err
- }
- ctr.systemPid = systemPid(resp.Container)
- return ctr.client.backend.StateChanged(ctr.containerID, StateInfo{
- CommonStateInfo: CommonStateInfo{
- State: StateStart,
- Pid: ctr.systemPid,
- }})
- }
- func (ctr *container) newProcess(friendlyName string) *process {
- return &process{
- dir: ctr.dir,
- processCommon: processCommon{
- containerID: ctr.containerID,
- friendlyName: friendlyName,
- client: ctr.client,
- },
- }
- }
- func (ctr *container) handleEvent(e *containerd.Event) error {
- ctr.client.lock(ctr.containerID)
- defer ctr.client.unlock(ctr.containerID)
- switch e.Type {
- case StateExit, StatePause, StateResume, StateOOM:
- st := StateInfo{
- CommonStateInfo: CommonStateInfo{
- State: e.Type,
- ExitCode: e.Status,
- },
- OOMKilled: e.Type == StateExit && ctr.oom,
- }
- if e.Type == StateOOM {
- ctr.oom = true
- }
- if e.Type == StateExit && e.Pid != InitFriendlyName {
- st.ProcessID = e.Pid
- st.State = StateExitProcess
- }
- if st.State == StateExit && ctr.restartManager != nil {
- restart, wait, err := ctr.restartManager.ShouldRestart(e.Status, false, time.Since(ctr.startedAt))
- if err != nil {
- logrus.Warnf("libcontainerd: container %s %v", ctr.containerID, err)
- } else if restart {
- st.State = StateRestart
- ctr.restarting = true
- ctr.client.deleteContainer(e.Id)
- go func() {
- err := <-wait
- ctr.client.lock(ctr.containerID)
- defer ctr.client.unlock(ctr.containerID)
- ctr.restarting = false
- if err != nil {
- st.State = StateExit
- ctr.clean()
- ctr.client.q.append(e.Id, func() {
- if err := ctr.client.backend.StateChanged(e.Id, st); err != nil {
- logrus.Errorf("libcontainerd: %v", err)
- }
- })
- if err != restartmanager.ErrRestartCanceled {
- logrus.Errorf("libcontainerd: %v", err)
- }
- } else {
- ctr.start()
- }
- }()
- }
- }
- // Remove process from list if we have exited
- // We need to do so here in case the Message Handler decides to restart it.
- switch st.State {
- case StateExit:
- ctr.clean()
- ctr.client.deleteContainer(e.Id)
- case StateExitProcess:
- ctr.cleanProcess(st.ProcessID)
- }
- ctr.client.q.append(e.Id, func() {
- if err := ctr.client.backend.StateChanged(e.Id, st); err != nil {
- logrus.Errorf("libcontainerd: backend.StateChanged(): %v", err)
- }
- if e.Type == StatePause || e.Type == StateResume {
- ctr.pauseMonitor.handle(e.Type)
- }
- if e.Type == StateExit {
- if en := ctr.client.getExitNotifier(e.Id); en != nil {
- en.close()
- }
- }
- })
- default:
- logrus.Debugf("libcontainerd: event unhandled: %+v", e)
- }
- return nil
- }
- // discardFifos attempts to fully read the container fifos to unblock processes
- // that may be blocked on the writer side.
- func (ctr *container) discardFifos() {
- for _, i := range []int{syscall.Stdout, syscall.Stderr} {
- f := ctr.fifo(i)
- c := make(chan struct{})
- go func() {
- r := openReaderFromFifo(f)
- close(c) // this channel is used to not close the writer too early, before readonly open has been called.
- io.Copy(ioutil.Discard, r)
- }()
- <-c
- closeReaderFifo(f) // avoid blocking permanently on open if there is no writer side
- }
- }
|