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 <avagin@openvz.org>
This commit is contained in:
Andrey Vagin 2014-12-19 12:40:03 +03:00
parent c406a6b6e0
commit 5ecd29c1f2
5 changed files with 68 additions and 54 deletions

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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.

View File

@ -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")
}