Browse code

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)

Brandon Philips authored on 2013/12/07 14:19:30
Showing 6 changed files
... ...
@@ -24,6 +24,7 @@ import (
24 24
 	"regexp"
25 25
 	"strconv"
26 26
 	"strings"
27
+	"syscall"
27 28
 )
28 29
 
29 30
 const (
... ...
@@ -1081,16 +1082,66 @@ func ServeRequest(srv *Server, apiversion float64, w http.ResponseWriter, req *h
1081 1081
 	return nil
1082 1082
 }
1083 1083
 
1084
+// ServeFD creates an http.Server and sets it up to serve given a socket activated
1085
+// argument.
1086
+func ServeFd(addr string, handle http.Handler) error {
1087
+	ls, e := systemd.ListenFD(addr)
1088
+	if e != nil {
1089
+		return e
1090
+	}
1091
+
1092
+	chErrors := make(chan error, len(ls))
1093
+
1094
+	// Since ListenFD will return one or more sockets we have
1095
+	// to create a go func to spawn off multiple serves
1096
+	for i, _ := range(ls) {
1097
+		listener := ls[i]
1098
+		go func () {
1099
+			httpSrv := http.Server{Handler: handle}
1100
+			chErrors <- httpSrv.Serve(listener)
1101
+		}()
1102
+	}
1103
+
1104
+	for i := 0; i < len(ls); i += 1 {
1105
+		err := <-chErrors
1106
+		if err != nil {
1107
+			return err
1108
+		}
1109
+	}
1110
+
1111
+	return nil
1112
+}
1113
+
1114
+// ListenAndServe sets up the required http.Server and gets it listening for
1115
+// each addr passed in and does protocol specific checking.
1084 1116
 func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
1085 1117
 	r, err := createRouter(srv, logging)
1086 1118
 	if err != nil {
1087 1119
 		return err
1088 1120
 	}
1089
-	l, e := net.Listen(proto, addr)
1090
-	if e != nil {
1091
-		return e
1121
+
1122
+	if proto == "fd" {
1123
+		return ServeFd(addr, r)
1092 1124
 	}
1125
+
1093 1126
 	if proto == "unix" {
1127
+		if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
1128
+			return err
1129
+		}
1130
+	}
1131
+
1132
+	l, err := net.Listen(proto, addr)
1133
+	if err != nil {
1134
+		return err
1135
+	}
1136
+
1137
+	// Basic error and sanity checking
1138
+	switch proto {
1139
+	case "tcp":
1140
+		if !strings.HasPrefix(addr, "127.0.0.1") {
1141
+			log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
1142
+		}
1143
+	case "unix":
1094 1144
 		if err := os.Chmod(addr, 0660); err != nil {
1095 1145
 			return err
1096 1146
 		}
... ...
@@ -1110,11 +1161,10 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
1110 1110
 				return err
1111 1111
 			}
1112 1112
 		}
1113
+	default:
1114
+		return fmt.Errorf("Invalid protocol format.")
1113 1115
 	}
1114
-	httpSrv := http.Server{Addr: addr, Handler: r}
1115 1116
 
1116
-	log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
1117
-	// Tell the init daemon we are accepting requests
1118
-	go systemd.SdNotify("READY=1")
1117
+	httpSrv := http.Server{Addr: addr, Handler: r}
1119 1118
 	return httpSrv.Serve(l)
1120 1119
 }
1121 1120
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+[Unit]
1
+Description=Docker Application Container Engine
2
+Documentation=http://docs.docker.io
3
+After=network.target
4
+
5
+[Service]
6
+ExecStartPre=/bin/mount --make-rprivate /
7
+ExecStart=/usr/bin/docker -d -H fd://*
8
+
9
+[Install]
10
+WantedBy=multi-user.target
0 11
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+[Unit]
1
+Description=Docker Socket for the API
2
+
3
+[Socket]
4
+ListenStream=/var/run/docker.sock
5
+
6
+[Install]
7
+WantedBy=sockets.target
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/dotcloud/docker/pkg/cgroups"
11 11
 	"github.com/dotcloud/docker/pkg/graphdb"
12 12
 	"github.com/dotcloud/docker/registry"
13
+	"github.com/dotcloud/docker/systemd"
13 14
 	"github.com/dotcloud/docker/utils"
14 15
 	"io"
15 16
 	"io/ioutil"
... ...
@@ -114,29 +115,20 @@ func jobInitApi(job *engine.Job) engine.Status {
114 114
 	return engine.StatusOK
115 115
 }
116 116
 
117
+// ListenAndServe loops through all of the protocols sent in to docker and spawns
118
+// off a go routine to setup a serving http.Server for each.
117 119
 func (srv *Server) ListenAndServe(job *engine.Job) engine.Status {
118 120
 	protoAddrs := job.Args
119 121
 	chErrors := make(chan error, len(protoAddrs))
122
+
120 123
 	for _, protoAddr := range protoAddrs {
121 124
 		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
122
-		switch protoAddrParts[0] {
123
-		case "unix":
124
-			if err := syscall.Unlink(protoAddrParts[1]); err != nil && !os.IsNotExist(err) {
125
-				log.Fatal(err)
126
-			}
127
-		case "tcp":
128
-			if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
129
-				log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
130
-			}
131
-		default:
132
-			job.Errorf("Invalid protocol format.")
133
-			return engine.StatusErr
134
-		}
135
-		go func() {
136
-			// FIXME: merge Server.ListenAndServe with ListenAndServe
125
+		go func () {
126
+			log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1])
137 127
 			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging"))
138 128
 		}()
139 129
 	}
130
+
140 131
 	for i := 0; i < len(protoAddrs); i += 1 {
141 132
 		err := <-chErrors
142 133
 		if err != nil {
... ...
@@ -144,6 +136,10 @@ func (srv *Server) ListenAndServe(job *engine.Job) engine.Status {
144 144
 			return engine.StatusErr
145 145
 		}
146 146
 	}
147
+
148
+	// Tell the init daemon we are accepting requests
149
+	go systemd.SdNotify("READY=1")
150
+
147 151
 	return engine.StatusOK
148 152
 }
149 153
 
150 154
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+package systemd
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"net"
6
+	"strconv"
7
+
8
+	"github.com/coreos/go-systemd/activation"
9
+)
10
+
11
+// ListenFD returns the specified socket activated files as a slice of
12
+// net.Listeners or all of the activated files if "*" is given.
13
+func ListenFD(addr string) ([]net.Listener, error) {
14
+	files := activation.Files(false)
15
+	if files == nil || len(files) == 0 {
16
+		return nil, errors.New("No sockets found")
17
+	}
18
+
19
+	fdNum, _ := strconv.Atoi(addr)
20
+	fdOffset := fdNum - 3
21
+	if (addr != "*") && (len(files) < int(fdOffset)+1) {
22
+		return nil, errors.New("Too few socket activated files passed in")
23
+	}
24
+
25
+	// socket activation
26
+	listeners := make([]net.Listener, len(files))
27
+	for i, f := range files {
28
+		var err error
29
+		listeners[i], err = net.FileListener(f)
30
+		if err != nil {
31
+			return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error())
32
+		}
33
+	}
34
+
35
+	if addr == "*" {
36
+		return listeners, nil
37
+	}
38
+
39
+	return []net.Listener{listeners[fdOffset]}, nil
40
+}
... ...
@@ -767,6 +767,8 @@ func ParseHost(defaultHost string, defaultPort int, defaultUnix, addr string) (s
767 767
 	case strings.HasPrefix(addr, "tcp://"):
768 768
 		proto = "tcp"
769 769
 		addr = strings.TrimPrefix(addr, "tcp://")
770
+	case strings.HasPrefix(addr, "fd://"):
771
+		return addr, nil
770 772
 	case addr == "":
771 773
 		proto = "unix"
772 774
 		addr = defaultUnix