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

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

Add socket activation
This commit is contained in:
Michael Crosby 2014-01-28 11:38:25 -08:00
commit 2723133a69
10 changed files with 226 additions and 23 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,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

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

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

View file

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

View file

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

View file

@ -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
pkg/systemd/listendfd.go Normal file
View file

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

View file

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

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