diff --git a/api.go b/api.go index ad308335cc..a656414062 100644 --- a/api.go +++ b/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) diff --git a/commands.go b/commands.go index 7d33b81d0a..5bf4437b90 100644 --- a/commands.go +++ b/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 } diff --git a/container.go b/container.go index 7d85aef0a1..cf92461f2c 100644 --- a/container.go +++ b/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 } } diff --git a/contrib/host_integration/Dockerfile.dev b/contrib/host_integration/Dockerfile.dev new file mode 100644 index 0000000000..161416e750 --- /dev/null +++ b/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 + +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"] + diff --git a/contrib/host_integration/Dockerfile.min b/contrib/host_integration/Dockerfile.min new file mode 100644 index 0000000000..1a7b3a9d82 --- /dev/null +++ b/contrib/host_integration/Dockerfile.min @@ -0,0 +1,4 @@ +FROM busybox +MAINTAINER Guillaume J. Charmes +ADD manager /usr/bin/ +ENTRYPOINT ["/usr/bin/manager"] diff --git a/contrib/host_integration/manager.go b/contrib/host_integration/manager.go new file mode 100644 index 0000000000..98ff90aa1b --- /dev/null +++ b/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", "", "Author of the image") + description := flag.String("d", "", "Description of the image") + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "\nUsage: manager \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 == "" && img[0].Author != "" { + *author = strings.Replace(img[0].Author, "\"", "", -1) + } + // If no description has been set, use the comment from the image + if *description == "" && 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) + } +} diff --git a/docs/sources/use/host_integration.rst b/docs/sources/use/host_integration.rst new file mode 100644 index 0000000000..cc2f56a5c7 --- /dev/null +++ b/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] + + -a="": Author of the image + -d="": 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 ' -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 ' -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. diff --git a/docs/sources/use/index.rst b/docs/sources/use/index.rst index d0a40159e9..7d0b3c5a31 100644 --- a/docs/sources/use/index.rst +++ b/docs/sources/use/index.rst @@ -18,3 +18,4 @@ Contents: baseimages port_redirection puppet + host_integration diff --git a/server.go b/server.go index 27d1968dc8..65040ba7fd 100644 --- a/server.go +++ b/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)) diff --git a/utils/signal_darwin.go b/utils/signal_darwin.go new file mode 100644 index 0000000000..28730db8e5 --- /dev/null +++ b/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, + ) +} diff --git a/utils/signal_linux.go b/utils/signal_linux.go new file mode 100644 index 0000000000..26cfd56967 --- /dev/null +++ b/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, + ) +}