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