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:
parent
4b7c071e9c
commit
4918769b1a
11 changed files with 430 additions and 16 deletions
15
api.go
15
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)
|
||||
|
|
92
commands.go
92
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
|
||||
}
|
||||
|
|
14
container.go
14
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
contrib/host_integration/Dockerfile.dev
Normal file
27
contrib/host_integration/Dockerfile.dev
Normal 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"]
|
||||
|
4
contrib/host_integration/Dockerfile.min
Normal file
4
contrib/host_integration/Dockerfile.min
Normal file
|
@ -0,0 +1,4 @@
|
|||
FROM busybox
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
|
||||
ADD manager /usr/bin/
|
||||
ENTRYPOINT ["/usr/bin/manager"]
|
130
contrib/host_integration/manager.go
Normal file
130
contrib/host_integration/manager.go
Normal 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)
|
||||
}
|
||||
}
|
68
docs/sources/use/host_integration.rst
Normal file
68
docs/sources/use/host_integration.rst
Normal 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.
|
|
@ -18,3 +18,4 @@ Contents:
|
|||
baseimages
|
||||
port_redirection
|
||||
puppet
|
||||
host_integration
|
||||
|
|
|
@ -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
44
utils/signal_darwin.go
Normal 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
47
utils/signal_linux.go
Normal 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,
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue