Files
runc/restore.go
T
Michael Crosby fdb100d247 Destroy container along with processes before stdio
We need to make sure the container is destroyed before closing the stdio
for the container.  This becomes a big issues when running in the host's
pid namespace because the other processes could have inherited the stdio
of the initial process.  The call to close will just block as they still
have the io open.

Calling destroy before closing io, especially in the host pid namespace
will cause all additional processes to be killed in the container's
cgroup.  This will allow the io to be closed successfuly.

This change makes sure the order for destroy and close is correct as
well as ensuring that if any errors encoutered during start or exec will
be handled by terminating the process and destroying the container.  We
cannot use defers here because we need to enforce the correct ordering
on destroy.

This also sets the subreaper setting for runc so that when running in
pid host, runc can wait on the addiontal processes launched by the
container, useful on destroy, but also good for reaping the additional
processes that were launched.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2016-03-15 13:17:11 -07:00

182 lines
4.4 KiB
Go

// +build linux
package main
import (
"os"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/specs/specs-go"
)
var restoreCommand = cli.Command{
Name: "restore",
Usage: "restore a container from a previous checkpoint",
ArgsUsage: `<container-id>
Where "<container-id>" is the name for the instance of the container to be
restored.`,
Description: `Restores the saved state of the container instance that was previously saved
using the runc checkpoint command.`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "image-path",
Value: "",
Usage: "path to criu image files for restoring",
},
cli.StringFlag{
Name: "work-path",
Value: "",
Usage: "path for saving work files and logs",
},
cli.BoolFlag{
Name: "tcp-established",
Usage: "allow open tcp connections",
},
cli.BoolFlag{
Name: "ext-unix-sk",
Usage: "allow external unix sockets",
},
cli.BoolFlag{
Name: "shell-job",
Usage: "allow shell jobs",
},
cli.BoolFlag{
Name: "file-locks",
Usage: "handle file locks, for safety",
},
cli.StringFlag{
Name: "manage-cgroups-mode",
Value: "",
Usage: "cgroups mode: 'soft' (default), 'full' and 'strict'.",
},
cli.StringFlag{
Name: "bundle, b",
Value: "",
Usage: "path to the root of the bundle directory",
},
cli.BoolFlag{
Name: "detach,d",
Usage: "detach from the container's process",
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "specify the file to write the process id to",
},
cli.BoolFlag{
Name: "no-subreaper",
Usage: "disable the use of the subreaper used to reap reparented processes",
},
},
Action: func(context *cli.Context) {
imagePath := context.String("image-path")
id := context.Args().First()
if id == "" {
fatal(errEmptyID)
}
if imagePath == "" {
imagePath = getDefaultImagePath(context)
}
bundle := context.String("bundle")
if bundle != "" {
if err := os.Chdir(bundle); err != nil {
fatal(err)
}
}
spec, err := loadSpec(specConfig)
if err != nil {
fatal(err)
}
config, err := createLibcontainerConfig(id, spec)
if err != nil {
fatal(err)
}
status, err := restoreContainer(context, spec, config, imagePath)
if err != nil {
fatal(err)
}
os.Exit(status)
},
}
func restoreContainer(context *cli.Context, spec *specs.Spec, config *configs.Config, imagePath string) (code int, err error) {
var (
rootuid = 0
id = context.Args().First()
)
factory, err := loadFactory(context)
if err != nil {
return -1, err
}
container, err := factory.Load(id)
if err != nil {
container, err = factory.Create(id, config)
if err != nil {
return -1, err
}
}
options := criuOptions(context)
status, err := container.Status()
if err != nil {
logrus.Error(err)
}
if status == libcontainer.Running {
fatalf("Container with id %s already running", id)
}
setManageCgroupsMode(context, options)
// ensure that the container is always removed if we were the process
// that created it.
detach := context.Bool("detach")
if !detach {
defer destroy(container)
}
process := &libcontainer.Process{}
tty, err := setupIO(process, rootuid, "", false, detach)
if err != nil {
return -1, err
}
defer tty.Close()
handler := newSignalHandler(tty, !context.Bool("no-subreaper"))
if err := container.Restore(process, options); err != nil {
return -1, err
}
if err := tty.ClosePostStart(); err != nil {
return -1, err
}
if pidFile := context.String("pid-file"); pidFile != "" {
if err := createPidFile(pidFile, process); err != nil {
process.Signal(syscall.SIGKILL)
process.Wait()
return -1, err
}
}
if detach {
return 0, nil
}
return handler.forward(process)
}
func criuOptions(context *cli.Context) *libcontainer.CriuOpts {
imagePath := getCheckpointImagePath(context)
if err := os.MkdirAll(imagePath, 0655); err != nil {
fatal(err)
}
return &libcontainer.CriuOpts{
ImagesDirectory: imagePath,
WorkDirectory: context.String("work-path"),
LeaveRunning: context.Bool("leave-running"),
TcpEstablished: context.Bool("tcp-established"),
ExternalUnixConnections: context.Bool("ext-unix-sk"),
ShellJob: context.Bool("shell-job"),
FileLocks: context.Bool("file-locks"),
}
}