Merge pull request #311 from avagin/api-linux
new-api: execute a process inside an existing container
This commit is contained in:
commit
a7ab930d8d
|
@ -34,7 +34,22 @@ func (c *linuxContainer) Config() *configs.Config {
|
|||
}
|
||||
|
||||
func (c *linuxContainer) RunState() (configs.RunState, error) {
|
||||
return configs.Destroyed, nil // FIXME return a real state
|
||||
if c.state.InitPid <= 0 {
|
||||
return configs.Destroyed, nil
|
||||
}
|
||||
|
||||
// return Running if the init process is alive
|
||||
err := syscall.Kill(c.state.InitPid, 0)
|
||||
if err != nil {
|
||||
if err == syscall.ESRCH {
|
||||
return configs.Destroyed, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
//FIXME get a cgroup state to check other states
|
||||
|
||||
return configs.Running, nil
|
||||
}
|
||||
|
||||
func (c *linuxContainer) Processes() ([]int, error) {
|
||||
|
@ -62,18 +77,32 @@ func (c *linuxContainer) Stats() (*ContainerStats, error) {
|
|||
return stats, nil
|
||||
}
|
||||
|
||||
func (c *linuxContainer) StartProcess(pconfig *ProcessConfig) (int, error) {
|
||||
func (c *linuxContainer) StartProcess(config *ProcessConfig) (int, error) {
|
||||
state, err := c.RunState()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if state != configs.Destroyed {
|
||||
glog.Info("start new container process")
|
||||
panic("not implemented")
|
||||
cmd := exec.Command(c.initArgs[0], c.initArgs[1:]...)
|
||||
cmd.Stdin = config.Stdin
|
||||
cmd.Stdout = config.Stdout
|
||||
cmd.Stderr = config.Stderr
|
||||
|
||||
cmd.Env = config.Env
|
||||
cmd.Dir = c.config.RootFs
|
||||
|
||||
if cmd.SysProcAttr == nil {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
|
||||
if err := c.startInitProcess(pconfig); err != nil {
|
||||
cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL
|
||||
|
||||
if state != configs.Destroyed {
|
||||
glog.Info("start new container process")
|
||||
return namespaces.ExecIn(config.Args, config.Env, cmd, c.config, c.state)
|
||||
}
|
||||
|
||||
if err := c.startInitProcess(cmd, config); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
|
@ -103,22 +132,7 @@ func (c *linuxContainer) updateStateFile() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *linuxContainer) startInitProcess(config *ProcessConfig) error {
|
||||
cmd := exec.Command(c.initArgs[0], append(c.initArgs[1:], config.Args...)...)
|
||||
cmd.Stdin = config.Stdin
|
||||
cmd.Stdout = config.Stdout
|
||||
cmd.Stderr = config.Stderr
|
||||
|
||||
cmd.Env = config.Env
|
||||
cmd.Dir = c.config.RootFs
|
||||
|
||||
if cmd.SysProcAttr == nil {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
|
||||
cmd.SysProcAttr.Cloneflags = uintptr(namespaces.GetNamespaceFlags(c.config.Namespaces))
|
||||
cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL
|
||||
|
||||
func (c *linuxContainer) startInitProcess(cmd *exec.Cmd, config *ProcessConfig) error {
|
||||
err := namespaces.Exec(config.Args, config.Env, cmd, c.config, c.state)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -167,5 +167,10 @@ func (l *linuxFactory) loadContainerState(root string) (*configs.State, error) {
|
|||
func (f *linuxFactory) StartInitialization(pipefd uintptr) (err error) {
|
||||
pipe := os.NewFile(uintptr(pipefd), "pipe")
|
||||
|
||||
pid := os.Getenv("_LIBCONTAINER_INITPID")
|
||||
if pid != "" {
|
||||
return namespaces.InitIn(pipe)
|
||||
}
|
||||
|
||||
return namespaces.Init(pipe)
|
||||
}
|
||||
|
|
|
@ -31,9 +31,10 @@ func Exec(args []string, env []string, command *exec.Cmd, container *configs.Con
|
|||
return err
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
command.ExtraFiles = []*os.File{child}
|
||||
|
||||
command.Dir = container.RootFs
|
||||
command.SysProcAttr.Cloneflags = uintptr(GetNamespaceFlags(container.Namespaces))
|
||||
|
||||
if err := command.Start(); err != nil {
|
||||
child.Close()
|
||||
|
|
|
@ -5,12 +5,9 @@ package namespaces
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer/apparmor"
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
|
@ -19,27 +16,10 @@ import (
|
|||
"github.com/docker/libcontainer/system"
|
||||
)
|
||||
|
||||
// ExecIn reexec's the initPath with the argv 0 rewrite to "nsenter" so that it is able to run the
|
||||
// ExecIn reexec's cmd with _LIBCONTAINER_INITPID=PID so that it is able to run the
|
||||
// setns code in a single threaded environment joining the existing containers' namespaces.
|
||||
func ExecIn(container *configs.Config, state *configs.State, userArgs []string, initPath, action string,
|
||||
stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) {
|
||||
|
||||
args := []string{fmt.Sprintf("nsenter-%s", action), "--nspid", strconv.Itoa(state.InitPid)}
|
||||
|
||||
if console != "" {
|
||||
args = append(args, "--console", console)
|
||||
}
|
||||
|
||||
cmd := &exec.Cmd{
|
||||
Path: initPath,
|
||||
Args: append(args, append([]string{"--"}, userArgs...)...),
|
||||
}
|
||||
|
||||
if filepath.Base(initPath) == initPath {
|
||||
if lp, err := exec.LookPath(initPath); err == nil {
|
||||
cmd.Path = lp
|
||||
}
|
||||
}
|
||||
func ExecIn(args []string, env []string, cmd *exec.Cmd, container *configs.Config, state *configs.State) (int, error) {
|
||||
var err error
|
||||
|
||||
parent, child, err := newInitPipe()
|
||||
if err != nil {
|
||||
|
@ -47,13 +27,8 @@ func ExecIn(container *configs.Config, state *configs.State, userArgs []string,
|
|||
}
|
||||
defer parent.Close()
|
||||
|
||||
// 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
|
||||
cmd.Stdin = stdin
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
cmd.ExtraFiles = []*os.File{child}
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_INITPID=%d", state.InitPid))
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
child.Close()
|
||||
|
@ -68,6 +43,20 @@ func ExecIn(container *configs.Config, state *configs.State, userArgs []string,
|
|||
return -1, terr
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(parent)
|
||||
|
||||
if err := encoder.Encode(container); err != nil {
|
||||
return terminate(err)
|
||||
}
|
||||
|
||||
process := processArgs{
|
||||
Env: append(env[0:], container.Env...),
|
||||
Args: args,
|
||||
}
|
||||
if err := encoder.Encode(process); err != nil {
|
||||
return terminate(err)
|
||||
}
|
||||
|
||||
// Enter cgroups.
|
||||
if err := EnterCgroups(state, cmd.Process.Pid); err != nil {
|
||||
return terminate(err)
|
||||
|
@ -77,21 +66,54 @@ func ExecIn(container *configs.Config, state *configs.State, userArgs []string,
|
|||
return terminate(err)
|
||||
}
|
||||
|
||||
if startCallback != nil {
|
||||
startCallback(cmd)
|
||||
return cmd.Process.Pid, nil
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if _, ok := err.(*exec.ExitError); !ok {
|
||||
return -1, err
|
||||
// Finalize entering into a container and execute a specified command
|
||||
func InitIn(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.
|
||||
if err != nil {
|
||||
// ensure that any data sent from the parent is consumed so it doesn't
|
||||
// receive ECONNRESET when the child writes to the pipe.
|
||||
ioutil.ReadAll(pipe)
|
||||
if err := json.NewEncoder(pipe).Encode(initError{
|
||||
Message: err.Error(),
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil
|
||||
// ensure that this pipe is always closed
|
||||
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
|
||||
}
|
||||
|
||||
if err := FinalizeSetns(container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := system.Execv(process.Args[0], process.Args[0:], process.Env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Finalize expects that the setns calls have been setup and that is has joined an
|
||||
// existing namespace
|
||||
func FinalizeSetns(container *configs.Config, args []string) error {
|
||||
func FinalizeSetns(container *configs.Config) error {
|
||||
// clear the current processes env and replace it with the environment defined on the container
|
||||
if err := LoadContainerEnvironment(container); err != nil {
|
||||
return err
|
||||
|
@ -111,11 +133,7 @@ func FinalizeSetns(container *configs.Config, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if err := system.Execv(args[0], args[0:], os.Environ()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnterCgroups(state *configs.State, pid int) error {
|
||||
|
|
|
@ -5,6 +5,7 @@ package nsenter
|
|||
/*
|
||||
__attribute__((constructor)) init() {
|
||||
nsenter();
|
||||
nsexec();
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <linux/limits.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
|
||||
// Use raw setns syscall for versions of glibc that don't include it (namely glibc-2.12)
|
||||
#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 14
|
||||
#define _GNU_SOURCE
|
||||
#include <sched.h>
|
||||
#include "syscall.h"
|
||||
#ifdef SYS_setns
|
||||
int setns(int fd, int nstype)
|
||||
{
|
||||
return syscall(SYS_setns, fd, nstype);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void nsexec()
|
||||
{
|
||||
char *namespaces[] = { "ipc", "uts", "net", "pid", "mnt" };
|
||||
const int num = sizeof(namespaces) / sizeof(char *);
|
||||
char buf[PATH_MAX], *val;
|
||||
int child, i, tfd;
|
||||
pid_t pid;
|
||||
|
||||
val = getenv("_LIBCONTAINER_INITPID");
|
||||
if (val == NULL)
|
||||
return;
|
||||
|
||||
pid = atoi(val);
|
||||
snprintf(buf, sizeof(buf), "%d", pid);
|
||||
if (strcmp(val, buf)) {
|
||||
fprintf(stderr, "Unable to parse _LIBCONTAINER_INITPID");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Check that the specified process exists */
|
||||
snprintf(buf, PATH_MAX - 1, "/proc/%d/ns", pid);
|
||||
tfd = open(buf, O_DIRECTORY | O_RDONLY);
|
||||
if (tfd == -1) {
|
||||
fprintf(stderr,
|
||||
"nsenter: Failed to open \"%s\" with error: \"%s\"\n",
|
||||
buf, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
struct stat st;
|
||||
int fd;
|
||||
|
||||
/* Symlinks on all namespaces exist for dead processes, but they can't be opened */
|
||||
if (fstatat(tfd, namespaces[i], &st, AT_SYMLINK_NOFOLLOW) == -1) {
|
||||
// Ignore nonexistent namespaces.
|
||||
if (errno == ENOENT)
|
||||
continue;
|
||||
}
|
||||
|
||||
fd = openat(tfd, namespaces[i], O_RDONLY);
|
||||
if (fd == -1) {
|
||||
fprintf(stderr,
|
||||
"nsenter: Failed to open ns file \"%s\" for ns \"%s\" with error: \"%s\"\n",
|
||||
buf, namespaces[i], strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
// Set the namespace.
|
||||
if (setns(fd, 0) == -1) {
|
||||
fprintf(stderr,
|
||||
"nsenter: Failed to setns for \"%s\" with error: \"%s\"\n",
|
||||
namespaces[i], strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
child = fork();
|
||||
if (child < 0) {
|
||||
fprintf(stderr, "Unable to fork: %s", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
// We must fork to actually enter the PID namespace.
|
||||
if (child == 0) {
|
||||
// Finish executing, let the Go runtime take over.
|
||||
return;
|
||||
} else {
|
||||
// Parent, wait for the child.
|
||||
int status = 0;
|
||||
if (waitpid(child, &status, 0) == -1) {
|
||||
fprintf(stderr,
|
||||
"nsenter: Failed to waitpid with error: \"%s\"\n",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
// Forward the child's exit code or re-send its death signal.
|
||||
if (WIFEXITED(status)) {
|
||||
exit(WEXITSTATUS(status));
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
kill(getpid(), WTERMSIG(status));
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/libcontainer"
|
||||
|
@ -30,19 +29,6 @@ var execCommand = cli.Command{
|
|||
}
|
||||
|
||||
func execAction(context *cli.Context) {
|
||||
if context.Bool("list") {
|
||||
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
|
||||
fmt.Fprint(w, "NAME\tUSAGE\n")
|
||||
|
||||
for k, f := range argvs {
|
||||
fmt.Fprintf(w, "%s\t%s\n", k, f.Usage)
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var exitCode int
|
||||
|
||||
process := &libcontainer.ProcessConfig{
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/libcontainer"
|
||||
_ "github.com/docker/libcontainer/namespaces/nsenter"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -3,43 +3,15 @@ package main
|
|||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
logPath = os.Getenv("log")
|
||||
argvs = make(map[string]*rFunc)
|
||||
)
|
||||
|
||||
func init() {
|
||||
argvs["exec"] = &rFunc{
|
||||
Usage: "execute a process inside an existing container",
|
||||
Action: nsenterExec,
|
||||
}
|
||||
|
||||
argvs["mknod"] = &rFunc{
|
||||
Usage: "mknod a device inside an existing container",
|
||||
Action: nsenterMknod,
|
||||
}
|
||||
|
||||
argvs["ip"] = &rFunc{
|
||||
Usage: "display the container's network interfaces",
|
||||
Action: nsenterIp,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// we need to check our argv 0 for any registred functions to run instead of the
|
||||
// normal cli code path
|
||||
f, exists := argvs[strings.TrimPrefix(os.Args[0], "nsenter-")]
|
||||
if exists {
|
||||
runFunc(f)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = "nsinit"
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/docker/libcontainer/configs"
|
||||
"github.com/docker/libcontainer/devices"
|
||||
"github.com/docker/libcontainer/mount/nodes"
|
||||
"github.com/docker/libcontainer/namespaces"
|
||||
_ "github.com/docker/libcontainer/namespaces/nsenter"
|
||||
)
|
||||
|
||||
// nsenterExec exec's a process inside an existing container
|
||||
func nsenterExec(config *configs.Config, args []string) {
|
||||
if err := namespaces.FinalizeSetns(config, args); err != nil {
|
||||
log.Fatalf("failed to nsenter: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// nsenterMknod runs mknod inside an existing container
|
||||
//
|
||||
// mknod <path> <type> <major> <minor>
|
||||
func nsenterMknod(config *configs.Config, args []string) {
|
||||
if len(args) != 4 {
|
||||
log.Fatalf("expected mknod to have 4 arguments not %d", len(args))
|
||||
}
|
||||
|
||||
t := rune(args[1][0])
|
||||
|
||||
major, err := strconv.Atoi(args[2])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
minor, err := strconv.Atoi(args[3])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
n := &devices.Device{
|
||||
Path: args[0],
|
||||
Type: t,
|
||||
MajorNumber: int64(major),
|
||||
MinorNumber: int64(minor),
|
||||
}
|
||||
|
||||
if err := nodes.CreateDeviceNode("/", n); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// nsenterIp displays the network interfaces inside a container's net namespace
|
||||
func nsenterIp(config *configs.Config, args []string) {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
|
||||
fmt.Fprint(w, "NAME\tMTU\tMAC\tFLAG\tADDRS\n")
|
||||
|
||||
for _, iface := range interfaces {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
o := []string{}
|
||||
|
||||
for _, a := range addrs {
|
||||
o = append(o, a.String())
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n", iface.Name, iface.MTU, iface.HardwareAddr, iface.Flags, strings.Join(o, ","))
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
}
|
Loading…
Reference in New Issue