mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
8d588d9c5b
During container startup we end up spending a fair amount of time encoding/decoding json. This cuts out some of that since we already have the decoded object in memory. The old flow looked like: 1. Start container request 2. Create file 3. Encode container spec to json 4. Write to file 5. Close file 6. Open file 7. Read file 8. Decode container spec 9. Close file 10. Send to containerd. The new flow cuts out steps 6-9 completely, and with it a lot of time spent in reflect and file IO. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
246 lines
5.9 KiB
Go
246 lines
5.9 KiB
Go
// +build linux solaris
|
|
|
|
package libcontainerd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
containerd "github.com/containerd/containerd/api/grpc/types"
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/tonistiigi/fifo"
|
|
"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 && !os.IsNotExist(err) {
|
|
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(spec *specs.Spec, checkpoint, checkpointDir string, attachStdio StdioCallback) (err error) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ready := make(chan struct{})
|
|
|
|
fifoCtx, cancel := context.WithCancel(context.Background())
|
|
defer func() {
|
|
if err != nil {
|
|
cancel()
|
|
}
|
|
}()
|
|
|
|
iopipe, err := ctr.openFifos(fifoCtx, spec.Process.Terminal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var stdinOnce sync.Once
|
|
|
|
// we need to delay stdin closure after container start or else "stdin close"
|
|
// event will be rejected by containerd.
|
|
// stdin closure happens in attachStdio
|
|
stdin := iopipe.Stdin
|
|
iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error {
|
|
var err error
|
|
stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed
|
|
err = stdin.Close()
|
|
go func() {
|
|
select {
|
|
case <-ready:
|
|
case <-ctx.Done():
|
|
}
|
|
select {
|
|
case <-ready:
|
|
if err := ctr.sendCloseStdin(); err != nil {
|
|
logrus.Warnf("failed to close stdin: %+v", err)
|
|
}
|
|
default:
|
|
}
|
|
}()
|
|
})
|
|
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),
|
|
Checkpoint: checkpoint,
|
|
CheckpointDir: checkpointDir,
|
|
// 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)
|
|
|
|
if err := attachStdio(*iopipe); err != nil {
|
|
ctr.closeFifos(iopipe)
|
|
return err
|
|
}
|
|
|
|
resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r)
|
|
if err != nil {
|
|
ctr.closeFifos(iopipe)
|
|
return err
|
|
}
|
|
ctr.systemPid = systemPid(resp.Container)
|
|
close(ready)
|
|
|
|
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
|
|
}
|
|
|
|
// Remove process from list if we have exited
|
|
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() {
|
|
ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
|
|
for _, i := range []int{syscall.Stdout, syscall.Stderr} {
|
|
f, err := fifo.OpenFifo(ctx, ctr.fifo(i), syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
|
|
if err != nil {
|
|
logrus.Warnf("error opening fifo %v for discarding: %+v", f, err)
|
|
continue
|
|
}
|
|
go func() {
|
|
io.Copy(ioutil.Discard, f)
|
|
}()
|
|
}
|
|
}
|