瀏覽代碼

Add -nostdin and -proxy to docker attach, allow arbirary signal to be sent via docker kill api endpoint
Allow attach from `docker start`
Add host integration script generation
Update doc with host integration

Guillaume J. Charmes 12 年之前
父節點
當前提交
4918769b1a

+ 14 - 1
api.go

@@ -135,8 +135,21 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 	}
+	if err := parseForm(r); err != nil {
+		return err
+	}
 	name := vars["name"]
-	if err := srv.ContainerKill(name); err != nil {
+	s := r.Form.Get("signal")
+	signal := 9
+	if s != "" {
+		if s, err := strconv.Atoi(s); err != nil {
+			return err
+		} else {
+			signal = s
+		}
+	}
+
+	if err := srv.ContainerKill(name, signal); err != nil {
 		return err
 	}
 	w.WriteHeader(http.StatusNoContent)

+ 86 - 6
commands.go

@@ -547,6 +547,8 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
 
 func (cli *DockerCli) CmdStart(args ...string) error {
 	cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
+	attach := cmd.Bool("a", false, "Attach container's stdout/stderr and forward all signals to the process")
+	openStdin := cmd.Bool("i", false, "Attach container's stdin")
 	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
@@ -555,17 +557,79 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 		return nil
 	}
 
+	var cErr chan error
+	if *attach || *openStdin {
+		if cmd.NArg() > 1 {
+			return fmt.Errorf("Impossible to start and attach multiple containers at once.")
+		}
+
+		body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
+		if err != nil {
+			return err
+		}
+
+		container := &Container{}
+		err = json.Unmarshal(body, container)
+		if err != nil {
+			return err
+		}
+
+		if !container.Config.Tty {
+			sigc := make(chan os.Signal, 1)
+			utils.CatchAll(sigc)
+			go func() {
+				for s := range sigc {
+					if _, _, err := cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cmd.Arg(0), s), nil); err != nil {
+						utils.Debugf("Error sending signal: %s", err)
+					}
+				}
+			}()
+		}
+
+		if container.Config.Tty {
+			if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
+				return err
+			}
+		}
+
+		v := url.Values{}
+		v.Set("stream", "1")
+		if *openStdin && container.Config.OpenStdin {
+			v.Set("stdin", "1")
+		}
+		v.Set("stdout", "1")
+		v.Set("stderr", "1")
+
+		cErr = utils.Go(func() error {
+			return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out)
+		})
+	}
+
 	var encounteredError error
-	for _, name := range args {
+	for _, name := range cmd.Args() {
 		_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
 		if err != nil {
-			fmt.Fprintf(cli.err, "%s\n", err)
-			encounteredError = fmt.Errorf("Error: failed to start one or more containers")
+			if !*attach || !*openStdin {
+				fmt.Fprintf(cli.err, "%s\n", err)
+				encounteredError = fmt.Errorf("Error: failed to start one or more containers")
+			}
 		} else {
-			fmt.Fprintf(cli.out, "%s\n", name)
+			if !*attach || !*openStdin {
+				fmt.Fprintf(cli.out, "%s\n", name)
+			}
 		}
 	}
-	return encounteredError
+	if encounteredError != nil {
+		if *openStdin || *attach {
+			cli.in.Close()
+			<-cErr
+		}
+		return encounteredError
+	}
+	if *openStdin || *attach {
+		return <-cErr
+	}
+	return nil
 }
 
 func (cli *DockerCli) CmdInspect(args ...string) error {
@@ -1238,6 +1302,8 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
 
 func (cli *DockerCli) CmdAttach(args ...string) error {
 	cmd := Subcmd("attach", "CONTAINER", "Attach to a running container")
+	noStdin := cmd.Bool("nostdin", false, "Do not attach stdin")
+	proxy := cmd.Bool("proxy", false, "Proxify all received signal to the process (even in non-tty mode)")
 	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
@@ -1269,10 +1335,24 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 
 	v := url.Values{}
 	v.Set("stream", "1")
-	v.Set("stdin", "1")
+	if !*noStdin && container.Config.OpenStdin {
+		v.Set("stdin", "1")
+	}
 	v.Set("stdout", "1")
 	v.Set("stderr", "1")
 
+	if *proxy && !container.Config.Tty {
+		sigc := make(chan os.Signal, 1)
+		utils.CatchAll(sigc)
+		go func() {
+			for s := range sigc {
+				if _, _, err := cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cmd.Arg(0), s), nil); err != nil {
+					utils.Debugf("Error sending signal: %s", err)
+				}
+			}
+		}()
+	}
+
 	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out, cli.err); err != nil {
 		return err
 	}

+ 7 - 7
container.go

@@ -1044,13 +1044,13 @@ func (container *Container) cleanup() {
 	}
 }
 
-func (container *Container) kill() error {
+func (container *Container) kill(sig int) error {
 	if !container.State.Running {
 		return nil
 	}
 
 	// Sending SIGKILL to the process via lxc
-	output, err := exec.Command("lxc-kill", "-n", container.ID, "9").CombinedOutput()
+	output, err := exec.Command("lxc-kill", "-n", container.ID, strconv.Itoa(sig)).CombinedOutput()
 	if err != nil {
 		log.Printf("error killing container %s (%s, %s)", container.ID, output, err)
 	}
@@ -1060,7 +1060,7 @@ func (container *Container) kill() error {
 		if container.cmd == nil {
 			return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ID)
 		}
-		log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.ID)
+		log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %d  - trying direct SIGKILL", sig, container.ID)
 		if err := container.cmd.Process.Kill(); err != nil {
 			return err
 		}
@@ -1071,13 +1071,13 @@ func (container *Container) kill() error {
 	return nil
 }
 
-func (container *Container) Kill() error {
+func (container *Container) Kill(sig int) error {
 	container.State.Lock()
 	defer container.State.Unlock()
 	if !container.State.Running {
 		return nil
 	}
-	return container.kill()
+	return container.kill(sig)
 }
 
 func (container *Container) Stop(seconds int) error {
@@ -1091,7 +1091,7 @@ func (container *Container) Stop(seconds int) error {
 	if output, err := exec.Command("lxc-kill", "-n", container.ID, "15").CombinedOutput(); err != nil {
 		log.Print(string(output))
 		log.Print("Failed to send SIGTERM to the process, force killing")
-		if err := container.kill(); err != nil {
+		if err := container.kill(9); err != nil {
 			return err
 		}
 	}
@@ -1099,7 +1099,7 @@ func (container *Container) Stop(seconds int) error {
 	// 2. Wait for the process to exit on its own
 	if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
 		log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
-		if err := container.kill(); err != nil {
+		if err := container.kill(9); err != nil {
 			return err
 		}
 	}

+ 27 - 0
contrib/host_integration/Dockerfile.dev

@@ -0,0 +1,27 @@
+#
+# This Dockerfile will create an image that allows to generate upstart and
+# systemd scripts (more to come)
+#
+# docker-version 0.6.2
+#
+
+FROM		ubuntu:12.10
+MAINTAINER	Guillaume J. Charmes <guillaume@dotcloud.com>
+
+RUN		apt-get update && apt-get install -y wget git mercurial
+
+# Install Go
+RUN		wget --no-check-certificate https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz -O go-1.1.2.tar.gz
+RUN		tar -xzvf go-1.1.2.tar.gz && mv /go /goroot
+RUN		mkdir /go
+
+ENV		GOROOT	  /goroot
+ENV		GOPATH	  /go
+ENV		PATH	  $GOROOT/bin:$PATH
+
+RUN		go get github.com/dotcloud/docker && cd /go/src/github.com/dotcloud/docker && git checkout v0.6.3
+ADD		manager.go	/manager/
+RUN		cd /manager && go build -o /usr/bin/manager
+
+ENTRYPOINT	["/usr/bin/manager"]
+

+ 4 - 0
contrib/host_integration/Dockerfile.min

@@ -0,0 +1,4 @@
+FROM		busybox
+MAINTAINER	Guillaume J. Charmes <guillaume@dotcloud.com>
+ADD		manager	  /usr/bin/
+ENTRYPOINT	["/usr/bin/manager"]

+ 130 - 0
contrib/host_integration/manager.go

@@ -0,0 +1,130 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"github.com/dotcloud/docker"
+	"os"
+	"strings"
+	"text/template"
+)
+
+var templates = map[string]string{
+
+	"upstart": `description "{{.description}}"
+author "{{.author}}"
+start on filesystem and started lxc-net and started docker
+stop on runlevel [!2345]
+respawn
+exec /home/vagrant/goroot/bin/docker start -a {{.container_id}}
+`,
+
+	"systemd": `[Unit]
+	Description={{.description}}
+	Author={{.author}}
+	After=docker.service
+
+[Service]
+	Restart=always
+	ExecStart=/usr/bin/docker start -a {{.container_id}}
+	ExecStop=/usr/bin/docker stop -t 2 {{.container_id}}
+
+[Install]
+	WantedBy=local.target
+`,
+}
+
+func main() {
+	// Parse command line for custom options
+	kind := flag.String("t", "upstart", "Type of manager requested")
+	author := flag.String("a", "<none>", "Author of the image")
+	description := flag.String("d", "<none>", "Description of the image")
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, "\nUsage: manager <container id>\n\n")
+		flag.PrintDefaults()
+	}
+	flag.Parse()
+
+	// We require at least the container ID
+	if flag.NArg() != 1 {
+		println(flag.NArg())
+		flag.Usage()
+		return
+	}
+
+	// Check that the requested process manager is supported
+	if _, exists := templates[*kind]; !exists {
+		panic("Unkown script template")
+	}
+
+	// Load the requested template
+	tpl, err := template.New("processManager").Parse(templates[*kind])
+	if err != nil {
+		panic(err)
+	}
+
+	// Create stdout/stderr buffers
+	bufOut := bytes.NewBuffer(nil)
+	bufErr := bytes.NewBuffer(nil)
+
+	// Instanciate the Docker CLI
+	cli := docker.NewDockerCli(nil, bufOut, bufErr, "unix", "/var/run/docker.sock")
+	// Retrieve the container info
+	if err := cli.CmdInspect(flag.Arg(0)); err != nil {
+		// As of docker v0.6.3, CmdInspect always returns nil
+		panic(err)
+	}
+
+	// If there is nothing in the error buffer, then the Docker daemon is there and the container has been found
+	if bufErr.Len() == 0 {
+		// Unmarshall the resulting container data
+		c := []*docker.Container{{}}
+		if err := json.Unmarshal(bufOut.Bytes(), &c); err != nil {
+			panic(err)
+		}
+		// Reset the buffers
+		bufOut.Reset()
+		bufErr.Reset()
+		// Retrieve the info of the linked image
+		if err := cli.CmdInspect(c[0].Image); err != nil {
+			panic(err)
+		}
+		// If there is nothing in the error buffer, then the image has been found.
+		if bufErr.Len() == 0 {
+			// Unmarshall the resulting image data
+			img := []*docker.Image{{}}
+			if err := json.Unmarshal(bufOut.Bytes(), &img); err != nil {
+				panic(err)
+			}
+			// If no author has been set, use the one from the image
+			if *author == "<none>" && img[0].Author != "" {
+				*author = strings.Replace(img[0].Author, "\"", "", -1)
+			}
+			// If no description has been set, use the comment from the image
+			if *description == "<none>" && img[0].Comment != "" {
+				*description = strings.Replace(img[0].Comment, "\"", "", -1)
+			}
+		}
+	}
+
+	/// Old version: Wrtie the resulting script to file
+	// f, err := os.OpenFile(kind, os.O_CREATE|os.O_WRONLY, 0755)
+	// if err != nil {
+	// 	panic(err)
+	// }
+	// defer f.Close()
+
+	// Create a map with needed data
+	data := map[string]string{
+		"author":       *author,
+		"description":  *description,
+		"container_id": flag.Arg(0),
+	}
+
+	// Process the template and output it on Stdout
+	if err := tpl.Execute(os.Stdout, data); err != nil {
+		panic(err)
+	}
+}

+ 68 - 0
docs/sources/use/host_integration.rst

@@ -0,0 +1,68 @@
+:title: Host Integration
+:description: How to generate scripts for upstart, systemd, etc.
+:keywords: systemd, upstart, supervisor, docker, documentation, host integration
+
+Introduction
+============
+
+When you have finished setting up your image and are happy with your running
+container, you may want to use a process manager like `upstart` or `systemd`.
+
+In order to do so, we provide a simple image: creack/manger:min.
+
+This image takes the container ID as parameter. We also can specify the kind of
+process manager and meta datas like Author and Description.
+
+If no process manager is specified, then `upstart` is assumed.
+
+Note: The result will be an output to stdout.
+
+Usage
+=====
+Usage: docker run creack/manager:min [OPTIONS] <container id>
+
+  -a="<none>": Author of the image
+  -d="<none>": Description of the image
+  -t="upstart": Type of manager requested
+
+Development
+===========
+
+The image creack/manager:min is a `busybox` base with the binary as entrypoint.
+It is meant to be light and fast to download.
+
+Now, if you want/need to change or add things, you can download the full
+creack/manager repository that contains creack/manager:min and
+creack/manager:dev.
+
+The Dockerfiles and the sources are available in `/contrib/host_integration`.
+
+
+Upstart
+=======
+
+Upstart is the default process manager. The generated script will start the
+container after docker daemon. If the container dies, it will respawn.
+Start/Restart/Stop/Reload are supported. Reload will send a SIGHUP to the container.
+
+Example:
+`CID=$(docker run -d creack/firefo-vnc)`
+`docker run creack/manager:min -a 'Guillaume J. Charmes <guillaume@dotcloud.com>' -d 'Awesome Firefox in VLC' $CID > /etc/init/firefoxvnc.conf`
+
+You can now do `start firefoxvnc` or `stop firefoxvnc` and if the container
+dies for some reason, upstart will restart it.
+
+Systemd
+=======
+
+In order to generate a systemd script, we need to -t option. The generated
+script will start the container after docker daemon. If the container dies, it
+will respawn.
+Start/Restart/Reload/Stop are supported.
+
+Example (fedora):
+`CID=$(docker run -d creack/firefo-vnc)`
+`docker run creack/manager:min -t systemd -a 'Guillaume J. Charmes <guillaume@dotcloud.com>' -d 'Awesome Firefox in VLC' $CID > /usr/lib/systemd/system/firefoxvnc.service`
+
+You can now do `systemctl start firefoxvnc` or `systemctl stop firefoxvnc`
+and if the container dies for some reason, systemd will restart it.

+ 1 - 0
docs/sources/use/index.rst

@@ -18,3 +18,4 @@ Contents:
    baseimages
    port_redirection
    puppet
+   host_integration

+ 2 - 2
server.go

@@ -72,9 +72,9 @@ func (srv *Server) versionInfos() []utils.VersionInfo {
 	return ret
 }
 
-func (srv *Server) ContainerKill(name string) error {
+func (srv *Server) ContainerKill(name string, sig int) error {
 	if container := srv.runtime.Get(name); container != nil {
-		if err := container.Kill(); err != nil {
+		if err := container.Kill(sig); err != nil {
 			return fmt.Errorf("Error killing container %s: %s", name, err)
 		}
 		srv.LogEvent("kill", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))

+ 44 - 0
utils/signal_darwin.go

@@ -0,0 +1,44 @@
+package utils
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+)
+
+func CatchAll(sigc chan os.Signal) {
+	signal.Notify(sigc,
+		syscall.SIGABRT,
+		syscall.SIGALRM,
+		syscall.SIGBUS,
+		syscall.SIGCHLD,
+		syscall.SIGCONT,
+		syscall.SIGEMT,
+		syscall.SIGFPE,
+		syscall.SIGHUP,
+		syscall.SIGILL,
+		syscall.SIGINFO,
+		syscall.SIGINT,
+		syscall.SIGIO,
+		syscall.SIGIOT,
+		syscall.SIGKILL,
+		syscall.SIGPIPE,
+		syscall.SIGPROF,
+		syscall.SIGQUIT,
+		syscall.SIGSEGV,
+		syscall.SIGSTOP,
+		syscall.SIGSYS,
+		syscall.SIGTERM,
+		syscall.SIGTRAP,
+		syscall.SIGTSTP,
+		syscall.SIGTTIN,
+		syscall.SIGTTOU,
+		syscall.SIGURG,
+		syscall.SIGUSR1,
+		syscall.SIGUSR2,
+		syscall.SIGVTALRM,
+		syscall.SIGWINCH,
+		syscall.SIGXCPU,
+		syscall.SIGXFSZ,
+	)
+}

+ 47 - 0
utils/signal_linux.go

@@ -0,0 +1,47 @@
+package utils
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+)
+
+func CatchAll(sigc chan os.Signal) {
+	signal.Notify(sigc,
+		syscall.SIGABRT,
+		syscall.SIGALRM,
+		syscall.SIGBUS,
+		syscall.SIGCHLD,
+		syscall.SIGCLD,
+		syscall.SIGCONT,
+		syscall.SIGFPE,
+		syscall.SIGHUP,
+		syscall.SIGILL,
+		syscall.SIGINT,
+		syscall.SIGIO,
+		syscall.SIGIOT,
+		syscall.SIGKILL,
+		syscall.SIGPIPE,
+		syscall.SIGPOLL,
+		syscall.SIGPROF,
+		syscall.SIGPWR,
+		syscall.SIGQUIT,
+		syscall.SIGSEGV,
+		syscall.SIGSTKFLT,
+		syscall.SIGSTOP,
+		syscall.SIGSYS,
+		syscall.SIGTERM,
+		syscall.SIGTRAP,
+		syscall.SIGTSTP,
+		syscall.SIGTTIN,
+		syscall.SIGTTOU,
+		syscall.SIGUNUSED,
+		syscall.SIGURG,
+		syscall.SIGUSR1,
+		syscall.SIGUSR2,
+		syscall.SIGVTALRM,
+		syscall.SIGWINCH,
+		syscall.SIGXCPU,
+		syscall.SIGXFSZ,
+	)
+}