From 5ecd29c1f20fec04eece5fdb9c47ae0a736645dd Mon Sep 17 00:00:00 2001 From: Andrey Vagin Date: Fri, 19 Dec 2014 12:40:03 +0300 Subject: [PATCH] linux_container: fork an init process in a new set of namespaces Use namespace.Exec() and namespace.Init() to execute processes in CT. Now an init process is actually executed in a new container. This series doesn't change code about creating containers, it only reworks code according with new API. Signed-off-by: Andrey Vagin --- linux_container.go | 10 +++++--- linux_factory.go | 5 ++-- namespaces/exec.go | 61 ++++++++++++++++++++-------------------------- namespaces/init.go | 38 +++++++++++++++++++++++------ nsinit/init.go | 8 +----- 5 files changed, 68 insertions(+), 54 deletions(-) diff --git a/linux_container.go b/linux_container.go index 99cea6335..0a3945b0d 100644 --- a/linux_container.go +++ b/linux_container.go @@ -11,6 +11,7 @@ import ( "syscall" "github.com/docker/libcontainer/configs" + "github.com/docker/libcontainer/namespaces" "github.com/docker/libcontainer/network" "github.com/golang/glog" ) @@ -119,16 +120,17 @@ func (c *linuxContainer) startInitProcess(config *ProcessConfig) error { cmd.SysProcAttr = &syscall.SysProcAttr{} } + cmd.SysProcAttr.Cloneflags = uintptr(namespaces.GetNamespaceFlags(c.config.Namespaces)) cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL - //FIXME call namespaces.Exec() - if err := cmd.Start(); err != nil { + err := namespaces.Exec(config.Args, config.Env, cmd, c.config, c.state) + if err != nil { return err } - c.state.InitPid = cmd.Process.Pid - err := c.updateStateFile() + err = c.updateStateFile() if err != nil { + // FIXME c.Kill() return err } diff --git a/linux_factory.go b/linux_factory.go index 5dc679bdc..ecef9dcd8 100644 --- a/linux_factory.go +++ b/linux_factory.go @@ -12,6 +12,7 @@ import ( "github.com/golang/glog" "github.com/docker/libcontainer/configs" + "github.com/docker/libcontainer/namespaces" ) const ( @@ -164,7 +165,7 @@ func (l *linuxFactory) loadContainerState(root string) (*configs.State, error) { // StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state // This is a low level implementation detail of the reexec and should not be consumed externally func (f *linuxFactory) StartInitialization(pipefd uintptr) (err error) { + pipe := os.NewFile(uintptr(pipefd), "pipe") - /* FIXME call namespaces.Init() */ - return nil + return namespaces.Init(pipe) } diff --git a/namespaces/exec.go b/namespaces/exec.go index 32b9ab126..337d54163 100644 --- a/namespaces/exec.go +++ b/namespaces/exec.go @@ -21,36 +21,45 @@ import ( // Move this to libcontainer package. // Exec performs setup outside of a namespace so that a container can be // executed. Exec is a high level function for working with container namespaces. -func Exec(container *configs.Config, stdin io.Reader, stdout, stderr io.Writer, console, dataPath string, args []string, createCommand CreateCommand, startCallback func()) (int, error) { +func Exec(args []string, env []string, command *exec.Cmd, container *configs.Config, state *configs.State) error { var err error // create a pipe so that we can syncronize with the namespaced process and // pass the state and configuration to the child process parent, child, err := newInitPipe() if err != nil { - return -1, err + return err } defer parent.Close() - command := createCommand(container, console, dataPath, os.Args[0], child, args) - // Note: these are only used in non-tty mode - // if there is a tty for the container it will be opened within the namespace and the - // fds will be duped to stdin, stdiout, and stderr - command.Stdin = stdin - command.Stdout = stdout - command.Stderr = stderr + command.ExtraFiles = []*os.File{child} + command.Dir = container.RootFs if err := command.Start(); err != nil { child.Close() - return -1, err + return err } child.Close() - terminate := func(terr error) (int, error) { + terminate := func(terr error) error { // TODO: log the errors for kill and wait command.Process.Kill() command.Wait() - return -1, terr + return terr + } + + encoder := json.NewEncoder(parent) + + if err := encoder.Encode(container); err != nil { + return terminate(err) + } + + process := processArgs{ + Env: env, + Args: args, + } + if err := encoder.Encode(process); err != nil { + return terminate(err) } started, err := system.GetProcessStartTime(command.Process.Pid) @@ -71,7 +80,7 @@ func Exec(container *configs.Config, stdin io.Reader, stdout, stderr io.Writer, return terminate(err) } // send the state to the container's init process then shutdown writes for the parent - if err := json.NewEncoder(parent).Encode(networkState); err != nil { + if err := encoder.Encode(networkState); err != nil { return terminate(err) } // shutdown writes for the parent side of the pipe @@ -79,18 +88,6 @@ func Exec(container *configs.Config, stdin io.Reader, stdout, stderr io.Writer, return terminate(err) } - state := &configs.State{ - InitPid: command.Process.Pid, - InitStartTime: started, - NetworkState: networkState, - CgroupPaths: cgroupPaths, - } - - if err := configs.SaveState(dataPath, state); err != nil { - return terminate(err) - } - defer configs.DeleteState(dataPath) - // wait for the child process to fully complete and receive an error message // if one was encoutered var ierr *initError @@ -101,16 +98,12 @@ func Exec(container *configs.Config, stdin io.Reader, stdout, stderr io.Writer, return terminate(ierr) } - if startCallback != nil { - startCallback() - } + state.InitPid = command.Process.Pid + state.InitStartTime = started + state.NetworkState = networkState + state.CgroupPaths = cgroupPaths - if err := command.Wait(); err != nil { - if _, ok := err.(*exec.ExitError); !ok { - return -1, err - } - } - return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil + return nil } // DefaultCreateCommand will return an exec.Cmd with the Cloneflags set to the proper namespaces diff --git a/namespaces/init.go b/namespaces/init.go index a6d49f61a..441b3c340 100644 --- a/namespaces/init.go +++ b/namespaces/init.go @@ -24,13 +24,20 @@ import ( "github.com/docker/libcontainer/utils" ) +// Process is used for transferring parameters from Exec() to Init() +type processArgs struct { + Args []string `json:"args,omitempty"` + Env []string `json:"environment,omitempty"` + ConsolePath string `json:"console_path,omitempty"` +} + // TODO(vishh): This is part of the libcontainer API and it does much more than just namespaces related work. // Move this to libcontainer package. // Init is the init process that first runs inside a new namespace to setup mounts, users, networking, // and other options required for the new container. // The caller of Init function has to ensure that the go runtime is locked to an OS thread // (using runtime.LockOSThread) else system calls like setns called within Init may not work as intended. -func Init(container *configs.Config, uncleanRootfs, consolePath string, pipe *os.File, args []string) (err error) { +func Init(pipe *os.File) (err error) { defer func() { // if we have an error during the initialization of the container's init then send it back to the // parent process in the form of an initError. @@ -48,6 +55,23 @@ func Init(container *configs.Config, uncleanRootfs, consolePath string, pipe *os pipe.Close() }() + decoder := json.NewDecoder(pipe) + + var container *configs.Config + if err := decoder.Decode(&container); err != nil { + return err + } + + var process *processArgs + if err := decoder.Decode(&process); err != nil { + return err + } + + uncleanRootfs, err := os.Getwd() + if err != nil { + return err + } + rootfs, err := utils.ResolveRootfs(uncleanRootfs) if err != nil { return err @@ -61,22 +85,22 @@ func Init(container *configs.Config, uncleanRootfs, consolePath string, pipe *os // We always read this as it is a way to sync with the parent as well var networkState *network.NetworkState - if err := json.NewDecoder(pipe).Decode(&networkState); err != nil { + if err := decoder.Decode(&networkState); err != nil { return err } // join any namespaces via a path to the namespace fd if provided if err := joinExistingNamespaces(container.Namespaces); err != nil { return err } - if consolePath != "" { - if err := console.OpenAndDup(consolePath); err != nil { + if process.ConsolePath != "" { + if err := console.OpenAndDup(process.ConsolePath); err != nil { return err } } if _, err := syscall.Setsid(); err != nil { return fmt.Errorf("setsid %s", err) } - if consolePath != "" { + if process.ConsolePath != "" { if err := system.Setctty(); err != nil { return fmt.Errorf("setctty %s", err) } @@ -96,7 +120,7 @@ func Init(container *configs.Config, uncleanRootfs, consolePath string, pipe *os label.Init() if err := mount.InitializeMountNamespace(rootfs, - consolePath, + process.ConsolePath, container.RestrictSys, (*mount.MountConfig)(container.MountConfig)); err != nil { return fmt.Errorf("setup mount namespace %s", err) @@ -138,7 +162,7 @@ func Init(container *configs.Config, uncleanRootfs, consolePath string, pipe *os return fmt.Errorf("restore parent death signal %s", err) } - return system.Execv(args[0], args[0:], os.Environ()) + return system.Execv(process.Args[0], process.Args[0:], process.Env) } // RestoreParentDeathSignal sets the parent death signal to old. diff --git a/nsinit/init.go b/nsinit/init.go index 853c9e8e9..088361390 100644 --- a/nsinit/init.go +++ b/nsinit/init.go @@ -1,9 +1,7 @@ package main import ( - "github.com/docker/libcontainer/system" "log" - "os" "github.com/codegangsta/cli" "github.com/docker/libcontainer" @@ -36,9 +34,5 @@ func initAction(context *cli.Context) { log.Fatal(err) } - args := []string(context.Args()) - - if err := system.Execv(args[0], args[0:], os.Environ()); err != nil { - log.Fatal(err) - } + panic("This line should never been executed") }