From 9b1705598c3a5362c875924ce99f100384bbc0e8 Mon Sep 17 00:00:00 2001 From: Ivan Prisyazhnyy Date: Wed, 15 Mar 2023 17:01:00 +0100 Subject: [PATCH] runsc/restore: must be able to restore to prepared container runsc restore must be able to restore into a prepared container to allow the user to configure container specific namespaces as otherwise there is no other means to override different bundle parameters and especially namespaces configuration. Specifically, otherwise, it is impossible to configure networking other than by cloning the bundle config and preparing special netns for the container instead of just configuring it on the fly without whole management complexity. This fix allows to do the following: - runsc create - configure container netns (even with the netlink api) - restore into the container with nice networking It also fixes the restore command to behave exactly as it is stated in the documentation: runsc create runsc restore --image-path= at https://github.com/google/gvisor/blob/master/g3doc/user_guide/checkpoint_restore.md Generally, user shall not try to do a checkpoint restore into the void. If container is not found, we keep back compatibility, but print a warning. Signed-off-by: Ivan Prisyazhnyy --- runsc/cmd/BUILD | 1 + runsc/cmd/restore.go | 65 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD index 96e81a4ae..e0f5a015d 100644 --- a/runsc/cmd/BUILD +++ b/runsc/cmd/BUILD @@ -53,6 +53,7 @@ go_library( deps = [ "//pkg/abi/linux", "//pkg/atomicbitops", + "//pkg/cleanup", "//pkg/coretag", "//pkg/coverage", "//pkg/cpuid", diff --git a/runsc/cmd/restore.go b/runsc/cmd/restore.go index a58c3f865..604523c4e 100644 --- a/runsc/cmd/restore.go +++ b/runsc/cmd/restore.go @@ -16,10 +16,13 @@ package cmd import ( "context" + "os" "path/filepath" "github.com/google/subcommands" "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/cleanup" + "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/runsc/cmd/util" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/container" @@ -89,32 +92,76 @@ func (r *Restore) Execute(_ context.Context, f *flag.FlagSet, args ...any) subco if bundleDir == "" { bundleDir = getwdOrDie() } - spec, err := specutils.ReadSpec(bundleDir, conf) - if err != nil { - return util.Errorf("reading spec: %v", err) - } - specutils.LogSpecDebug(spec, conf.OCISeccomp) - if r.imagePath == "" { return util.Errorf("image-path flag must be provided") } + var cu cleanup.Cleanup + defer cu.Clean() + conf.RestoreFile = filepath.Join(r.imagePath, checkpointFileName) runArgs := container.Args{ ID: id, - Spec: spec, + Spec: nil, BundleDir: bundleDir, ConsoleSocket: r.consoleSocket, PIDFile: r.pidFile, UserLog: r.userLog, Attached: !r.detach, } - ws, err := container.Run(conf, runArgs) + + log.Debugf("Restore container, cid: %s, rootDir: %q", id, conf.RootDir) + c, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{}) if err != nil { - return util.Errorf("running container: %v", err) + if err != os.ErrNotExist { + return util.Errorf("loading container: %v", err) + } + + log.Warningf("Container not found, creating new one, cid: %s, spec from: %s", id, bundleDir) + + // Read the spec again here to ensure flag annotations from the spec are + // applied to "conf". + if runArgs.Spec, err = specutils.ReadSpec(bundleDir, conf); err != nil { + return util.Errorf("reading spec: %v", err) + } + specutils.LogSpecDebug(runArgs.Spec, conf.OCISeccomp) + + if c, err = container.New(conf, runArgs); err != nil { + return util.Errorf("creating container: %v", err) + } + + // Clean up partially created container if an error occurs. + // Any errors returned by Destroy() itself are ignored. + cu.Add(func() { + c.Destroy() + }) + } else { + runArgs.Spec = c.Spec + } + + log.Debugf("Restore: %v", conf.RestoreFile) + if err := c.Restore(runArgs.Spec, conf, conf.RestoreFile); err != nil { + return util.Errorf("starting container: %v", err) + } + + // If we allocate a terminal, forward signals to the sandbox process. + // Otherwise, Ctrl+C will terminate this process and its children, + // including the terminal. + if c.Spec.Process.Terminal { + stopForwarding := c.ForwardSignals(0, true /* fgProcess */) + defer stopForwarding() + } + + var ws unix.WaitStatus + if runArgs.Attached { + if ws, err = c.Wait(); err != nil { + return util.Errorf("running container: %v", err) + } } *waitStatus = ws + cu.Release() + return subcommands.ExitSuccess }