Implemented a self-injecting process wrapper that runs inside the container

- Before starting the container, docker injects itself inside the container by mount binding the dockerd binary into /sbin/init
- Instead of running the user process directly inside the container, we run /sbin/init targetprocess [args...]
- When docker is run as /sbin/init (e.g. argv[0] == "/sbin/init"), then its own sys init code kicks in
- The sys init code will be responsible for setting up the process environment prior to its execution (setuid, networking, ...).
- Finally, docker's sys init will exec() the container's process, thus replacing itself with the target binary (which will be running as pid 1)
This commit is contained in:
Andrea Luzzardi 2013-02-13 14:01:44 -08:00
parent 0d46006269
commit 58a2294260
5 changed files with 53 additions and 0 deletions

View File

@ -16,6 +16,12 @@ import (
"time"
)
var sysInitPath string
func init() {
sysInitPath = SelfPath()
}
type Container struct {
Id string
Root string
@ -29,6 +35,7 @@ type Container struct {
Filesystem *Filesystem
State *State
SysInitPath string
lxcConfigPath string
cmd *exec.Cmd
stdout *writeBroadcaster
@ -58,6 +65,7 @@ func createContainer(id string, root string, command string, args []string, laye
Filesystem: newFilesystem(path.Join(root, "rootfs"), path.Join(root, "rw"), layers),
State: newState(),
SysInitPath: sysInitPath,
lxcConfigPath: path.Join(root, "config.lxc"),
stdout: newWriteBroadcaster(),
stderr: newWriteBroadcaster(),
@ -261,6 +269,7 @@ func (container *Container) Start() error {
"-n", container.Id,
"-f", container.lxcConfigPath,
"--",
"/sbin/init",
container.Path,
}
params = append(params, container.Args...)

View File

@ -6,6 +6,13 @@ import (
"testing"
)
// Hack to run sys init during unit testing
func init() {
if SelfPath() == "/sbin/init" {
SysInit()
}
}
func newTestDocker() (*Docker, error) {
root, err := ioutil.TempDir("", "docker-test")
if err != nil {

View File

@ -705,6 +705,11 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
}
func main() {
if docker.SelfPath() == "/sbin/init" {
// Running in init mode
docker.SysInit()
return
}
future.Seed()
flag.Parse()
d, err := New()

View File

@ -74,6 +74,9 @@ lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,no
#lxc.mount.entry = varlock {{$ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0
#lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
# Inject docker-init
lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0
# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
lxc.mount.entry = /etc/resolv.conf {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0

29
sysinit.go Normal file
View File

@ -0,0 +1,29 @@
package docker
import (
"fmt"
"log"
"os"
"os/exec"
"syscall"
)
// Sys Init code
// This code is run INSIDE the container and is responsible for setting
// up the environment before running the actual process
func SysInit() {
if len(os.Args) <= 1 {
fmt.Println("You should not invoke docker-init manually")
os.Exit(1)
}
path, err := exec.LookPath(os.Args[1])
if err != nil {
log.Printf("Unable to locate %v", os.Args[1])
os.Exit(127)
}
if err := syscall.Exec(path, os.Args[1:], os.Environ()); err != nil {
panic(err)
}
}