Explorar o código

Merge pull request #3105 from philips/add-socket-activation

Add socket activation
Michael Crosby %!s(int64=11) %!d(string=hai) anos
pai
achega
2723133a69

+ 57 - 7
api.go

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

+ 10 - 0
contrib/init/systemd/socket-activation/docker.service

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

+ 8 - 0
contrib/init/systemd/socket-activation/docker.socket

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

+ 1 - 1
docker/docker.go

@@ -44,7 +44,7 @@ func main() {
 		flMtu                = flag.Int([]string{"#mtu", "-mtu"}, docker.DefaultNetworkMtu, "Set the containers network mtu")
 	)
 	flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers")
-	flag.Var(&flHosts, []string{"H", "-host"}, "Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise")
+	flag.Var(&flHosts, []string{"H", "-host"}, "tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified")
 
 	flag.Parse()
 

+ 6 - 1
docs/sources/reference/commandline/cli.rst

@@ -27,7 +27,7 @@ To list available commands, either run ``docker`` with no parameters or execute
 
     Usage of docker:
       -D, --debug=false: Enable debug mode
-      -H, --host=[]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise
+      -H, --host=[]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise. systemd socket activation can be used with fd://[socketfd].
       --api-enable-cors=false: Enable CORS headers in the remote API
       -b, --bridge="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking
       --bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b
@@ -63,6 +63,11 @@ the ``-H`` flag for the client.
         # both are equal
 
 
+To run the daemon with `systemd socket activation <http://0pointer.de/blog/projects/socket-activation.html>`_, use ``docker -d -H fd://``.
+Using ``fd://`` will work perfectly for most setups but you can also specify individual sockets too ``docker -d -H fd://3``.
+If the specified socket activated files aren't found then docker will exit.
+You can find examples of using systemd socket activation with docker and systemd in the `docker source tree <https://github.com/dotcloud/docker/blob/master/contrib/init/systemd/socket-activation/>`_.
+
 .. _cli_attach:
 
 ``attach``

+ 55 - 0
pkg/systemd/activation/files.go

@@ -0,0 +1,55 @@
+/*
+Copyright 2013 CoreOS Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+// Package activation implements primitives for systemd socket activation.
+package activation
+
+import (
+	"os"
+	"strconv"
+	"syscall"
+)
+
+// based on: https://gist.github.com/alberts/4640792
+const (
+	listenFdsStart = 3
+)
+
+func Files(unsetEnv bool) []*os.File {
+	if unsetEnv {
+		// there is no way to unset env in golang os package for now
+		// https://code.google.com/p/go/issues/detail?id=6423
+		defer os.Setenv("LISTEN_PID", "")
+		defer os.Setenv("LISTEN_FDS", "")
+	}
+
+	pid, err := strconv.Atoi(os.Getenv("LISTEN_PID"))
+	if err != nil || pid != os.Getpid() {
+		return nil
+	}
+
+	nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS"))
+	if err != nil || nfds == 0 {
+		return nil
+	}
+
+	var files []*os.File
+	for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ {
+		syscall.CloseOnExec(fd)
+		files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd)))
+	}
+
+	return files
+}

+ 37 - 0
pkg/systemd/activation/listeners.go

@@ -0,0 +1,37 @@
+/*
+Copyright 2014 CoreOS Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package activation
+
+import (
+	"fmt"
+	"net"
+)
+
+// Listeners returns net.Listeners for all socket activated fds passed to this process.
+func Listeners(unsetEnv bool) ([]net.Listener, error) {
+	files := Files(unsetEnv)
+	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())
+		}
+	}
+
+	return listeners, nil
+}

+ 40 - 0
pkg/systemd/listendfd.go

@@ -0,0 +1,40 @@
+package systemd
+
+import (
+	"errors"
+	"net"
+	"strconv"
+
+	"github.com/dotcloud/docker/pkg/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) {
+	// socket activation
+	listeners, err := activation.Listeners(false)
+	if err != nil {
+		return nil, err
+	}
+
+	if listeners == nil || len(listeners) == 0 {
+		return nil, errors.New("No sockets found")
+	}
+
+	// default to all fds just like unix:// and tcp://
+	if addr == "" {
+		addr = "*"
+	}
+
+	fdNum, _ := strconv.Atoi(addr)
+	fdOffset := fdNum - 3
+	if (addr != "*") && (len(listeners) < int(fdOffset)+1) {
+		return nil, errors.New("Too few socket activated files passed in")
+	}
+
+	if addr == "*" {
+		return listeners, nil
+	}
+
+	return []net.Listener{listeners[fdOffset]}, nil
+}

+ 10 - 14
server.go

@@ -9,6 +9,7 @@ import (
 	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/pkg/cgroups"
 	"github.com/dotcloud/docker/pkg/graphdb"
+	"github.com/dotcloud/docker/pkg/systemd"
 	"github.com/dotcloud/docker/registry"
 	"github.com/dotcloud/docker/utils"
 	"io"
@@ -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
 }
 

+ 2 - 0
utils/utils.go

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