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
This commit is contained in:
Guillaume J. Charmes 2013-09-11 23:50:26 -07:00 committed by Victor Vieux
parent 4b7c071e9c
commit 4918769b1a
11 changed files with 430 additions and 16 deletions

15
api.go
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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
utils/signal_darwin.go Normal file
View file

@ -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
utils/signal_linux.go Normal file
View file

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