From d8e1d0798e544880ad4f031d6262036453f9778d Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 18 May 2015 10:26:19 +0100 Subject: [PATCH 01/18] replace test usage of ifconfig with ip link show --- test/400_weave_local_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/400_weave_local_test.sh b/test/400_weave_local_test.sh index c4f9eab4..d86e2ece 100755 --- a/test/400_weave_local_test.sh +++ b/test/400_weave_local_test.sh @@ -10,7 +10,7 @@ run_on $HOST1 sudo ./weave --local launch -iprange 10.2.5.0/24 assert_raises "docker_on $HOST1 ps | grep weave" run_on $HOST1 sudo ./weave --local run 10.2.6.5/24 -ti --name=c1 gliderlabs/alpine /bin/sh -assert_raises "exec_on $HOST1 c1 ifconfig | grep ethwe" +assert_raises "exec_on $HOST1 c1 ip link show ethwe" run_on $HOST1 sudo ./weave --local run -ti --name=c2 gliderlabs/alpine /bin/sh assert_raises "exec_on $HOST1 c2 ifconfig | grep ethwe" From 6d7de3ddff809034a524ec9ad214d34f3bb33764 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Fri, 1 May 2015 15:19:31 +0100 Subject: [PATCH 02/18] Proxy implementation This adds a docker remote API proxy to the weaveexec image. The proxy has hooks for (post-) container start and (pre-) container create; the former attaches a weave interface to a container given a WEAVE_CIDR environment entry, and the latter adjusts the entrypoint of a container to wait for the ethwe interface. Notes: None of the hooks need to inspect/change large bodies. In order to avoid a crash when handling images larger than memory we need to ensure that the entire request body is never loaded into memory (i.e. No ioutils.ReadAll). When proxying a raw-stream response, we can't use http.WriteHeader. It sets the Transfer-Encoding to chunked, which will throw off the clients. Instead we have to hijack the socket, and write out http status code manually. For chunked transfer-encodings, clients (docker-py) depend on each http chunk containing exactly one JSON object. Therefore, we cannot re-chunk the body, and have to pass it across verbatim. Proxy needs to pass multi-cidrs separately when calling weave. Otherwise they get smashed together on the command line, and the call fails. When creating the image, if no entrypoint is specified on the container, it should use the entrypoint of the image. Code for this is based on powerstrip-weave. We handle WEAVE_CIDR='' by acting like it is not set. The reason being, that because WEAVE_CIDR is inherited from images (along with env vars), setting it to blank is equivalent to unsetting it. --- .gitignore | 1 + Makefile | 8 +- proxy/client.go | 171 ++++++++++++++++++++++++++ proxy/create_container_interceptor.go | 88 +++++++++++++ proxy/interceptor.go | 19 +++ proxy/proxy.go | 96 +++++++++++++++ proxy/start_container_interceptor.go | 75 +++++++++++ weaveexec/Dockerfile | 4 + weaveexec/main.go | 39 ++++++ 9 files changed, 498 insertions(+), 3 deletions(-) create mode 100644 proxy/client.go create mode 100644 proxy/create_container_interceptor.go create mode 100644 proxy/interceptor.go create mode 100644 proxy/proxy.go create mode 100644 proxy/start_container_interceptor.go create mode 100644 weaveexec/main.go diff --git a/.gitignore b/.gitignore index 94b5d700..e685a1b6 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ releases weave.tar weavedns.tar weaveexec.tar +weaveexec/proxy weaveexec/weave weaveexec/sigproxy weaveexec/docker*.tgz diff --git a/Makefile b/Makefile index 601e6430..36fef509 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD) WEAVER_EXE=weaver/weaver WEAVEDNS_EXE=weavedns/weavedns SIGPROXY_EXE=sigproxy/sigproxy +PROXY_EXE=weaveexec/proxy WEAVER_IMAGE=$(DOCKERHUB_USER)/weave WEAVEDNS_IMAGE=$(DOCKERHUB_USER)/weavedns WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec @@ -27,9 +28,9 @@ all: $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT) travis: $(WEAVER_EXE) $(WEAVEDNS_EXE) update: - go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE)) ./$(dir $(SIGPROXY_EXE)) + go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE)) ./$(dir $(SIGPROXY_EXE)) ./$(dir $(PROXY_EXE)) -$(WEAVER_EXE) $(WEAVEDNS_EXE): common/*.go +$(WEAVER_EXE) $(WEAVEDNS_EXE) $(PROXY_EXE): common/*.go go get -tags netgo ./$(@D) go build -ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo -o $@ ./$(@D) @strings $@ | grep cgo_stub\\\.go >/dev/null || { \ @@ -43,6 +44,7 @@ $(WEAVER_EXE) $(WEAVEDNS_EXE): common/*.go $(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go weaver/main.go $(WEAVEDNS_EXE): nameserver/*.go weavedns/main.go +$(PROXY_EXE): proxy/*.go weaveexec/main.go # Sigproxy needs separate rule as it fails the netgo check in the main # build stanza due to not importing net package @@ -57,7 +59,7 @@ $(WEAVEDNS_EXPORT): weavedns/Dockerfile $(WEAVEDNS_EXE) $(SUDO) docker build -t $(WEAVEDNS_IMAGE) weavedns $(SUDO) docker save $(WEAVEDNS_IMAGE):latest > $@ -$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) +$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(PROXY_EXE) cp weave weaveexec/weave cp sigproxy/sigproxy weaveexec/sigproxy cp $(DOCKER_DISTRIB) weaveexec/docker.tgz diff --git a/proxy/client.go b/proxy/client.go new file mode 100644 index 00000000..adfac43e --- /dev/null +++ b/proxy/client.go @@ -0,0 +1,171 @@ +package proxy + +import ( + "bufio" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httputil" + + . "github.com/weaveworks/weave/common" +) + +type client struct { + interceptor + Dial func() (net.Conn, error) +} + +func newClient(dial func() (net.Conn, error), i interceptor) *client { + return &client{ + interceptor: i, + Dial: dial, + } +} + +func (c *client) ServeHTTP(w http.ResponseWriter, r *http.Request) { + req, err := c.InterceptRequest(r) + if err == docker.ErrNoSuchImage { + http.NotFound(w, r) + return + } + if err != nil { + http.Error(w, "Unable to create proxied request", http.StatusInternalServerError) + Warning.Print("Error intercepting request: ", err) + return + } + + conn, err := c.Dial() + if err != nil { + http.Error(w, "Could not connect to target", http.StatusInternalServerError) + Warning.Print(err) + return + } + client := httputil.NewClientConn(conn, nil) + defer client.Close() + + resp, err := client.Do(req) + if err != nil && err != httputil.ErrPersistEOF { + http.Error(w, fmt.Sprintf("Could not make request to target: %s", err.Error()), http.StatusInternalServerError) + Warning.Print("Error forwarding request: ", err) + return + } + resp, err = c.InterceptResponse(resp) + if err != nil { + http.Error(w, "Unable to intercept response", http.StatusInternalServerError) + Warning.Print("Error intercepting response: ", err) + return + } + + hdr := w.Header() + for k, vs := range resp.Header { + for _, v := range vs { + hdr.Add(k, v) + } + } + Debug.Printf("Response from target: %s %v", resp.Status, w.Header()) + + if resp.Header.Get("Content-Type") == "application/vnd.docker.raw-stream" { + doRawStream(w, resp, client) + } else if resp.TransferEncoding != nil && resp.TransferEncoding[0] == "chunked" { + doChunkedResponse(w, resp, client) + } else { + w.WriteHeader(resp.StatusCode) + if _, err := io.Copy(w, resp.Body); err != nil { + Warning.Print(err) + } + } +} + +func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) { + down, downBuf, up, rem, err := hijack(w, client) + if err != nil { + Error.Fatal(w, "Unable to hijack connection for raw stream mode", http.StatusInternalServerError) + return + } + defer down.Close() + defer up.Close() + + if _, err := down.Write([]byte("HTTP/1.1 200 OK\n")); err != nil { + Warning.Print(err) + return + } + + if err := resp.Header.Write(down); err != nil { + Warning.Print(err) + return + } + + if _, err := down.Write([]byte("\n")); err != nil { + Warning.Print(err) + return + } + + end := make(chan bool) + go func() { + defer close(end) + if _, err := io.Copy(up, downBuf); err != nil { + Warning.Print(err) + } + err = up.(interface { + CloseWrite() error + }).CloseWrite() + if err != nil { + Debug.Printf("Error Closing upstream: %s", err) + } + }() + + if _, err := io.Copy(down, io.MultiReader(rem, up)); err != nil { + Warning.Print(err) + } + err = down.(interface { + CloseWrite() error + }).CloseWrite() + if err != nil { + Debug.Printf("Error Closing downstream: %s", err) + } + + <-end +} + +func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) { + // Because we can't go back to request/response after we + // hijack the connection, we need to close it and make the + // client open another. + w.Header().Add("Connection", "close") + w.WriteHeader(resp.StatusCode) + + down, _, up, rem, err := hijack(w, client) + if err != nil { + Error.Fatal("Unable to hijack response stream for chunked response", http.StatusInternalServerError) + return + } + defer up.Close() + defer down.Close() + // Copy the chunked response body to downstream, + // stopping at the end of the chunked section. + rawResponseBody := io.MultiReader(rem, up) + if _, err := io.Copy(ioutil.Discard, httputil.NewChunkedReader(io.TeeReader(rawResponseBody, down))); err != nil { + Error.Fatal("Error copying chunked response body", http.StatusInternalServerError) + return + } + resp.Trailer.Write(down) + // a chunked response ends with a CRLF + down.Write([]byte("\r\n")) +} + +func hijack(w http.ResponseWriter, client *httputil.ClientConn) (down net.Conn, downBuf *bufio.ReadWriter, up net.Conn, rem io.Reader, err error) { + hj, ok := w.(http.Hijacker) + if !ok { + err = errors.New("Unable to cast to Hijack") + return + } + down, downBuf, err = hj.Hijack() + if err != nil { + return + } + up, rem = client.Hijack() + return +} diff --git a/proxy/create_container_interceptor.go b/proxy/create_container_interceptor.go new file mode 100644 index 00000000..c96431c4 --- /dev/null +++ b/proxy/create_container_interceptor.go @@ -0,0 +1,88 @@ +package proxy + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/fsouza/go-dockerclient" +) + +type createContainerInterceptor struct { + client *docker.Client +} + +func CreateContainerInterceptor(client *docker.Client) *createContainerInterceptor { + return &createContainerInterceptor{ + client: client, + } +} + +type createContainerRequestBody struct { + *docker.Config + HostConfig *docker.HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"` + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` +} + +func (i *createContainerInterceptor) InterceptRequest(r *http.Request) (*http.Request, error) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + r.Body.Close() + + container := createContainerRequestBody{} + if err := json.Unmarshal(body, &container); err != nil { + return nil, err + } + + if _, ok := weaveAddrFromConfig(container.Config); ok { + image, err := i.client.InspectImage(container.Config.Image) + if err != nil { + return nil, err + } + + addToVolumesFrom(container.HostConfig, "weaveproxy") + setWeaveWaitEntrypoint(image.Config, container.Config) + } + + newBody, err := json.Marshal(container) + if err != nil { + return nil, err + } + r.Body = ioutil.NopCloser(bytes.NewReader(newBody)) + r.ContentLength = int64(len(newBody)) + + return r, nil +} + +func addToVolumesFrom(config *docker.HostConfig, mounts ...string) error { + config.VolumesFrom = append(config.VolumesFrom, mounts...) + return nil +} + +func setWeaveWaitEntrypoint(image, container *docker.Config) { + container.Cmd = combineEntrypoints(image, container) + container.Entrypoint = []string{"/home/weavewait/wait-for-weave"} +} + +func combineEntrypoints(image, container *docker.Config) []string { + var entry, command []string + if len(container.Entrypoint) > 0 { + entry = container.Entrypoint + } else if len(image.Entrypoint) > 0 { + entry = image.Entrypoint + } + if len(container.Cmd) > 0 { + command = container.Cmd + } else if len(image.Cmd) > 0 { + command = image.Cmd + } + + return append(entry, command...) +} + +func (i *createContainerInterceptor) InterceptResponse(res *http.Response) (*http.Response, error) { + return res, nil +} diff --git a/proxy/interceptor.go b/proxy/interceptor.go new file mode 100644 index 00000000..203a0d03 --- /dev/null +++ b/proxy/interceptor.go @@ -0,0 +1,19 @@ +package proxy + +import "net/http" + +type interceptor interface { + InterceptRequest(*http.Request) (*http.Request, error) + InterceptResponse(*http.Response) (*http.Response, error) +} + +type nullInterceptor struct { +} + +func (i nullInterceptor) InterceptRequest(r *http.Request) (*http.Request, error) { + return r, nil +} + +func (i nullInterceptor) InterceptResponse(r *http.Response) (*http.Response, error) { + return r, nil +} diff --git a/proxy/proxy.go b/proxy/proxy.go new file mode 100644 index 00000000..d36e6347 --- /dev/null +++ b/proxy/proxy.go @@ -0,0 +1,96 @@ +package proxy + +import ( + "net" + "net/http" + "net/url" + "regexp" + "strings" + + "github.com/fsouza/go-dockerclient" + . "github.com/weaveworks/weave/common" +) + +const ( + RAW_STREAM = "application/vnd.docker.raw-stream" +) + +type proxy struct { + Dial func() (net.Conn, error) + client *docker.Client +} + +func targetNetwork(u *url.URL) string { + return u.Scheme +} + +func targetAddress(u *url.URL) (addr string) { + switch u.Scheme { + case "tcp": + addr = u.Host + case "unix": + addr = u.Path + } + 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") +} + +func isCreateContainer(r *http.Request) bool { + ok, err := regexp.MatchString("/v[0-9\\.]*/containers/create", r.URL.Path) + return err == nil && ok +} + +func isStartContainer(r *http.Request) bool { + ok, err := regexp.MatchString("^/v[0-9\\.]*/containers/[^/]*/start$", r.URL.Path) + return err == nil && ok +} + +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) + case isStartContainer(r): + proxy.StartContainer(w, r) + case isWeaveStatus(r): + proxy.WeaveStatus(w, r) + default: + proxy.ProxyRequest(w, r) + } +} + +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) StartContainer(w http.ResponseWriter, r *http.Request) { + NewClient(proxy.Dial, StartContainerInterceptor(proxy.client)).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 new file mode 100644 index 00000000..5cf20fe6 --- /dev/null +++ b/proxy/start_container_interceptor.go @@ -0,0 +1,75 @@ +package proxy + +import ( + "net/http" + "os/exec" + "regexp" + "strings" + + "github.com/fsouza/go-dockerclient" + . "github.com/weaveworks/weave/common" +) + +type startContainerInterceptor struct { + client *docker.Client +} + +func StartContainerInterceptor(client *docker.Client) Interceptor { + return &startContainerInterceptor{ + client: client, + } +} + +func (i *startContainerInterceptor) InterceptRequest(r *http.Request) (*http.Request, error) { + return r, nil +} + +func (i *startContainerInterceptor) InterceptResponse(res *http.Response) (*http.Response, error) { + containerId := containerFromPath(res.Request.URL.Path) + + 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)) + } + } else { + Debug.Print("No Weave CIDR, ignoring") + } + + return res, nil +} + +func containerFromPath(path string) string { + if subs := regexp.MustCompile("^/v[0-9\\.]*/containers/([^/]*)/.*").FindStringSubmatch(path); subs != nil { + return subs[1] + } + 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 +} diff --git a/weaveexec/Dockerfile b/weaveexec/Dockerfile index e7134926..6295b8a6 100644 --- a/weaveexec/Dockerfile +++ b/weaveexec/Dockerfile @@ -15,6 +15,10 @@ WORKDIR /home/weave ADD ./weave /home/weave/ ADD ./sigproxy /home/weave/ +ADD ./proxy /home/weave/ ADD ./docker.tgz / +ADD ./wait-for-weave /home/weavewait/wait-for-weave +VOLUME /home/weavewait + ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"] diff --git a/weaveexec/main.go b/weaveexec/main.go new file mode 100644 index 00000000..548aafb9 --- /dev/null +++ b/weaveexec/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "flag" + "net/http" + + . "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/proxy" +) + +func main() { + var target, listen string + var 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() + + if debug { + InitDefaultLogging(true) + } + + p, err := proxy.NewProxy(target) + if err != nil { + Error.Fatalf("Could not start proxy: %s", err) + } + s := &http.Server{ + Addr: listen, + Handler: p, + } + + Info.Printf("Listening on %s", listen) + Info.Printf("Proxying %s", target) + + if err := s.ListenAndServe(); err != nil { + Error.Fatalf("Could not listen on %s: %s", listen, err) + } +} From 6c4271f15d689ad0ce01f5e7e6379f6a7990bff2 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Tue, 5 May 2015 15:17:11 +0100 Subject: [PATCH 03/18] Add launch-proxy command to cli Use curl /weave to wait for the proxy to boot before returning. --- weave | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/weave b/weave index 5cf1da6e..39426d9f 100755 --- a/weave +++ b/weave @@ -27,6 +27,7 @@ usage() { echo "weave setup" echo "weave launch [-password ] [-nickname ] [-iprange ] ..." echo "weave launch-dns " + echo "weave launch-proxy [-H ]" echo "weave connect " echo "weave forget " echo "weave run [--with-dns] [ ...] ..." @@ -679,6 +680,63 @@ get_ipam_cidr() { fi } +###################################################################### +# weave proxy helpers +###################################################################### + +PROXY_PORT=12375 +PROXY_CONTAINER_NAME=weaveproxy + +proxy_host() { + case $DOCKER_HOST in + tcp://*) + PROXY_HOST=$(echo $DOCKER_HOST | sed -e "s|tcp://\([^:]*\):[0-9]*|\1|") + ;; + *) + PROXY_HOST="localhost" + ;; + esac +} + +wait_for_proxy_to_boot() { + proxy_host + for n in `seq 1 10`; do + if curl --max-time 1 --connect-timeout 1 -s -X GET http://$PROXY_HOST:$PROXY_PORT/weave ; then + return 0 + fi + sleep 1 + done + echo "Proxy did not start within 10 seconds" >&2 + return 1 +} + +ensure_proxy_running() { + case $(docker inspect --format '{{.State.Running}} {{.Config.Image}}' $PROXY_CONTAINER_NAME 2>/dev/null) in + "true $EXEC_IMAGE") + return 0 + ;; + true*) + echo "Proxy already running with unknown image" >&2 + exit 1 + ;; + false*) + docker rm -f $PROXY_CONTAINER_NAME >/dev/null + ;; + esac + # Set WEAVEPROXY_DOCKER_ARGS in the environment in order to supply + # additional parameters, such as resource limits, to docker + # when launching the weave proxy container. + PROXY_CONTAINER=$(docker run --privileged --net=host -d \ + --name=$PROXY_CONTAINER_NAME \ + -p $PROXY_PORT:$PROXY_PORT/tcp -v /var/run/docker.sock:/var/run/docker.sock \ + -v /proc:/hostproc -e PROCFS=/hostproc \ + --entrypoint=/home/weave/proxy \ + $WEAVE_DOCKER_ARGS $WEAVEPROXY_DOCKER_ARGS \ + $EXEC_IMAGE "$@") + + wait_for_proxy_to_boot +} + ########################################################################################## [ $(id -u) = 0 ] || { @@ -807,6 +865,10 @@ case "$COMMAND" in populate_dns echo $DNS_CONTAINER ;; + launch-proxy) + ensure_proxy_running "$@" + echo $PROXY_CONTAINER + ;; connect) [ $# -eq 1 ] || usage http_call $CONTAINER_NAME $HTTP_PORT POST /connect -d "peer=$1" From 5a6a67f632141cd5f6589f127ec17712496cc18d Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 13 May 2015 13:57:27 +0100 Subject: [PATCH 04/18] Add integration test for the proxy, using docker-py client --- test/600_proxy_docker_py_test.sh | 16 ++++++++++++++++ test/610_proxy_wait_for_weave_test.sh | 15 +++++++++++++++ test/config.sh | 7 +++++++ 3 files changed, 38 insertions(+) create mode 100755 test/600_proxy_docker_py_test.sh create mode 100755 test/610_proxy_wait_for_weave_test.sh diff --git a/test/600_proxy_docker_py_test.sh b/test/600_proxy_docker_py_test.sh new file mode 100755 index 00000000..dc5e3282 --- /dev/null +++ b/test/600_proxy_docker_py_test.sh @@ -0,0 +1,16 @@ +#! /bin/bash + +. ./config.sh + +start_suite "Run docker-py test suite against the proxy" + +weave_on $HOST1 launch-proxy +assert_raises "docker_on $HOST1 ps | grep weaveproxy" +assert_raises "docker_on $HOST1 run \ + -e NOT_ON_HOST=true \ + -e DOCKER_HOST=tcp://172.17.42.1:12375 \ + -v /tmp:/tmp \ + -v /var/run/docker.sock:/var/run/docker.sock \ + joffrey/docker-py python tests/integration_test.py" + +end_suite diff --git a/test/610_proxy_wait_for_weave_test.sh b/test/610_proxy_wait_for_weave_test.sh new file mode 100755 index 00000000..c1cb054c --- /dev/null +++ b/test/610_proxy_wait_for_weave_test.sh @@ -0,0 +1,15 @@ +#! /bin/bash + +. ./config.sh + +start_suite "Proxy waits for weave to be ready before running container commands" +weave_on $HOST1 launch-proxy +BASE_IMAGE=ubuntu +# Ensure the base image does not exist, so that it will be pulled +if (docker_on $HOST1 images $BASE_IMAGE | grep $BASE_IMAGE); then + docker_on $HOST1 rmi $BASE_IMAGE +fi + +assert_raises "docker_proxy_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.1/24' $BASE_IMAGE ip link show ethwe | grep 'state UP'" + +end_suite diff --git a/test/config.sh b/test/config.sh index 3983c826..48de3afb 100644 --- a/test/config.sh +++ b/test/config.sh @@ -76,6 +76,13 @@ docker_on() { docker -H tcp://$host:2375 "$@" } +docker_proxy_on() { + host=$1 + shift 1 + greyly echo "Docker (with proxy) on $host: $@" >&2 + docker -H tcp://$host:12375 "$@" +} + weave_on() { host=$1 shift 1 From 9389001a1d144b08f518020a5bcb3094b85a15a3 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 18 May 2015 11:03:04 +0100 Subject: [PATCH 05/18] [docs] Initial version of proxy docs --- site/experimental-features.md | 53 +++++++++++++++++++++++++++++++++++ site/index.md | 1 + 2 files changed, 54 insertions(+) create mode 100644 site/experimental-features.md diff --git a/site/experimental-features.md b/site/experimental-features.md new file mode 100644 index 00000000..bc3c2a24 --- /dev/null +++ b/site/experimental-features.md @@ -0,0 +1,53 @@ +--- +title: Experimental features +layout: default +--- + +# Experimental features + + * [Proxy](#proxy) + +### Proxy + +Instead of the weave command-line utility, you may prefer to use the standard +docker command-line utility to manage containers. This will allow containers to +be started in foreground mode. It will also ensure that the weave network +interface is available before running your container. + +#### Setup + +To do this, while still connecting containers to the weave network, you can use +the weave proxy. We can start the proxy with + + host1$ weave launch-proxy + +By default, the proxy will connect to docker at `unix:///var/run/docker.sock`, +and listen on port 12375. However, we can adjust the connection to docker via +the `-H` argument. All docker commands can be run via the proxy, so it is safe +to globally adjust your `DOCKER_HOST`. + + host1$ export DOCKER_HOST="tcp://host1:12375" + host1$ docker ps + +#### Usage + +When containers are created via the weave proxy, their entrypoint will be +modified to wait for the weave network interface to become available. When they are +started via the weave proxy, any IP addresses and networks specified in the +`WEAVE_CIDR` environment variable will be attached. We can create and start a +container via the weave proxy with + + host1$ docker run -e WEAVE_CIDR=10.2.1.1/24 -ti ubuntu /bin/sh + +or, equivalently with + + host1$ docker create -e WEAVE_CIDR=10.2.1.1/24 -ti ubuntu /bin/sh + 5ef831df61d50a1a49272357155a976595e7268e590f0a2c75693337b14e1382 + host1$ docker start 5ef831df61d50a1a49272357155a976595e7268e590f0a2c75693337b14e1382 + +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`. + +#### Limitations + +* The proxy does not currently support TLS. +* If you have a firewall, you will need to make sure port 12375 is open. diff --git a/site/index.md b/site/index.md index 57264417..6b6be5a9 100644 --- a/site/index.md +++ b/site/index.md @@ -8,6 +8,7 @@ layout: default * [Introduction, Installation and Example](https://github.com/weaveworks/weave#readme) * [Getting Started Guides](http://weave.works/guides/) * [Features](features.html) + * [Experimental features](experimental-features.html) * [Automatic IP Address Management](ipam.html) * [Automatic Discovery with WeaveDNS](weavedns.html) * [Troubleshooting](troubleshooting.html) From 444b1482fad1e7f247890036f783756b3ed9fc77 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 13 May 2015 16:38:56 +0100 Subject: [PATCH 06/18] add weavewait binocarlos/wait-for-weave is Apache2 licensed, so attribution/etc applies. Thus, we re-invent the wheel here. --- .gitignore | 2 ++ Makefile | 11 +++++++---- proxy/create_container_interceptor.go | 2 +- weaveexec/Dockerfile | 2 +- weavewait/main.go | 26 ++++++++++++++++++++++++++ 5 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 weavewait/main.go diff --git a/.gitignore b/.gitignore index e685a1b6..4c848836 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ coverage.html weaver/weaver weavedns/weavedns sigproxy/sigproxy +weavewait/weavewait tools/bin tools/build releases @@ -41,6 +42,7 @@ weavedns.tar weaveexec.tar weaveexec/proxy weaveexec/weave +weaveexec/weavewait weaveexec/sigproxy weaveexec/docker*.tgz Vagrantfile.local diff --git a/Makefile b/Makefile index 36fef509..cec7e20f 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ WEAVER_EXE=weaver/weaver WEAVEDNS_EXE=weavedns/weavedns SIGPROXY_EXE=sigproxy/sigproxy PROXY_EXE=weaveexec/proxy +WEAVEWAIT_EXE=weavewait/weavewait WEAVER_IMAGE=$(DOCKERHUB_USER)/weave WEAVEDNS_IMAGE=$(DOCKERHUB_USER)/weavedns WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec @@ -30,7 +31,7 @@ travis: $(WEAVER_EXE) $(WEAVEDNS_EXE) update: go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE)) ./$(dir $(SIGPROXY_EXE)) ./$(dir $(PROXY_EXE)) -$(WEAVER_EXE) $(WEAVEDNS_EXE) $(PROXY_EXE): common/*.go +$(WEAVER_EXE) $(WEAVEDNS_EXE) $(PROXY_EXE) $(WEAVEWAIT_EXE): common/*.go go get -tags netgo ./$(@D) go build -ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo -o $@ ./$(@D) @strings $@ | grep cgo_stub\\\.go >/dev/null || { \ @@ -45,6 +46,7 @@ $(WEAVER_EXE) $(WEAVEDNS_EXE) $(PROXY_EXE): common/*.go $(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go weaver/main.go $(WEAVEDNS_EXE): nameserver/*.go weavedns/main.go $(PROXY_EXE): proxy/*.go weaveexec/main.go +$(WEAVEWAIT_EXE): weavewait/*.go weavewait/main.go # Sigproxy needs separate rule as it fails the netgo check in the main # build stanza due to not importing net package @@ -59,9 +61,10 @@ $(WEAVEDNS_EXPORT): weavedns/Dockerfile $(WEAVEDNS_EXE) $(SUDO) docker build -t $(WEAVEDNS_IMAGE) weavedns $(SUDO) docker save $(WEAVEDNS_IMAGE):latest > $@ -$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(PROXY_EXE) +$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(PROXY_EXE) $(WEAVEWAIT_EXE) cp weave weaveexec/weave - cp sigproxy/sigproxy weaveexec/sigproxy + cp $(SIGPROXY_EXE) weaveexec/sigproxy + cp $(WEAVEWAIT_EXE) weaveexec/weavewait cp $(DOCKER_DISTRIB) weaveexec/docker.tgz $(SUDO) docker build -t $(WEAVEEXEC_IMAGE) weaveexec $(SUDO) docker save $(WEAVEEXEC_IMAGE):latest > $@ @@ -94,7 +97,7 @@ publish: $(PUBLISH) clean: -$(SUDO) docker rmi $(WEAVER_IMAGE) $(WEAVEDNS_IMAGE) $(WEAVEEXEC_IMAGE) - rm -f $(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT) + rm -f $(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(PROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT) build: $(SUDO) go clean -i net diff --git a/proxy/create_container_interceptor.go b/proxy/create_container_interceptor.go index c96431c4..ac022685 100644 --- a/proxy/create_container_interceptor.go +++ b/proxy/create_container_interceptor.go @@ -64,7 +64,7 @@ func addToVolumesFrom(config *docker.HostConfig, mounts ...string) error { func setWeaveWaitEntrypoint(image, container *docker.Config) { container.Cmd = combineEntrypoints(image, container) - container.Entrypoint = []string{"/home/weavewait/wait-for-weave"} + container.Entrypoint = []string{"/home/weavewait/weavewait"} } func combineEntrypoints(image, container *docker.Config) []string { diff --git a/weaveexec/Dockerfile b/weaveexec/Dockerfile index 6295b8a6..03c5fa24 100644 --- a/weaveexec/Dockerfile +++ b/weaveexec/Dockerfile @@ -16,9 +16,9 @@ WORKDIR /home/weave ADD ./weave /home/weave/ ADD ./sigproxy /home/weave/ ADD ./proxy /home/weave/ +ADD ./weavewait /home/weavewait/weavewait ADD ./docker.tgz / -ADD ./wait-for-weave /home/weavewait/wait-for-weave VOLUME /home/weavewait ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"] diff --git a/weavewait/main.go b/weavewait/main.go new file mode 100644 index 00000000..c4f52549 --- /dev/null +++ b/weavewait/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + + "github.com/weaveworks/weave/net" +) + +func main() { + if _, err := net.EnsureInterface("ethwe", 20); err != nil { + fmt.Fprintln(os.Stderr, err) + } + + if len(os.Args) <= 1 { + os.Exit(0) + } + cmd := exec.Command(os.Args[1], os.Args[2:]...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + os.Exit(1) + } +} From 96a69d2303f998d8c070766f023721d9a96d231e Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Thu, 14 May 2015 16:03:38 +0100 Subject: [PATCH 07/18] Add dns support for the proxy In the proxy, I swapped the 'flag' library for 'code.google.com/p/getopt', which uses double-dash flags. proxy lets 'weave attach' do dns registration on container start proxy needs to set DNSSearch if not specified --- proxy/client.go | 1 + proxy/common.go | 28 ++++++ proxy/create_container_interceptor.go | 96 ++++++++++++++----- proxy/proxy.go | 68 +++++++------ proxy/start_container_interceptor.go | 49 +++------- site/experimental-features.md | 12 +++ test/620_proxy_entrypoint_inheritance_test.sh | 14 +++ test/630_proxy_dns_test.sh | 18 ++++ test/650_proxy_entrypoint_override_test.sh | 14 +++ weave | 2 +- weaveexec/main.go | 29 ++++-- 11 files changed, 229 insertions(+), 102 deletions(-) create mode 100644 proxy/common.go create mode 100755 test/620_proxy_entrypoint_inheritance_test.sh create mode 100755 test/630_proxy_dns_test.sh create mode 100755 test/650_proxy_entrypoint_override_test.sh 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) } From bedc20c7c1f40792d4285c6b85d996c4d1858db4 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 15 May 2015 11:43:39 +0100 Subject: [PATCH 08/18] extract docker-bridge-ip fetching from proxy into bash script --- proxy/create_container_interceptor.go | 33 ++++----------------------- proxy/proxy.go | 23 ++++++++++++++----- weave | 4 ++++ 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/proxy/create_container_interceptor.go b/proxy/create_container_interceptor.go index 59c7e1eb..12d04df9 100644 --- a/proxy/create_container_interceptor.go +++ b/proxy/create_container_interceptor.go @@ -3,17 +3,16 @@ package proxy import ( "bytes" "encoding/json" - "fmt" "io/ioutil" "net/http" - "strings" "github.com/fsouza/go-dockerclient" ) type createContainerInterceptor struct { - client *docker.Client - withDNS bool + client *docker.Client + withDNS bool + dockerBridgeIP string } type createContainerRequestBody struct { @@ -97,11 +96,7 @@ func (i *createContainerInterceptor) setWeaveDNS(container *createContainerReque return nil } - dockerBridgeIP, err := getDockerBridgeIP() - if err != nil { - return err - } - container.HostConfig.DNS = append(container.HostConfig.DNS, dockerBridgeIP) + container.HostConfig.DNS = append(container.HostConfig.DNS, i.dockerBridgeIP) if len(container.HostConfig.DNSSearch) == 0 { container.HostConfig.DNSSearch = []string{"."} @@ -109,26 +104,6 @@ func (i *createContainerInterceptor) setWeaveDNS(container *createContainerReque 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) { return res, nil } diff --git a/proxy/proxy.go b/proxy/proxy.go index 03b1fa99..6bce75f2 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -12,9 +12,10 @@ import ( ) type Proxy struct { - Dial func() (net.Conn, error) - client *docker.Client - withDNS bool + Dial func() (net.Conn, error) + client *docker.Client + withDNS bool + dockerBridgeIP string } func NewProxy(targetURL string, withDNS bool) (*Proxy, error) { @@ -22,6 +23,15 @@ func NewProxy(targetURL string, withDNS bool) (*Proxy, error) { if err != nil { return nil, err } + + var dockerBridgeIP []byte + if withDNS { + dockerBridgeIP, err = callWeave("docker-bridge-ip") + if err != nil { + return nil, err + } + } + client, err := docker.NewClient(targetURL) if err != nil { return nil, err @@ -30,8 +40,9 @@ func NewProxy(targetURL string, withDNS bool) (*Proxy, error) { Dial: func() (net.Conn, error) { return net.Dial(targetNetwork(u), targetAddress(u)) }, - client: client, - withDNS: withDNS, + client: client, + withDNS: withDNS, + dockerBridgeIP: string(dockerBridgeIP), }, nil } @@ -82,7 +93,7 @@ func (proxy *Proxy) weaveStatus(w http.ResponseWriter, r *http.Request) { } func (proxy *Proxy) createContainer(w http.ResponseWriter, r *http.Request) { - newClient(proxy.Dial, &createContainerInterceptor{proxy.client, proxy.withDNS}).ServeHTTP(w, r) + newClient(proxy.Dial, &createContainerInterceptor{proxy.client, proxy.withDNS, proxy.dockerBridgeIP}).ServeHTTP(w, r) } func (proxy *Proxy) startContainer(w http.ResponseWriter, r *http.Request) { diff --git a/weave b/weave index 2c16c4b8..d8e0554b 100755 --- a/weave +++ b/weave @@ -921,6 +921,10 @@ case "$COMMAND" in dns_args "$@" echo -n $DNS_ARGS ;; + docker-bridge-ip) + docker_bridge_ip + echo -n $DOCKER_BRIDGE_IP + ;; start) collect_cidr_args "$@" shift $CIDR_COUNT From 55d17ba3bf92852c406da42703594f425465582f Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 15 May 2015 13:53:19 +0100 Subject: [PATCH 09/18] separate proxy package into weaveproxy and proxy --- .gitignore | 3 ++- Makefile | 13 +++++++------ weave | 2 +- weaveexec/Dockerfile | 2 +- {weaveexec => weaveproxy}/main.go | 0 5 files changed, 11 insertions(+), 9 deletions(-) rename {weaveexec => weaveproxy}/main.go (100%) diff --git a/.gitignore b/.gitignore index 4c848836..6f39f896 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ coverage.html # Project specific weaver/weaver weavedns/weavedns +weaveproxy/weaveproxy sigproxy/sigproxy weavewait/weavewait tools/bin @@ -40,8 +41,8 @@ releases weave.tar weavedns.tar weaveexec.tar -weaveexec/proxy weaveexec/weave +weaveexec/weaveproxy weaveexec/weavewait weaveexec/sigproxy weaveexec/docker*.tgz diff --git a/Makefile b/Makefile index cec7e20f..463ba6cd 100644 --- a/Makefile +++ b/Makefile @@ -10,8 +10,8 @@ DOCKERHUB_USER=weaveworks WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD) WEAVER_EXE=weaver/weaver WEAVEDNS_EXE=weavedns/weavedns +WEAVEPROXY_EXE=weaveproxy/weaveproxy SIGPROXY_EXE=sigproxy/sigproxy -PROXY_EXE=weaveexec/proxy WEAVEWAIT_EXE=weavewait/weavewait WEAVER_IMAGE=$(DOCKERHUB_USER)/weave WEAVEDNS_IMAGE=$(DOCKERHUB_USER)/weavedns @@ -29,9 +29,9 @@ all: $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT) travis: $(WEAVER_EXE) $(WEAVEDNS_EXE) update: - go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE)) ./$(dir $(SIGPROXY_EXE)) ./$(dir $(PROXY_EXE)) + go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE)) ./$(dir $(SIGPROXY_EXE)) ./$(dir $(WEAVEPROXY_EXE)) -$(WEAVER_EXE) $(WEAVEDNS_EXE) $(PROXY_EXE) $(WEAVEWAIT_EXE): common/*.go +$(WEAVER_EXE) $(WEAVEDNS_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE): common/*.go go get -tags netgo ./$(@D) go build -ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo -o $@ ./$(@D) @strings $@ | grep cgo_stub\\\.go >/dev/null || { \ @@ -45,7 +45,7 @@ $(WEAVER_EXE) $(WEAVEDNS_EXE) $(PROXY_EXE) $(WEAVEWAIT_EXE): common/*.go $(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go weaver/main.go $(WEAVEDNS_EXE): nameserver/*.go weavedns/main.go -$(PROXY_EXE): proxy/*.go weaveexec/main.go +$(WEAVEPROXY_EXE): proxy/*.go weaveproxy/main.go $(WEAVEWAIT_EXE): weavewait/*.go weavewait/main.go # Sigproxy needs separate rule as it fails the netgo check in the main @@ -61,10 +61,11 @@ $(WEAVEDNS_EXPORT): weavedns/Dockerfile $(WEAVEDNS_EXE) $(SUDO) docker build -t $(WEAVEDNS_IMAGE) weavedns $(SUDO) docker save $(WEAVEDNS_IMAGE):latest > $@ -$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(PROXY_EXE) $(WEAVEWAIT_EXE) +$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) cp weave weaveexec/weave cp $(SIGPROXY_EXE) weaveexec/sigproxy cp $(WEAVEWAIT_EXE) weaveexec/weavewait + cp $(WEAVEPROXY_EXE) weaveexec/weaveproxy cp $(DOCKER_DISTRIB) weaveexec/docker.tgz $(SUDO) docker build -t $(WEAVEEXEC_IMAGE) weaveexec $(SUDO) docker save $(WEAVEEXEC_IMAGE):latest > $@ @@ -97,7 +98,7 @@ publish: $(PUBLISH) clean: -$(SUDO) docker rmi $(WEAVER_IMAGE) $(WEAVEDNS_IMAGE) $(WEAVEEXEC_IMAGE) - rm -f $(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(PROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT) + rm -f $(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT) build: $(SUDO) go clean -i net diff --git a/weave b/weave index d8e0554b..744df836 100755 --- a/weave +++ b/weave @@ -730,7 +730,7 @@ ensure_proxy_running() { --name=$PROXY_CONTAINER_NAME \ -p $PROXY_PORT:$PROXY_PORT/tcp -v /var/run/docker.sock:/var/run/docker.sock \ -v /proc:/hostproc -e PROCFS=/hostproc \ - --entrypoint=/home/weave/proxy \ + --entrypoint=/home/weave/weaveproxy \ $WEAVE_DOCKER_ARGS $WEAVEPROXY_DOCKER_ARGS \ $EXEC_IMAGE "$@") diff --git a/weaveexec/Dockerfile b/weaveexec/Dockerfile index 03c5fa24..65b0f47a 100644 --- a/weaveexec/Dockerfile +++ b/weaveexec/Dockerfile @@ -15,7 +15,7 @@ WORKDIR /home/weave ADD ./weave /home/weave/ ADD ./sigproxy /home/weave/ -ADD ./proxy /home/weave/ +ADD ./weaveproxy /home/weave/ ADD ./weavewait /home/weavewait/weavewait ADD ./docker.tgz / diff --git a/weaveexec/main.go b/weaveproxy/main.go similarity index 100% rename from weaveexec/main.go rename to weaveproxy/main.go From 6fa0a9d70cf5f4b939d99329f853c477b06b470d Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 15 May 2015 14:00:00 +0100 Subject: [PATCH 10/18] moving weaveproxy docs off 'experimental-features' --- site/features.md | 10 +++++++++- site/index.md | 1 - site/{experimental-features.md => proxy.md} | 20 +++++++------------- 3 files changed, 16 insertions(+), 15 deletions(-) rename site/{experimental-features.md => proxy.md} (89%) diff --git a/site/features.md b/site/features.md index 1e0ecab5..dfee4b13 100644 --- a/site/features.md +++ b/site/features.md @@ -24,6 +24,7 @@ example](https://github.com/weaveworks/weave#example): * [Fault tolerance](#fault-tolerance) * [Automatic IP address management](#ipam) * [Automatic discovery with WeaveDNS](#dns) + * [Proxy](#proxy) ### Virtual Ethernet Switch @@ -403,4 +404,11 @@ to enable and use this feature. WeaveDNS is a distributed DNS service for weave networks, enabling containers to address each other by name rather than IP address. Find -out more about weaveDNS from its [documentation](weavedns.html). +out more about WeaveDNS from its [documentation](weavedns.html). + +### Proxy + +The weave proxy enables you to use the docker command-line utility, allowing +weave-enabled containers to be started in the foreground, and to wait for the +weave network interface before running. Find out more about the weave proxy +from its [documentation](proxy.html). diff --git a/site/index.md b/site/index.md index 6b6be5a9..57264417 100644 --- a/site/index.md +++ b/site/index.md @@ -8,7 +8,6 @@ layout: default * [Introduction, Installation and Example](https://github.com/weaveworks/weave#readme) * [Getting Started Guides](http://weave.works/guides/) * [Features](features.html) - * [Experimental features](experimental-features.html) * [Automatic IP Address Management](ipam.html) * [Automatic Discovery with WeaveDNS](weavedns.html) * [Troubleshooting](troubleshooting.html) diff --git a/site/experimental-features.md b/site/proxy.md similarity index 89% rename from site/experimental-features.md rename to site/proxy.md index 3d31fb21..c1d0daa0 100644 --- a/site/experimental-features.md +++ b/site/proxy.md @@ -1,20 +1,16 @@ --- -title: Experimental features +title: Weave Proxy layout: default --- -# Experimental features - - * [Proxy](#proxy) - -### Proxy +# Weave Proxy Instead of the weave command-line utility, you may prefer to use the standard docker command-line utility to manage containers. This will allow containers to be started in foreground mode. It will also ensure that the weave network interface is available before running your container. -#### Setup +## Setup To do this, while still connecting containers to the weave network, you can use the weave proxy. We can start the proxy with @@ -29,7 +25,7 @@ to globally adjust your `DOCKER_HOST`. host1$ export DOCKER_HOST="tcp://host1:12375" host1$ docker ps -#### Usage +## Usage When containers are created via the weave proxy, their entrypoint will be modified to wait for the weave network interface to become available. When they are @@ -47,7 +43,7 @@ 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 +## 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 @@ -55,11 +51,9 @@ Containers started via the proxy can be automatically configured to use WeaveDNS 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). +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.html). -More details on weaveDNS can be found in the [weaveDNS documentation](weavedns-readme.html). - -#### Limitations +## Limitations * The proxy does not currently support TLS. * If you have a firewall, you will need to make sure port 12375 is open. From 2a120e5565d0d0a19acbac5d01a9df54fc6a582e Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 18 May 2015 12:28:17 +0100 Subject: [PATCH 11/18] refactor proxy rawStream handler --- proxy/client.go | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/proxy/client.go b/proxy/client.go index 1decfe8d..ac52cd49 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -104,31 +104,26 @@ func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.Cl return } - end := make(chan bool) - go func() { - defer close(end) - if _, err := io.Copy(up, downBuf); err != nil { - Warning.Print(err) - } - err = up.(interface { - CloseWrite() error - }).CloseWrite() - if err != nil { - Debug.Printf("Error Closing upstream: %s", err) - } - }() + upDone := make(chan struct{}) + downDone := make(chan struct{}) + go copyStream(down, io.MultiReader(rem, up), upDone) + go copyStream(up, downBuf, downDone) + <-upDone + <-downDone +} - if _, err := io.Copy(down, io.MultiReader(rem, up)); err != nil { +func copyStream(dst io.Writer, src io.Reader, done chan struct{}) { + defer close(done) + if _, err := io.Copy(dst, src); err != nil { Warning.Print(err) } - err = down.(interface { + if c, ok := dst.(interface { CloseWrite() error - }).CloseWrite() - if err != nil { - Debug.Printf("Error Closing downstream: %s", err) + }); ok { + if err := c.CloseWrite(); err != nil { + Warning.Printf("Error closing connection: %s", err) + } } - - <-end } func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) { From ca4b8e6f8eed61389de4e735f647dd395d410f31 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 18 May 2015 15:36:44 +0100 Subject: [PATCH 12/18] add proxy support for 'docker restart' --- proxy/proxy.go | 2 +- test/640_proxy_restart_reattaches_test.sh | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100755 test/640_proxy_restart_reattaches_test.sh diff --git a/proxy/proxy.go b/proxy/proxy.go index 6bce75f2..a87fc90c 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -70,7 +70,7 @@ func isCreateContainer(r *http.Request) bool { } func isStartContainer(r *http.Request) bool { - ok, err := regexp.MatchString("^/v[0-9\\.]*/containers/[^/]*/start$", r.URL.Path) + ok, err := regexp.MatchString("^/v[0-9\\.]*/containers/[^/]*/(re)?start$", r.URL.Path) return err == nil && ok } diff --git a/test/640_proxy_restart_reattaches_test.sh b/test/640_proxy_restart_reattaches_test.sh new file mode 100755 index 00000000..01017764 --- /dev/null +++ b/test/640_proxy_restart_reattaches_test.sh @@ -0,0 +1,20 @@ +#! /bin/bash + +. ./config.sh + +C1=10.2.0.78 +C2=10.2.0.34 +NAME=seetwo.weave.local + +start_suite "Proxy restart reattaches networking to containers" + +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 + +docker_proxy_on $HOST1 restart c2 +assert_raises "exec_on $HOST1 c2 ip link show ethwe | grep 'state UP'" +assert_dns_record $HOST1 c1 $NAME $C2 + +end_suite From 126d4f4079eadda4418d64097e712741627accad Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 18 May 2015 21:45:40 +0100 Subject: [PATCH 13/18] some refactoring and tidying - some inlining - compile regexps once - consistent error messages - always mention container id in startContainerInterceptor errors --- proxy/client.go | 4 +- proxy/create_container_interceptor.go | 10 ++-- proxy/proxy.go | 75 +++++++++------------------ proxy/start_container_interceptor.go | 46 ++++++++-------- 4 files changed, 51 insertions(+), 84 deletions(-) diff --git a/proxy/client.go b/proxy/client.go index ac52cd49..08e0a59f 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -33,7 +33,7 @@ func (c *client) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } if err != nil { - http.Error(w, "Unable to create proxied request", http.StatusInternalServerError) + http.Error(w, "Unable to intercept request", http.StatusInternalServerError) Warning.Print("Error intercepting request: ", err) return } @@ -49,7 +49,7 @@ func (c *client) ServeHTTP(w http.ResponseWriter, r *http.Request) { resp, err := client.Do(req) if err != nil && err != httputil.ErrPersistEOF { - http.Error(w, fmt.Sprintf("Could not make request to target: %s", err.Error()), http.StatusInternalServerError) + http.Error(w, fmt.Sprintf("Could not make request to target: %v", err), http.StatusInternalServerError) Warning.Print("Error forwarding request: ", err) return } diff --git a/proxy/create_container_interceptor.go b/proxy/create_container_interceptor.go index 12d04df9..d08cb2d8 100644 --- a/proxy/create_container_interceptor.go +++ b/proxy/create_container_interceptor.go @@ -34,7 +34,7 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) (*http.Re } if _, ok := weaveCIDRsFromConfig(container.Config); ok { - i.addToVolumesFrom(container.HostConfig, "weaveproxy") + container.HostConfig.VolumesFrom = append(container.HostConfig.VolumesFrom, "weaveproxy") if err := i.setWeaveWaitEntrypoint(container.Config); err != nil { return nil, err } @@ -53,10 +53,6 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) (*http.Re return r, nil } -func (i *createContainerInterceptor) addToVolumesFrom(config *docker.HostConfig, mounts ...string) { - config.VolumesFrom = append(config.VolumesFrom, mounts...) -} - func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error { var image *docker.Config if !configHasEntrypoint(container) || !configHasCmd(container) { @@ -104,6 +100,6 @@ func (i *createContainerInterceptor) setWeaveDNS(container *createContainerReque return nil } -func (i *createContainerInterceptor) InterceptResponse(res *http.Response) (*http.Response, error) { - return res, nil +func (i *createContainerInterceptor) InterceptResponse(r *http.Response) (*http.Response, error) { + return r, nil } diff --git a/proxy/proxy.go b/proxy/proxy.go index a87fc90c..7681230e 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -11,6 +11,11 @@ import ( . "github.com/weaveworks/weave/common" ) +var ( + containerCreateRegexp = regexp.MustCompile("/v[0-9\\.]*/containers/create") + containerStartRegexp = regexp.MustCompile("^/v[0-9\\.]*/containers/[^/]*/(re)?start$") +) + type Proxy struct { Dial func() (net.Conn, error) client *docker.Client @@ -36,9 +41,18 @@ func NewProxy(targetURL string, withDNS bool) (*Proxy, error) { if err != nil { return nil, err } + + targetAddr := "" + switch u.Scheme { + case "tcp": + targetAddr = u.Host + case "unix": + targetAddr = u.Path + } + return &Proxy{ Dial: func() (net.Conn, error) { - return net.Dial(targetNetwork(u), targetAddress(u)) + return net.Dial(u.Scheme, targetAddr) }, client: client, withDNS: withDNS, @@ -46,60 +60,21 @@ func NewProxy(targetURL string, withDNS bool) (*Proxy, error) { }, nil } -func targetNetwork(u *url.URL) string { - return u.Scheme -} - -func targetAddress(u *url.URL) (addr string) { - switch u.Scheme { - case "tcp": - addr = u.Host - case "unix": - addr = u.Path - } - return -} - -func isWeaveStatus(r *http.Request) bool { - return strings.HasPrefix(r.URL.Path, "/weave") -} - -func isCreateContainer(r *http.Request) bool { - ok, err := regexp.MatchString("/v[0-9\\.]*/containers/create", r.URL.Path) - return err == nil && ok -} - -func isStartContainer(r *http.Request) bool { - ok, err := regexp.MatchString("^/v[0-9\\.]*/containers/[^/]*/(re)?start$", r.URL.Path) - return err == nil && ok -} - func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { Info.Printf("%s %s", r.Method, r.URL) + path := r.URL.Path switch { - case isCreateContainer(r): - proxy.createContainer(w, r) - case isStartContainer(r): - proxy.startContainer(w, r) - case isWeaveStatus(r): - proxy.weaveStatus(w, r) + case containerCreateRegexp.MatchString(path): + proxy.serveWithInterceptor(&createContainerInterceptor{proxy.client, proxy.withDNS, proxy.dockerBridgeIP}, w, r) + case containerStartRegexp.MatchString(path): + proxy.serveWithInterceptor(&startContainerInterceptor{proxy.client, proxy.withDNS}, w, r) + case strings.HasPrefix(path, "/weave"): + w.WriteHeader(http.StatusOK) default: - proxy.proxyRequest(w, r) + proxy.serveWithInterceptor(&nullInterceptor{}, w, r) } } -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, proxy.withDNS, proxy.dockerBridgeIP}).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) serveWithInterceptor(i interceptor, w http.ResponseWriter, r *http.Request) { + newClient(proxy.Dial, i).ServeHTTP(w, r) } diff --git a/proxy/start_container_interceptor.go b/proxy/start_container_interceptor.go index c7fa4b8b..4049a107 100644 --- a/proxy/start_container_interceptor.go +++ b/proxy/start_container_interceptor.go @@ -9,6 +9,8 @@ import ( . "github.com/weaveworks/weave/common" ) +var containerIDRegexp = regexp.MustCompile("^/v[0-9\\.]*/containers/([^/]*)/.*") + type startContainerInterceptor struct { client *docker.Client withDNS bool @@ -18,39 +20,33 @@ func (i *startContainerInterceptor) InterceptRequest(r *http.Request) (*http.Req return r, nil } -func (i *startContainerInterceptor) InterceptResponse(res *http.Response) (*http.Response, error) { - containerID := containerFromPath(res.Request.URL.Path) +func (i *startContainerInterceptor) InterceptResponse(r *http.Response) (*http.Response, error) { + containerID := "" + if subs := containerIDRegexp.FindStringSubmatch(r.Request.URL.Path); subs == nil { + Warning.Printf("No container id found in request with path %s", r.Request.URL.Path) + return r, nil + } else { + containerID = subs[1] + } container, err := i.client.InspectContainer(containerID) if err != nil { - Warning.Print("Error Inspecting Container: ", err) - return res, nil + Warning.Printf("Error inspecting container %s: %v", containerID, err) + return r, nil } - 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 { + cidrs, ok := weaveCIDRsFromConfig(container.Config) + if !ok { Debug.Print("No Weave CIDR, ignoring") + return r, nil } - - return res, nil -} - -func containerFromPath(path string) string { - if subs := regexp.MustCompile("^/v[0-9\\.]*/containers/([^/]*)/.*").FindStringSubmatch(path); subs != nil { - return subs[1] - } - return "" -} - -func (i *startContainerInterceptor) attachContainerToWeave(containerID string, cidrs []string) error { + Info.Printf("Container %s was started with CIDR \"%s\"", containerID, strings.Join(cidrs, " ")) args := []string{"attach"} args = append(args, cidrs...) args = append(args, containerID) - _, err := callWeave(args...) - return err + if _, err := callWeave(args...); err != nil { + Warning.Printf("Attaching container %s to weave failed: %v", containerID, err) + return r, nil + } + return r, nil } From 2f8c5d7bd95c4da5eeaff76ee24702bedd3ae4d3 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 19 May 2015 09:59:36 +0100 Subject: [PATCH 14/18] proxy interceptors implicitly modify arguments, instead of returning them --- proxy/client.go | 6 +++--- proxy/create_container_interceptor.go | 18 +++++++++--------- proxy/interceptor.go | 12 ++++++------ proxy/start_container_interceptor.go | 16 ++++++++-------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/proxy/client.go b/proxy/client.go index 08e0a59f..3b094dde 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -27,7 +27,7 @@ func newClient(dial func() (net.Conn, error), i interceptor) *client { } func (c *client) ServeHTTP(w http.ResponseWriter, r *http.Request) { - req, err := c.InterceptRequest(r) + err := c.InterceptRequest(r) if err == docker.ErrNoSuchImage { http.NotFound(w, r) return @@ -47,13 +47,13 @@ func (c *client) ServeHTTP(w http.ResponseWriter, r *http.Request) { client := httputil.NewClientConn(conn, nil) defer client.Close() - resp, err := client.Do(req) + resp, err := client.Do(r) if err != nil && err != httputil.ErrPersistEOF { http.Error(w, fmt.Sprintf("Could not make request to target: %v", err), http.StatusInternalServerError) Warning.Print("Error forwarding request: ", err) return } - resp, err = c.InterceptResponse(resp) + err = c.InterceptResponse(resp) if err != nil { http.Error(w, "Unable to intercept response", http.StatusInternalServerError) Warning.Print("Error intercepting response: ", err) diff --git a/proxy/create_container_interceptor.go b/proxy/create_container_interceptor.go index d08cb2d8..35841a9b 100644 --- a/proxy/create_container_interceptor.go +++ b/proxy/create_container_interceptor.go @@ -21,36 +21,36 @@ type createContainerRequestBody struct { MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` } -func (i *createContainerInterceptor) InterceptRequest(r *http.Request) (*http.Request, error) { +func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error { body, err := ioutil.ReadAll(r.Body) if err != nil { - return nil, err + return err } r.Body.Close() container := createContainerRequestBody{} if err := json.Unmarshal(body, &container); err != nil { - return nil, err + return err } if _, ok := weaveCIDRsFromConfig(container.Config); ok { container.HostConfig.VolumesFrom = append(container.HostConfig.VolumesFrom, "weaveproxy") if err := i.setWeaveWaitEntrypoint(container.Config); err != nil { - return nil, err + return err } if err := i.setWeaveDNS(&container); err != nil { - return nil, err + return err } } newBody, err := json.Marshal(container) if err != nil { - return nil, err + return err } r.Body = ioutil.NopCloser(bytes.NewReader(newBody)) r.ContentLength = int64(len(newBody)) - return r, nil + return nil } func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error { @@ -100,6 +100,6 @@ func (i *createContainerInterceptor) setWeaveDNS(container *createContainerReque return nil } -func (i *createContainerInterceptor) InterceptResponse(r *http.Response) (*http.Response, error) { - return r, nil +func (i *createContainerInterceptor) InterceptResponse(r *http.Response) error { + return nil } diff --git a/proxy/interceptor.go b/proxy/interceptor.go index 203a0d03..69d903e2 100644 --- a/proxy/interceptor.go +++ b/proxy/interceptor.go @@ -3,17 +3,17 @@ package proxy import "net/http" type interceptor interface { - InterceptRequest(*http.Request) (*http.Request, error) - InterceptResponse(*http.Response) (*http.Response, error) + InterceptRequest(*http.Request) error + InterceptResponse(*http.Response) error } type nullInterceptor struct { } -func (i nullInterceptor) InterceptRequest(r *http.Request) (*http.Request, error) { - return r, nil +func (i nullInterceptor) InterceptRequest(r *http.Request) error { + return nil } -func (i nullInterceptor) InterceptResponse(r *http.Response) (*http.Response, error) { - return r, nil +func (i nullInterceptor) InterceptResponse(r *http.Response) error { + return nil } diff --git a/proxy/start_container_interceptor.go b/proxy/start_container_interceptor.go index 4049a107..88c391b3 100644 --- a/proxy/start_container_interceptor.go +++ b/proxy/start_container_interceptor.go @@ -16,15 +16,15 @@ type startContainerInterceptor struct { withDNS bool } -func (i *startContainerInterceptor) InterceptRequest(r *http.Request) (*http.Request, error) { - return r, nil +func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error { + return nil } -func (i *startContainerInterceptor) InterceptResponse(r *http.Response) (*http.Response, error) { +func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error { containerID := "" if subs := containerIDRegexp.FindStringSubmatch(r.Request.URL.Path); subs == nil { Warning.Printf("No container id found in request with path %s", r.Request.URL.Path) - return r, nil + return nil } else { containerID = subs[1] } @@ -32,13 +32,13 @@ func (i *startContainerInterceptor) InterceptResponse(r *http.Response) (*http.R container, err := i.client.InspectContainer(containerID) if err != nil { Warning.Printf("Error inspecting container %s: %v", containerID, err) - return r, nil + return nil } cidrs, ok := weaveCIDRsFromConfig(container.Config) if !ok { Debug.Print("No Weave CIDR, ignoring") - return r, nil + return nil } Info.Printf("Container %s was started with CIDR \"%s\"", containerID, strings.Join(cidrs, " ")) args := []string{"attach"} @@ -46,7 +46,7 @@ func (i *startContainerInterceptor) InterceptResponse(r *http.Response) (*http.R args = append(args, containerID) if _, err := callWeave(args...); err != nil { Warning.Printf("Attaching container %s to weave failed: %v", containerID, err) - return r, nil + return nil } - return r, nil + return nil } From 1e7c82bb586d5dda0bb1b739c307d2d7a7f1987d Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 19 May 2015 10:03:36 +0100 Subject: [PATCH 15/18] use busybox instead of ubuntu for proxy image pulling test busybox is smaller. gliderlabs/alpine is even smaller, but is used by weaveexec, so we can't remove it. --- test/610_proxy_wait_for_weave_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/610_proxy_wait_for_weave_test.sh b/test/610_proxy_wait_for_weave_test.sh index c1cb054c..21504432 100755 --- a/test/610_proxy_wait_for_weave_test.sh +++ b/test/610_proxy_wait_for_weave_test.sh @@ -4,7 +4,7 @@ start_suite "Proxy waits for weave to be ready before running container commands" weave_on $HOST1 launch-proxy -BASE_IMAGE=ubuntu +BASE_IMAGE=busybox # Ensure the base image does not exist, so that it will be pulled if (docker_on $HOST1 images $BASE_IMAGE | grep $BASE_IMAGE); then docker_on $HOST1 rmi $BASE_IMAGE From 6911dc8d38a3faba4213e1927892e306dc40c9e4 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 19 May 2015 10:09:42 +0100 Subject: [PATCH 16/18] updating proxy with-dns description to match docs --- weaveproxy/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weaveproxy/main.go b/weaveproxy/main.go index 65cc14ac..2a395ae1 100644 --- a/weaveproxy/main.go +++ b/weaveproxy/main.go @@ -21,7 +21,7 @@ func main() { 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.BoolVarLong(&withDNS, "with-dns", 'w', "instruct created containers to use weaveDNS as their nameserver") getopt.Parse() if target == "" { From b71a4283698f60304070c7a685bc9d720df678e1 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 19 May 2015 10:34:25 +0100 Subject: [PATCH 17/18] proxy should be sending error code to client, instead of exiting --- proxy/client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proxy/client.go b/proxy/client.go index 3b094dde..f0733262 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -83,7 +83,7 @@ func (c *client) ServeHTTP(w http.ResponseWriter, r *http.Request) { func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) { down, downBuf, up, rem, err := hijack(w, client) if err != nil { - Error.Fatal(w, "Unable to hijack connection for raw stream mode", http.StatusInternalServerError) + http.Error(w, "Unable to hijack connection for raw stream mode", http.StatusInternalServerError) return } defer down.Close() @@ -135,7 +135,7 @@ func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httpu down, _, up, rem, err := hijack(w, client) if err != nil { - Error.Fatal("Unable to hijack response stream for chunked response", http.StatusInternalServerError) + http.Error(w, "Unable to hijack response stream for chunked response", http.StatusInternalServerError) return } defer up.Close() @@ -144,7 +144,7 @@ func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httpu // stopping at the end of the chunked section. rawResponseBody := io.MultiReader(rem, up) if _, err := io.Copy(ioutil.Discard, httputil.NewChunkedReader(io.TeeReader(rawResponseBody, down))); err != nil { - Error.Fatal("Error copying chunked response body", http.StatusInternalServerError) + http.Error(w, "Error copying chunked response body", http.StatusInternalServerError) return } resp.Trailer.Write(down) From ee07d645b4918aa3d6401ca27480c2c0cbf40352 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 19 May 2015 10:46:04 +0100 Subject: [PATCH 18/18] golint cleanup of else block --- proxy/start_container_interceptor.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/proxy/start_container_interceptor.go b/proxy/start_container_interceptor.go index 88c391b3..f2e361e4 100644 --- a/proxy/start_container_interceptor.go +++ b/proxy/start_container_interceptor.go @@ -21,13 +21,11 @@ func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error { } func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error { - containerID := "" if subs := containerIDRegexp.FindStringSubmatch(r.Request.URL.Path); subs == nil { Warning.Printf("No container id found in request with path %s", r.Request.URL.Path) return nil - } else { - containerID = subs[1] } + containerID := subs[1] container, err := i.client.InspectContainer(containerID) if err != nil {