1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

server: add socket activation

This adds the ability to socket activate docker by passing in
`-H fd://*` along with examples systemd configuration files.

The fastest way to test this is to run:

```
/usr/lib/systemd/systemd-activate -l 127.0.0.1:2001 /usr/bin/docker -d -H 'fd://*'
docker -H tcp://127.0.0.1:2001 ps
```

Docker-DCO-1.1-Signed-off-by: Brandon Philips <brandon.philips@coreos.com> (github: philips)
This commit is contained in:
Brandon Philips 2013-12-06 21:19:30 -08:00
parent e996daeed0
commit 87fb2c973d
6 changed files with 130 additions and 22 deletions

64
api.go
View file

@ -24,6 +24,7 @@ import (
"regexp"
"strconv"
"strings"
"syscall"
)
const (
@ -1081,16 +1082,66 @@ func ServeRequest(srv *Server, apiversion float64, w http.ResponseWriter, req *h
return nil
}
// ServeFD creates an http.Server and sets it up to serve given a socket activated
// argument.
func ServeFd(addr string, handle http.Handler) error {
ls, e := systemd.ListenFD(addr)
if e != nil {
return e
}
chErrors := make(chan error, len(ls))
// Since ListenFD will return one or more sockets we have
// to create a go func to spawn off multiple serves
for i, _ := range(ls) {
listener := ls[i]
go func () {
httpSrv := http.Server{Handler: handle}
chErrors <- httpSrv.Serve(listener)
}()
}
for i := 0; i < len(ls); i += 1 {
err := <-chErrors
if err != nil {
return err
}
}
return nil
}
// ListenAndServe sets up the required http.Server and gets it listening for
// each addr passed in and does protocol specific checking.
func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
r, err := createRouter(srv, logging)
if err != nil {
return err
}
l, e := net.Listen(proto, addr)
if e != nil {
return e
if proto == "fd" {
return ServeFd(addr, r)
}
if proto == "unix" {
if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
return err
}
}
l, err := net.Listen(proto, addr)
if err != nil {
return err
}
// Basic error and sanity checking
switch proto {
case "tcp":
if !strings.HasPrefix(addr, "127.0.0.1") {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
case "unix":
if err := os.Chmod(addr, 0660); err != nil {
return err
}
@ -1110,11 +1161,10 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
return err
}
}
default:
return fmt.Errorf("Invalid protocol format.")
}
httpSrv := http.Server{Addr: addr, Handler: r}
log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
// Tell the init daemon we are accepting requests
go systemd.SdNotify("READY=1")
httpSrv := http.Server{Addr: addr, Handler: r}
return httpSrv.Serve(l)
}

View file

@ -0,0 +1,11 @@
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.io
After=network.target
[Service]
ExecStartPre=/bin/mount --make-rprivate /
ExecStart=/usr/bin/docker -d -H fd://*
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,8 @@
[Unit]
Description=Docker Socket for the API
[Socket]
ListenStream=/var/run/docker.sock
[Install]
WantedBy=sockets.target

View file

@ -10,6 +10,7 @@ import (
"github.com/dotcloud/docker/pkg/cgroups"
"github.com/dotcloud/docker/pkg/graphdb"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/systemd"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@ -114,29 +115,20 @@ func jobInitApi(job *engine.Job) engine.Status {
return engine.StatusOK
}
// ListenAndServe loops through all of the protocols sent in to docker and spawns
// off a go routine to setup a serving http.Server for each.
func (srv *Server) ListenAndServe(job *engine.Job) engine.Status {
protoAddrs := job.Args
chErrors := make(chan error, len(protoAddrs))
for _, protoAddr := range protoAddrs {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
switch protoAddrParts[0] {
case "unix":
if err := syscall.Unlink(protoAddrParts[1]); err != nil && !os.IsNotExist(err) {
log.Fatal(err)
}
case "tcp":
if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
default:
job.Errorf("Invalid protocol format.")
return engine.StatusErr
}
go func () {
// FIXME: merge Server.ListenAndServe with ListenAndServe
log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1])
chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging"))
}()
}
for i := 0; i < len(protoAddrs); i += 1 {
err := <-chErrors
if err != nil {
@ -144,6 +136,10 @@ func (srv *Server) ListenAndServe(job *engine.Job) engine.Status {
return engine.StatusErr
}
}
// Tell the init daemon we are accepting requests
go systemd.SdNotify("READY=1")
return engine.StatusOK
}

41
systemd/listendfd.go Normal file
View file

@ -0,0 +1,41 @@
package systemd
import (
"errors"
"fmt"
"net"
"strconv"
"github.com/coreos/go-systemd/activation"
)
// ListenFD returns the specified socket activated files as a slice of
// net.Listeners or all of the activated files if "*" is given.
func ListenFD(addr string) ([]net.Listener, error) {
files := activation.Files(false)
if files == nil || len(files) == 0 {
return nil, errors.New("No sockets found")
}
fdNum, _ := strconv.Atoi(addr)
fdOffset := fdNum - 3
if (addr != "*") && (len(files) < int(fdOffset)+1) {
return nil, errors.New("Too few socket activated files passed in")
}
// socket activation
listeners := make([]net.Listener, len(files))
for i, f := range files {
var err error
listeners[i], err = net.FileListener(f)
if err != nil {
return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error())
}
}
if addr == "*" {
return listeners, nil
}
return []net.Listener{listeners[fdOffset]}, nil
}

View file

@ -767,6 +767,8 @@ func ParseHost(defaultHost string, defaultPort int, defaultUnix, addr string) (s
case strings.HasPrefix(addr, "tcp://"):
proto = "tcp"
addr = strings.TrimPrefix(addr, "tcp://")
case strings.HasPrefix(addr, "fd://"):
return addr, nil
case addr == "":
proto = "unix"
addr = defaultUnix