diff --git a/proxy/client.go b/proxy/client.go index adfac43e..1decfe8d 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httputil" + "github.com/fsouza/go-dockerclient" . "github.com/weaveworks/weave/common" ) diff --git a/proxy/common.go b/proxy/common.go new file mode 100644 index 00000000..4d29d95e --- /dev/null +++ b/proxy/common.go @@ -0,0 +1,28 @@ +package proxy + +import ( + "os/exec" + "strings" + + "github.com/fsouza/go-dockerclient" + . "github.com/weaveworks/weave/common" +) + +func callWeave(args ...string) ([]byte, error) { + args = append([]string{"--local"}, args...) + Debug.Print("Calling weave", args) + cmd := exec.Command("./weave", args...) + cmd.Env = []string{"PROCFS=/hostproc", "PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"} + out, err := cmd.CombinedOutput() + return out, err +} + +func weaveCIDRsFromConfig(config *docker.Config) ([]string, bool) { + for _, e := range config.Env { + if strings.HasPrefix(e, "WEAVE_CIDR=") { + result := strings.Trim(e[11:], " ") + return strings.Split(strings.TrimSpace(result), " "), result != "" + } + } + return nil, false +} diff --git a/proxy/create_container_interceptor.go b/proxy/create_container_interceptor.go index ac022685..59c7e1eb 100644 --- a/proxy/create_container_interceptor.go +++ b/proxy/create_container_interceptor.go @@ -3,20 +3,17 @@ package proxy import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "net/http" + "strings" "github.com/fsouza/go-dockerclient" ) type createContainerInterceptor struct { - client *docker.Client -} - -func CreateContainerInterceptor(client *docker.Client) *createContainerInterceptor { - return &createContainerInterceptor{ - client: client, - } + client *docker.Client + withDNS bool } type createContainerRequestBody struct { @@ -37,14 +34,14 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) (*http.Re return nil, err } - if _, ok := weaveAddrFromConfig(container.Config); ok { - image, err := i.client.InspectImage(container.Config.Image) - if err != nil { + if _, ok := weaveCIDRsFromConfig(container.Config); ok { + i.addToVolumesFrom(container.HostConfig, "weaveproxy") + if err := i.setWeaveWaitEntrypoint(container.Config); err != nil { + return nil, err + } + if err := i.setWeaveDNS(&container); err != nil { return nil, err } - - addToVolumesFrom(container.HostConfig, "weaveproxy") - setWeaveWaitEntrypoint(image.Config, container.Config) } newBody, err := json.Marshal(container) @@ -57,30 +54,79 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) (*http.Re return r, nil } -func addToVolumesFrom(config *docker.HostConfig, mounts ...string) error { +func (i *createContainerInterceptor) addToVolumesFrom(config *docker.HostConfig, mounts ...string) { config.VolumesFrom = append(config.VolumesFrom, mounts...) - return nil } -func setWeaveWaitEntrypoint(image, container *docker.Config) { - container.Cmd = combineEntrypoints(image, container) - container.Entrypoint = []string{"/home/weavewait/weavewait"} -} +func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error { + var image *docker.Config + if !configHasEntrypoint(container) || !configHasCmd(container) { + imageInfo, err := i.client.InspectImage(container.Image) + if err != nil { + return err + } + image = imageInfo.Config + } -func combineEntrypoints(image, container *docker.Config) []string { var entry, command []string - if len(container.Entrypoint) > 0 { + if configHasEntrypoint(container) { entry = container.Entrypoint - } else if len(image.Entrypoint) > 0 { + } else if configHasEntrypoint(image) { entry = image.Entrypoint } - if len(container.Cmd) > 0 { + if configHasCmd(container) { command = container.Cmd - } else if len(image.Cmd) > 0 { + } else if configHasCmd(image) { command = image.Cmd } - return append(entry, command...) + container.Entrypoint = []string{"/home/weavewait/weavewait"} + container.Cmd = append(entry, command...) + return nil +} + +func configHasEntrypoint(c *docker.Config) bool { + return c != nil && len(c.Entrypoint) > 0 +} +func configHasCmd(c *docker.Config) bool { + return c != nil && len(c.Cmd) > 0 +} + +func (i *createContainerInterceptor) setWeaveDNS(container *createContainerRequestBody) error { + if !i.withDNS { + return nil + } + + dockerBridgeIP, err := getDockerBridgeIP() + if err != nil { + return err + } + container.HostConfig.DNS = append(container.HostConfig.DNS, dockerBridgeIP) + + if len(container.HostConfig.DNSSearch) == 0 { + container.HostConfig.DNSSearch = []string{"."} + } + return nil +} + +func getDockerBridgeIP() (string, error) { + out, err := callWeave("dns-args") + if err != nil { + return "", fmt.Errorf("Error fetching weave dns-args: %s", err) + } + + var dockerBridgeIP string + segments := strings.Split(string(out), " ") + for i := 0; i < len(segments)-1; i++ { + if segments[i] == "--dns" { + dockerBridgeIP = segments[i+1] + } + } + + if dockerBridgeIP == "" { + return "", fmt.Errorf("Docker bridge IP not found in weave output: %s", string(out)) + } + return dockerBridgeIP, nil } func (i *createContainerInterceptor) InterceptResponse(res *http.Response) (*http.Response, error) { diff --git a/proxy/proxy.go b/proxy/proxy.go index d36e6347..03b1fa99 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -11,13 +11,28 @@ import ( . "github.com/weaveworks/weave/common" ) -const ( - RAW_STREAM = "application/vnd.docker.raw-stream" -) +type Proxy struct { + Dial func() (net.Conn, error) + client *docker.Client + withDNS bool +} -type proxy struct { - Dial func() (net.Conn, error) - client *docker.Client +func NewProxy(targetURL string, withDNS bool) (*Proxy, error) { + u, err := url.Parse(targetURL) + if err != nil { + return nil, err + } + client, err := docker.NewClient(targetURL) + if err != nil { + return nil, err + } + return &Proxy{ + Dial: func() (net.Conn, error) { + return net.Dial(targetNetwork(u), targetAddress(u)) + }, + client: client, + withDNS: withDNS, + }, nil } func targetNetwork(u *url.URL) string { @@ -34,23 +49,6 @@ func targetAddress(u *url.URL) (addr string) { return } -func NewProxy(targetUrl string) (*proxy, error) { - u, err := url.Parse(targetUrl) - if err != nil { - return nil, err - } - client, err := docker.NewClient(targetUrl) - if err != nil { - return nil, err - } - return &proxy{ - Dial: func() (net.Conn, error) { - return net.Dial(targetNetwork(u), targetAddress(u)) - }, - client: client, - }, nil -} - func isWeaveStatus(r *http.Request) bool { return strings.HasPrefix(r.URL.Path, "/weave") } @@ -65,32 +63,32 @@ func isStartContainer(r *http.Request) bool { return err == nil && ok } -func (proxy *proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { Info.Printf("%s %s", r.Method, r.URL) switch { case isCreateContainer(r): - proxy.CreateContainer(w, r) + proxy.createContainer(w, r) case isStartContainer(r): - proxy.StartContainer(w, r) + proxy.startContainer(w, r) case isWeaveStatus(r): - proxy.WeaveStatus(w, r) + proxy.weaveStatus(w, r) default: - proxy.ProxyRequest(w, r) + proxy.proxyRequest(w, r) } } -func (proxy *proxy) WeaveStatus(w http.ResponseWriter, r *http.Request) { +func (proxy *Proxy) weaveStatus(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func (proxy *proxy) CreateContainer(w http.ResponseWriter, r *http.Request) { - NewClient(proxy.Dial, CreateContainerInterceptor(proxy.client)).ServeHTTP(w, r) +func (proxy *Proxy) createContainer(w http.ResponseWriter, r *http.Request) { + newClient(proxy.Dial, &createContainerInterceptor{proxy.client, proxy.withDNS}).ServeHTTP(w, r) } -func (proxy *proxy) StartContainer(w http.ResponseWriter, r *http.Request) { - NewClient(proxy.Dial, StartContainerInterceptor(proxy.client)).ServeHTTP(w, r) +func (proxy *Proxy) startContainer(w http.ResponseWriter, r *http.Request) { + newClient(proxy.Dial, &startContainerInterceptor{proxy.client, proxy.withDNS}).ServeHTTP(w, r) } -func (proxy *proxy) ProxyRequest(w http.ResponseWriter, r *http.Request) { - NewClient(proxy.Dial, NullInterceptor).ServeHTTP(w, r) +func (proxy *Proxy) proxyRequest(w http.ResponseWriter, r *http.Request) { + newClient(proxy.Dial, &nullInterceptor{}).ServeHTTP(w, r) } diff --git a/proxy/start_container_interceptor.go b/proxy/start_container_interceptor.go index 5cf20fe6..c7fa4b8b 100644 --- a/proxy/start_container_interceptor.go +++ b/proxy/start_container_interceptor.go @@ -2,7 +2,6 @@ package proxy import ( "net/http" - "os/exec" "regexp" "strings" @@ -11,13 +10,8 @@ import ( ) type startContainerInterceptor struct { - client *docker.Client -} - -func StartContainerInterceptor(client *docker.Client) Interceptor { - return &startContainerInterceptor{ - client: client, - } + client *docker.Client + withDNS bool } func (i *startContainerInterceptor) InterceptRequest(r *http.Request) (*http.Request, error) { @@ -25,21 +19,19 @@ func (i *startContainerInterceptor) InterceptRequest(r *http.Request) (*http.Req } func (i *startContainerInterceptor) InterceptResponse(res *http.Response) (*http.Response, error) { - containerId := containerFromPath(res.Request.URL.Path) + containerID := containerFromPath(res.Request.URL.Path) - container, err := i.client.InspectContainer(containerId) + container, err := i.client.InspectContainer(containerID) if err != nil { Warning.Print("Error Inspecting Container: ", err) return res, nil } - if cidr, ok := weaveAddrFromConfig(container.Config); ok { - Info.Printf("Container %s was started with CIDR \"%s\"", containerId, cidr) - args := []string{"attach"} - args = append(args, strings.Split(strings.TrimSpace(cidr), " ")...) - args = append(args, containerId) - if out, err := callWeave(args...); err != nil { - Warning.Print("Calling weave failed: ", err, string(out)) + if cidrs, ok := weaveCIDRsFromConfig(container.Config); ok { + Info.Printf("Container %s was started with CIDR \"%s\"", containerID, strings.Join(cidrs, " ")) + if err := i.attachContainerToWeave(containerID, cidrs); err != nil { + Warning.Print("Attaching container to weave failed: ", err) + return res, nil } } else { Debug.Print("No Weave CIDR, ignoring") @@ -55,21 +47,10 @@ func containerFromPath(path string) string { return "" } -func callWeave(args ...string) ([]byte, error) { - args = append([]string{"--local"}, args...) - Debug.Print("Calling weave", args) - cmd := exec.Command("./weave", args...) - cmd.Env = []string{"PROCFS=/hostproc", "PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"} - out, err := cmd.CombinedOutput() - return out, err -} - -func weaveAddrFromConfig(config *docker.Config) (string, bool) { - for _, e := range config.Env { - if strings.HasPrefix(e, "WEAVE_CIDR=") { - result := strings.Trim(e[11:], " ") - return result, result != "" - } - } - return "", false +func (i *startContainerInterceptor) attachContainerToWeave(containerID string, cidrs []string) error { + args := []string{"attach"} + args = append(args, cidrs...) + args = append(args, containerID) + _, err := callWeave(args...) + return err } diff --git a/site/experimental-features.md b/site/experimental-features.md index bc3c2a24..3d31fb21 100644 --- a/site/experimental-features.md +++ b/site/experimental-features.md @@ -47,6 +47,18 @@ or, equivalently with Multiple IP addresses and networks can be supplied in the WEAVE_CIDR variable by space-separating them, as in `WEAVE_CIDR=10.2.1.1/24 10.2.2.1/24`. +#### Usage with WeaveDNS + +Containers started via the proxy can be automatically configured to use WeaveDNS for name resolution. To accomplish this we need to launch the proxy with the `--with-dns` option + + host1$ weave launch + host1$ weave launch-dns 10.2.254.1/24 + host1$ weave launch-proxy --with-dns + +With this done, any containers launched through the proxy will use weaveDNS for name resolution. WeaveDNS is used in addition to any dns servers specified via the `--dns` option. More details on weaveDNS can be found in the [weaveDNS documentation](weavedns-readme.html). + +More details on weaveDNS can be found in the [weaveDNS documentation](weavedns-readme.html). + #### Limitations * The proxy does not currently support TLS. diff --git a/test/620_proxy_entrypoint_inheritance_test.sh b/test/620_proxy_entrypoint_inheritance_test.sh new file mode 100755 index 00000000..2bb2fce6 --- /dev/null +++ b/test/620_proxy_entrypoint_inheritance_test.sh @@ -0,0 +1,14 @@ +#! /bin/bash + +. ./config.sh + +start_suite "Proxy uses entrypoint from the image with weavewait" +weave_on $HOST1 launch-proxy +docker_on $HOST1 build -t inspect-ethwe - <<- EOF + FROM gliderlabs/alpine + ENTRYPOINT ["ip", "link", "show", "ethwe"] +EOF + +assert_raises "docker_proxy_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.1/24' inspect-ethwe | grep 'state UP'" + +end_suite diff --git a/test/630_proxy_dns_test.sh b/test/630_proxy_dns_test.sh new file mode 100755 index 00000000..e4b94fee --- /dev/null +++ b/test/630_proxy_dns_test.sh @@ -0,0 +1,18 @@ +#! /bin/bash + +. ./config.sh + +C1=10.2.0.78 +C2=10.2.0.34 +NAME=seetwo.weave.local + +start_suite "Proxy registers containers with dns" + +weave_on $HOST1 launch-dns 10.2.254.1/24 +weave_on $HOST1 launch-proxy --with-dns +docker_proxy_on $HOST1 run -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $NAME gliderlabs/alpine /bin/sh +docker_proxy_on $HOST1 run -e WEAVE_CIDR=$C1/24 -dt --name=c1 aanand/docker-dnsutils /bin/sh + +assert_dns_record $HOST1 c1 $NAME $C2 + +end_suite diff --git a/test/650_proxy_entrypoint_override_test.sh b/test/650_proxy_entrypoint_override_test.sh new file mode 100755 index 00000000..4f826408 --- /dev/null +++ b/test/650_proxy_entrypoint_override_test.sh @@ -0,0 +1,14 @@ +#! /bin/bash + +. ./config.sh + +start_suite "Proxy allows overridden entrypoint from the container with weavewait" +weave_on $HOST1 launch-proxy +docker_on $HOST1 build -t false - <<- EOF + FROM gliderlabs/alpine + ENTRYPOINT ["/bin/false"] +EOF + +assert_raises "docker_proxy_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.1/24' --entrypoint '/sbin/ip' false link show ethwe | grep 'state UP'" + +end_suite diff --git a/weave b/weave index 39426d9f..2c16c4b8 100755 --- a/weave +++ b/weave @@ -27,7 +27,7 @@ usage() { echo "weave setup" echo "weave launch [-password ] [-nickname ] [-iprange ] ..." echo "weave launch-dns " - echo "weave launch-proxy [-H ]" + echo "weave launch-proxy [-H ] [--with-dns]" echo "weave connect " echo "weave forget " echo "weave run [--with-dns] [ ...] ..." diff --git a/weaveexec/main.go b/weaveexec/main.go index 548aafb9..65cc14ac 100644 --- a/weaveexec/main.go +++ b/weaveexec/main.go @@ -1,27 +1,42 @@ package main import ( - "flag" + "fmt" "net/http" + "code.google.com/p/getopt" . "github.com/weaveworks/weave/common" "github.com/weaveworks/weave/proxy" ) +var ( + defaultTarget = "unix:///var/run/docker.sock" + defaultListen = ":12375" +) + func main() { var target, listen string - var debug bool + var withDNS, debug bool - flag.StringVar(&target, "H", "unix:///var/run/docker.sock", "docker daemon URL to proxy") - flag.StringVar(&listen, "L", ":12375", "address on which to listen") - flag.BoolVar(&debug, "debug", false, "log debugging information") - flag.Parse() + getopt.BoolVarLong(&debug, "debug", 'd', "log debugging information") + getopt.StringVar(&target, 'H', fmt.Sprintf("docker daemon URL to proxy (default %s)", defaultTarget)) + getopt.StringVar(&listen, 'L', fmt.Sprintf("address on which to listen (default %s)", defaultListen)) + getopt.BoolVarLong(&withDNS, "with-dns", 'w', "register connected containers with weaveDNS") + getopt.Parse() + + if target == "" { + target = defaultTarget + } + + if listen == "" { + listen = defaultListen + } if debug { InitDefaultLogging(true) } - p, err := proxy.NewProxy(target) + p, err := proxy.NewProxy(target, withDNS) if err != nil { Error.Fatalf("Could not start proxy: %s", err) }