Merge pull request #938 from dotcloud/add_unix_socket-feature

* Runtime: Add unix socket and multiple -H
This commit is contained in:
Guillaume J. Charmes 2013-06-20 11:17:16 -07:00
Родитель 21a5a6202d 05796bed57
Коммит fa68fe6ff3
9 изменённых файлов: 164 добавлений и 54 удалений

19
api.go
Просмотреть файл

@ -8,12 +8,16 @@ import (
"github.com/gorilla/mux"
"io"
"log"
"net"
"net/http"
"os"
"strconv"
"strings"
)
const APIVERSION = 1.2
const DEFAULTHTTPHOST string = "127.0.0.1"
const DEFAULTHTTPPORT int = 4243
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack()
@ -848,12 +852,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
return r, nil
}
func ListenAndServe(addr string, srv *Server, logging bool) error {
log.Printf("Listening for HTTP on %s\n", addr)
func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
r, err := createRouter(srv, logging)
if err != nil {
return err
}
return http.ListenAndServe(addr, r)
l, e := net.Listen(proto, addr)
if e != nil {
return e
}
//as the daemon is launched as root, change to permission of the socket to allow non-root to connect
if proto == "unix" {
os.Chmod(addr, 0777)
}
httpSrv := http.Server{Addr: addr, Handler: r}
return httpSrv.Serve(l)
}

Просмотреть файл

@ -304,9 +304,9 @@ func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) {
return "", fmt.Errorf("An error occured during the build\n")
}
func NewBuilderClient(addr string, port int) BuildFile {
func NewBuilderClient(proto, addr string) BuildFile {
return &builderClient{
cli: NewDockerCli(addr, port),
cli: NewDockerCli(proto, addr),
config: &Config{},
tmpContainers: make(map[string]struct{}),
tmpImages: make(map[string]struct{}),

Просмотреть файл

@ -40,8 +40,8 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
return reflect.TypeOf(cli).MethodByName(methodName)
}
func ParseCommands(addr string, port int, args ...string) error {
cli := NewDockerCli(addr, port)
func ParseCommands(proto, addr string, args ...string) error {
cli := NewDockerCli(proto, addr)
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
@ -74,7 +74,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
return nil
}
}
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port)
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socker to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT)
for _, command := range [][2]string{
{"attach", "Attach to a running container"},
{"build", "Build a container from a Dockerfile"},
@ -197,7 +197,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
v := &url.Values{}
v.Set("t", *tag)
// Send the multipart request with correct content-type
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody)
req, err := http.NewRequest("POST", fmt.Sprintf("/v%s/build?%s", APIVERSION, v.Encode()), multipartBody)
if err != nil {
return err
}
@ -206,8 +206,13 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
req.Header.Set("X-Docker-Context-Compression", compression.Flag())
fmt.Println("Uploading Context...")
}
resp, err := http.DefaultClient.Do(req)
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil {
return err
}
@ -1302,7 +1307,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
params = bytes.NewBuffer(buf)
}
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params)
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
if err != nil {
return nil, -1, err
}
@ -1312,7 +1317,13 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
} else if method == "POST" {
req.Header.Set("Content-Type", "plain/text")
}
resp, err := http.DefaultClient.Do(req)
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return nil, -1, err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
@ -1337,7 +1348,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader([]byte{})
}
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in)
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
if err != nil {
return err
}
@ -1345,7 +1356,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if method == "POST" {
req.Header.Set("Content-Type", "plain/text")
}
resp, err := http.DefaultClient.Do(req)
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
@ -1395,7 +1412,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi
return err
}
req.Header.Set("Content-Type", "plain/text")
dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port))
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return err
}
@ -1478,13 +1495,13 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
return flags
}
func NewDockerCli(addr string, port int) *DockerCli {
func NewDockerCli(proto, addr string) *DockerCli {
authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
return &DockerCli{addr, port, authConfig}
return &DockerCli{proto, addr, authConfig}
}
type DockerCli struct {
host string
port int
proto string
addr string
authConfig *auth.AuthConfig
}

Просмотреть файл

@ -24,40 +24,29 @@ func main() {
docker.SysInit()
return
}
host := "127.0.0.1"
port := 4243
// FIXME: Switch d and D ? (to be more sshd like)
flDaemon := flag.Bool("d", false, "Daemon mode")
flDebug := flag.Bool("D", false, "Debug mode")
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
flDns := flag.String("dns", "", "Set custom dns servers")
flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)}
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
flag.Parse()
if len(flHosts) > 1 {
flHosts = flHosts[1:len(flHosts)] //trick to display a nice defaul value in the usage
}
for i, flHost := range flHosts {
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
}
if *bridgeName != "" {
docker.NetworkBridgeIface = *bridgeName
} else {
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
}
if strings.Contains(*flHost, ":") {
hostParts := strings.Split(*flHost, ":")
if len(hostParts) != 2 {
log.Fatal("Invalid bind address format.")
os.Exit(-1)
}
if hostParts[0] != "" {
host = hostParts[0]
}
if p, err := strconv.Atoi(hostParts[1]); err == nil {
port = p
}
} else {
host = *flHost
}
if *flDebug {
os.Setenv("DEBUG", "1")
}
@ -67,12 +56,17 @@ func main() {
flag.Usage()
return
}
if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors, *flDns); err != nil {
if err := daemon(*pidfile, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
log.Fatal(err)
os.Exit(-1)
}
} else {
if err := docker.ParseCommands(host, port, flag.Args()...); err != nil {
if len(flHosts) > 1 {
log.Fatal("Please specify only one -H")
return
}
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
log.Fatal(err)
os.Exit(-1)
}
@ -106,10 +100,7 @@ func removePidFile(pidfile string) {
}
}
func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns string) error {
if 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 /!\\")
}
func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
if err := createPidFile(pidfile); err != nil {
log.Fatal(err)
}
@ -131,6 +122,29 @@ func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns
if err != nil {
return err
}
return docker.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), server, true)
chErrors := make(chan error, len(protoAddrs))
for _, protoAddr := range protoAddrs {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
if protoAddrParts[0] == "unix" {
syscall.Unlink(protoAddrParts[1]);
} else if protoAddrParts[0] == "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 /!\\")
}
} else {
log.Fatal("Invalid protocol format.")
os.Exit(-1)
}
go func() {
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
}()
}
for i :=0 ; i < len(protoAddrs); i+=1 {
err := <-chErrors
if err != nil {
return err
}
}
return nil
}

Просмотреть файл

@ -1027,5 +1027,5 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a
To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
docker -d -H="192.168.1.9:4243" -api-enable-cors
docker -d -H="tcp://192.168.1.9:4243" -api-enable-cors

Просмотреть файл

@ -15,7 +15,7 @@ To list available commands, either run ``docker`` with no parameters or execute
$ docker
Usage: docker [OPTIONS] COMMAND [arg...]
-H="127.0.0.1:4243": Host:port to bind/connect to
-H=[tcp://127.0.0.1:4243]: tcp://host:port to bind/connect to or unix://path/to/socket to use
A self-sufficient runtime for linux containers.

Просмотреть файл

@ -33,11 +33,20 @@ Running an interactive shell
# allocate a tty, attach stdin and stdout
docker run -i -t base /bin/bash
Bind Docker to another host/port
--------------------------------
Bind Docker to another host/port or a unix socket
-------------------------------------------------
If you want Docker to listen to another port and bind to another ip
use -host and -port on both deamon and client
With -H it is possible to make the Docker daemon to listen on a specific ip and port. By default, it will listen on 127.0.0.1:4243 to allow only local connections but you can set it to 0.0.0.0:4243 or a specific host ip to give access to everybody.
Similarly, the Docker client can use -H to connect to a custom port.
-H accepts host and port assignment in the following format: tcp://[host][:port] or unix://path
For example:
* tcp://host -> tcp connection on host:4243
* tcp://host:port -> tcp connection on host:port
* tcp://:port -> tcp connection on 127.0.0.1:port
* unix://path/to/socket -> unix socket located at path/to/socket
.. code-block:: bash
@ -46,6 +55,17 @@ use -host and -port on both deamon and client
# Download a base image
docker -H :5555 pull base
You can use multiple -H, for example, if you want to listen
on both tcp and a unix socket
.. code-block:: bash
# Run docker in daemon mode
sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
# Download a base image
docker pull base
# OR
docker -H unix:///var/run/docker.sock pull base
Starting a long-running worker process
--------------------------------------

Просмотреть файл

@ -10,6 +10,7 @@ import (
"index/suffixarray"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
@ -652,3 +653,30 @@ func CheckLocalDns() bool {
}
return false
}
func ParseHost(host string, port int, addr string) string {
if strings.HasPrefix(addr, "unix://") {
return addr
}
if strings.HasPrefix(addr, "tcp://") {
addr = strings.TrimPrefix(addr, "tcp://")
}
if strings.Contains(addr, ":") {
hostParts := strings.Split(addr, ":")
if len(hostParts) != 2 {
log.Fatal("Invalid bind address format.")
os.Exit(-1)
}
if hostParts[0] != "" {
host = hostParts[0]
}
if p, err := strconv.Atoi(hostParts[1]); err == nil {
port = p
}
} else {
host = addr
}
return fmt.Sprintf("tcp://%s:%d", host, port)
}

Просмотреть файл

@ -274,3 +274,21 @@ func TestHumanSize(t *testing.T) {
t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
}
}
func TestParseHost(t *testing.T) {
if addr := ParseHost("127.0.0.1", 4243, "0.0.0.0"); addr != "tcp://0.0.0.0:4243" {
t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "0.0.0.1:5555"); addr != "tcp://0.0.0.1:5555" {
t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, ":6666"); addr != "tcp://127.0.0.1:6666" {
t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "tcp://:7777"); addr != "tcp://127.0.0.1:7777" {
t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "unix:///var/run/docker.sock"); addr != "unix:///var/run/docker.sock" {
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
}
}