diff --git a/.gitignore b/.gitignore index 4b3181fc0..f7d466f80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ release build +vendor \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 307089ed9..4d541ef2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,33 @@ All notable changes to this project will be documented in this file. ### Changed +## [v7] - 2016-03-05 +### Fixed +- Providing a SERVICE_NAME for a container with multiple ports exposed would cause services to overwrite each other +- dd3ab2e Fix specific port names not overriding port suffix + +### Added +- bridge.Ping - calls adapter.Ping +- Consul TCP Health Check +- Support for Consul unix sockets +- Basic Zookeper backend +- Support for Docker multi host networking +- Default to tcp for PortType if not provided +- Sync etcd cluster on service registration +- Support hostip for overlay network +- Cleanup dangling services +- Startup backend service connection retry + +### Removed + +### Changed +- Upgraded base image to alpine:3.2 and go 1.4 +- bridge.New returns an error instead of calling log.Fatal +- bridge.New will not attempt to ping an adapter. +- Specifying a SERVICE_NAME for containers exposing multiple ports will now result in a named service per port. #194 +- Etcd uses port 2379 instead of 4001 #340 +- Setup Docker client from environment +- Use exit status to determine if container was killed ## [v6] - 2015-08-07 ### Fixed @@ -17,6 +44,7 @@ All notable changes to this project will be documented in this file. - Panic from invalid skydns2 URI. ### Added +- Basic zookeeper adapter - Optional periodic resyncing of services from containers - More error logging for registries - Support for services on containers with `--net=host` @@ -54,6 +82,7 @@ All notable changes to this project will be documented in this file. - Dropped Godeps for now -[unreleased]: https://github.com/gliderlabs/registrator/compare/v6...HEAD +[unreleased]: https://github.com/gliderlabs/registrator/compare/v7...HEAD +[v7]: https://github.com/gliderlabs/registrator/compare/v6...v7 [v6]: https://github.com/gliderlabs/registrator/compare/v5...v6 [v5]: https://github.com/gliderlabs/registrator/compare/v0.4.0...v5 diff --git a/Dockerfile b/Dockerfile index b793d38a7..429113062 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,18 @@ -FROM gliderlabs/alpine:3.1 -ENTRYPOINT ["/bin/registrator"] - +FROM alpine:3.7 AS builder COPY . /go/src/github.com/gliderlabs/registrator -RUN apk-install -t build-deps go git mercurial \ +RUN apk --no-cache add -t build-deps build-base go git curl \ + && apk --no-cache add ca-certificates \ + && export GOPATH=/go && mkdir -p /go/bin && export PATH=$PATH:/go/bin \ + && curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh \ && cd /go/src/github.com/gliderlabs/registrator \ && export GOPATH=/go \ - && go get \ - && go build -ldflags "-X main.Version $(cat VERSION)" -o /bin/registrator \ + && git config --global http.https://gopkg.in.followRedirects true \ + && dep ensure \ + && go build -ldflags "-X main.Version=$(cat VERSION)" -o /bin/registrator \ && rm -rf /go \ && apk del --purge build-deps + +FROM alpine:3.7 +COPY --from=builder /bin/registrator /bin/registrator +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +ENTRYPOINT ["/bin/registrator"] diff --git a/Dockerfile.dev b/Dockerfile.dev index 1d7934626..0584cd232 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,9 +1,10 @@ -FROM gliderlabs/alpine:3.1 +FROM alpine:3.5 CMD ["/bin/registrator"] ENV GOPATH /go -RUN apk-install go git mercurial +RUN apk --no-cache add build-base go git ca-certificates COPY . /go/src/github.com/gliderlabs/registrator RUN cd /go/src/github.com/gliderlabs/registrator \ + && git config --global http.https://gopkg.in.followRedirects true \ && go get \ - && go build -ldflags "-X main.Version dev" -o /bin/registrator + && go build -ldflags "-X main.Version=dev" -o /bin/registrator diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 000000000..8c43e8960 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,244 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/Azure/go-ansiterm" + packages = [ + ".", + "winterm" + ] + revision = "d6e3b3328b783f23731bc4d058875b0371ff8109" + +[[projects]] + name = "github.com/Microsoft/go-winio" + packages = ["."] + revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f" + version = "v0.4.7" + +[[projects]] + branch = "master" + name = "github.com/Nvveen/Gotty" + packages = ["."] + revision = "cd527374f1e5bff4938207604a14f2e38a9cf512" + +[[projects]] + name = "github.com/cenkalti/backoff" + packages = ["."] + revision = "2ea60e5f094469f9e65adb9cd103795b73ae743e" + version = "v2.0.0" + +[[projects]] + branch = "master" + name = "github.com/containerd/continuity" + packages = ["pathdriver"] + revision = "c6cef34830231743494fe2969284df7b82cc0ad0" + +[[projects]] + name = "github.com/coreos/go-etcd" + packages = ["etcd"] + revision = "f02171fbd43c7b9b53ce8679b03235a1ef3c7b12" + version = "v2.0.0" + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + +[[projects]] + name = "github.com/docker/docker" + packages = [ + "api/types", + "api/types/blkiodev", + "api/types/container", + "api/types/filters", + "api/types/mount", + "api/types/network", + "api/types/registry", + "api/types/strslice", + "api/types/swarm", + "api/types/swarm/runtime", + "api/types/versions", + "opts", + "pkg/archive", + "pkg/fileutils", + "pkg/homedir", + "pkg/idtools", + "pkg/ioutils", + "pkg/jsonmessage", + "pkg/longpath", + "pkg/mount", + "pkg/pools", + "pkg/stdcopy", + "pkg/system", + "pkg/term", + "pkg/term/windows" + ] + revision = "a422774e593b33bd287d9890544ad9e09b380d8c" + +[[projects]] + name = "github.com/docker/go-connections" + packages = ["nat"] + revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d" + version = "v0.3.0" + +[[projects]] + name = "github.com/docker/go-units" + packages = ["."] + revision = "47565b4f722fb6ceae66b95f853feed578a4a51c" + version = "v0.3.3" + +[[projects]] + name = "github.com/fsouza/go-dockerclient" + packages = ["."] + revision = "ca33ff277b527ce11b793e62f9ba244129b01caf" + version = "v1.2.0" + +[[projects]] + branch = "master" + name = "github.com/gliderlabs/pkg" + packages = ["usage"] + revision = "36f28d47ec7aae4d25d3d2741ac5af91f7f18680" + +[[projects]] + name = "github.com/gogo/protobuf" + packages = ["proto"] + revision = "1adfc126b41513cc696b209667c8656ea7aac67c" + version = "v1.0.0" + +[[projects]] + name = "github.com/hashicorp/consul" + packages = ["api"] + revision = "fb848fc48818f58690db09d14640513aa6bf3c02" + version = "v1.0.7" + +[[projects]] + branch = "master" + name = "github.com/hashicorp/go-cleanhttp" + packages = ["."] + revision = "d5fe4b57a186c716b0e00b8c301cbd9b4182694d" + +[[projects]] + branch = "master" + name = "github.com/hashicorp/go-rootcerts" + packages = ["."] + revision = "6bb64b370b90e7ef1fa532be9e591a81c3493e00" + +[[projects]] + name = "github.com/hashicorp/serf" + packages = ["coordinate"] + revision = "d6574a5bb1226678d7010325fb6c985db20ee458" + version = "v0.8.1" + +[[projects]] + name = "github.com/miekg/dns" + packages = ["."] + revision = "83c435cc65d2862736428b9b4d07d0ab10ad3e4d" + version = "v1.0.5" + +[[projects]] + branch = "master" + name = "github.com/mitchellh/go-homedir" + packages = ["."] + revision = "b8bc1bf767474819792c23f32d8286a45736f1c6" + +[[projects]] + name = "github.com/opencontainers/go-digest" + packages = ["."] + revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf" + version = "v1.0.0-rc1" + +[[projects]] + name = "github.com/opencontainers/image-spec" + packages = [ + "specs-go", + "specs-go/v1" + ] + revision = "d60099175f88c47cd379c4738d158884749ed235" + version = "v1.0.1" + +[[projects]] + name = "github.com/opencontainers/runc" + packages = [ + "libcontainer/system", + "libcontainer/user" + ] + revision = "baf6536d6259209c3edfa2b22237af82942d3dfa" + version = "v0.1.1" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/samuel/go-zookeeper" + packages = ["zk"] + revision = "c4fab1ac1bec58281ad0667dc3f0907a9476ac47" + +[[projects]] + name = "github.com/sirupsen/logrus" + packages = ["."] + revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" + version = "v1.0.5" + +[[projects]] + name = "github.com/stretchr/testify" + packages = ["assert"] + revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" + version = "v1.2.1" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = [ + "ed25519", + "ed25519/internal/edwards25519", + "ssh/terminal" + ] + revision = "e73bf333ef8920dbb52ad18d4bd38ad9d9bc76d7" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = [ + "bpf", + "context", + "context/ctxhttp", + "internal/iana", + "internal/socket", + "ipv4", + "ipv6" + ] + revision = "5f9ae10d9af5b1c89ae6904293b14b064d4ada23" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows" + ] + revision = "79b0c6888797020a994db17c8510466c72fe75d9" + +[[projects]] + name = "gopkg.in/coreos/go-etcd.v0" + packages = ["etcd"] + revision = "6aa2da5a7a905609c93036b9307185a04a5a84a5" + version = "v0.4.6" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "cda685c394db30d7f3d3c76aa470320ba13d83a37c6c53fec0e58dd0566321f4" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 000000000..acaf02625 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,43 @@ +[[constraint]] + name = "github.com/cenkalti/backoff" + version = "2.0.0" + +[[constraint]] + name = "github.com/coreos/go-etcd" + version = "2.0.0" + +[[constraint]] + name = "github.com/fsouza/go-dockerclient" + version = "1.2.0" + +[[override]] + name = "github.com/docker/docker" + revision = "a422774e593b33bd287d9890544ad9e09b380d8c" + +[[constraint]] + branch = "master" + name = "github.com/gliderlabs/pkg" + +[[constraint]] + name = "github.com/hashicorp/consul" + version = "1.0.7" + +[[constraint]] + branch = "master" + name = "github.com/hashicorp/go-cleanhttp" + +[[constraint]] + branch = "master" + name = "github.com/samuel/go-zookeeper" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.2.1" + +[[constraint]] + name = "gopkg.in/coreos/go-etcd.v0" + version = "0.4.6" + +[prune] + go-tests = true + unused-packages = true diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..ab1a08da1 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,19 @@ +- What version of docker are you running? +- What version of registrator are you running? +- Did you build a custom version of registrator? If so, what is that image? +- What is the exact command you are running registrator with? +- What is the exact command you are running your container with? +- A log capture of all the docker events before, during, and after the issue. +- If relevant, `Dockerfile` for application that is having issues. + +Description of the problem: + +How reproducible: + +Steps to Reproduce: + +Actual Results: + +Expected Results: + +Additional info: diff --git a/Makefile b/Makefile index 1f01ed9c5..8ff9c57e9 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,12 @@ NAME=registrator VERSION=$(shell cat VERSION) +DEV_RUN_OPTS ?= consul: dev: docker build -f Dockerfile.dev -t $(NAME):dev . docker run --rm \ -v /var/run/docker.sock:/tmp/docker.sock \ - $(NAME):dev /bin/registrator consul: + $(NAME):dev /bin/registrator $(DEV_RUN_OPTS) build: mkdir -p build @@ -18,14 +19,15 @@ release: cp build/* release gh-release create gliderlabs/$(NAME) $(VERSION) \ $(shell git rev-parse --abbrev-ref HEAD) $(VERSION) - glu hubtag gliderlabs/$(NAME) $(VERSION) docs: + boot2docker ssh "sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'" || true docker run --rm -it -p 8000:8000 -v $(PWD):/work gliderlabs/pagebuilder mkdocs serve circleci: - rm -f ~/.gitconfig - go get -u github.com/gliderlabs/glu - glu circleci + rm ~/.gitconfig +ifneq ($(CIRCLE_BRANCH), release) + echo build-$$CIRCLE_BUILD_NUM > VERSION +endif .PHONY: build release docs diff --git a/README.md b/README.md index 1c98effda..c3151649e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Registrator -Service registry bridge for Docker, sponsored by [Weave](http://weave.works). +Service registry bridge for Docker. [![Circle CI](https://circleci.com/gh/gliderlabs/registrator.png?style=shield)](https://circleci.com/gh/gliderlabs/registrator) -[![Docker Hub](https://img.shields.io/badge/docker-ready-blue.svg)](https://registry.hub.docker.com/u/gliderlabs/registrator/) +[![Docker pulls](https://img.shields.io/docker/pulls/gliderlabs/registrator.svg)](https://hub.docker.com/r/gliderlabs/registrator/) [![IRC Channel](https://img.shields.io/badge/irc-%23gliderlabs-blue.svg)](https://kiwiirc.com/client/irc.freenode.net/#gliderlabs)

@@ -13,12 +13,17 @@ supports pluggable service registries, which currently includes [Consul](http://www.consul.io/), [etcd](https://github.com/coreos/etcd) and [SkyDNS 2](https://github.com/skynetservices/skydns/). +Full documentation available at http://gliderlabs.com/registrator + ## Getting Registrator Get the latest release, master, or any version of Registrator via [Docker Hub](https://registry.hub.docker.com/u/gliderlabs/registrator/): $ docker pull gliderlabs/registrator:latest +Latest tag always points to the latest release. There is also a `:master` tag +and version tags to pin to specific releases. + ## Using Registrator The quickest way to see Registrator in action is our @@ -34,6 +39,23 @@ Guide. Typically, running Registrator looks like this: gliderlabs/registrator:latest \ consul://localhost:8500 +## CLI Options +``` +Usage of /bin/registrator: + /bin/registrator [options] + + -cleanup=false: Remove dangling services + -deregister="always": Deregister exited services "always" or "on-success" + -internal=false: Use internal ports instead of published ones + -ip="": IP for ports mapped to the host + -resync=0: Frequency with which services are resynchronized + -retry-attempts=0: Max retry attempts to establish a connection with the backend. Use -1 for infinite retries + -retry-interval=2000: Interval (in millisecond) between retry-attempts. + -tags="": Append tags for all registered services + -ttl=0: TTL for services (default is no expiry) + -ttl-refresh=0: Frequency with which service TTLs are refreshed +``` + ## Contributing Pull requests are welcome! We recommend getting feedback before starting by @@ -42,13 +64,12 @@ discussing in [Slack](http://glider-slackin.herokuapp.com/). Also check out our Developer Guide on [Contributing Backends](https://gliderlabs.com/registrator/latest/dev/backends) and [Staging -Releases](https://gliderlabs.com/registrator/latest/dev/releases.). +Releases](https://gliderlabs.com/registrator/latest/dev/releases). ## Sponsors and Thanks -Ongoing support of this project is made possible by [Weave](http://weave.works), -the Docker SDN. Big thanks to Michael Crosby for -[skydock](https://github.com/crosbymichael/skydock) and the Consul mailing list +Big thanks to Weave for sponsoring, Michael Crosby for +[skydock](https://github.com/crosbymichael/skydock), and the Consul mailing list for inspiration. For a full list of sponsors, see diff --git a/SPONSORS b/SPONSORS index 47c141492..c70a20d8b 100644 --- a/SPONSORS +++ b/SPONSORS @@ -1,2 +1,2 @@ -DigitalOcean http://digitalocean.com +DigitalOcean http://digitalocean.com Weaveworks http://weave.works diff --git a/VERSION b/VERSION index 9c0be88a7..02a819f21 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v6 +v7 diff --git a/bridge/bridge.go b/bridge/bridge.go index 4901c0901..f02ca99c9 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -1,11 +1,13 @@ package bridge import ( + "errors" "log" "net" "net/url" "os" "path" + "regexp" "strconv" "strings" "sync" @@ -13,6 +15,8 @@ import ( dockerapi "github.com/fsouza/go-dockerclient" ) +var serviceIDPattern = regexp.MustCompile(`^(.+?):([a-zA-Z0-9][a-zA-Z0-9_.-]+):[0-9]+(?::udp)?$`) + type Bridge struct { sync.Mutex registry RegistryAdapter @@ -22,28 +26,28 @@ type Bridge struct { config Config } -func New(docker *dockerapi.Client, adapterUri string, config Config) *Bridge { +func New(docker *dockerapi.Client, adapterUri string, config Config) (*Bridge, error) { uri, err := url.Parse(adapterUri) if err != nil { - log.Fatal("Bad adapter URI:", adapterUri) + return nil, errors.New("bad adapter uri: " + adapterUri) } factory, found := AdapterFactories.Lookup(uri.Scheme) if !found { - log.Fatal("Unrecognized adapter:", adapterUri) - } - adapter := factory.New(uri) - err = adapter.Ping() - if err != nil { - log.Fatalf("%s: %s", uri.Scheme, err) + return nil, errors.New("unrecognized adapter: " + adapterUri) } + log.Println("Using", uri.Scheme, "adapter:", uri) return &Bridge{ docker: docker, config: config, - registry: adapter, + registry: factory.New(uri), services: make(map[string][]*Service), deadContainers: make(map[string]*DeadContainer), - } + }, nil +} + +func (b *Bridge) Ping() error { + return b.registry.Ping() } func (b *Bridge) Add(containerId string) { @@ -57,7 +61,7 @@ func (b *Bridge) Remove(containerId string) { } func (b *Bridge) RemoveOnExit(containerId string) { - b.remove(containerId, b.config.DeregisterCheck == "always" || b.didExitCleanly(containerId)) + b.remove(containerId, b.shouldRemove(containerId)) } func (b *Bridge) Refresh() { @@ -97,8 +101,7 @@ func (b *Bridge) Sync(quiet bool) { log.Printf("Syncing services on %d containers", len(containers)) - // NOTE: This assumes reregistering will do the right thing, i.e. nothing. - // NOTE: This will NOT remove services. + // NOTE: This assumes reregistering will do the right thing, i.e. nothing.. for _, listing := range containers { services := b.services[listing.ID] if services == nil { @@ -112,6 +115,69 @@ func (b *Bridge) Sync(quiet bool) { } } } + + // Clean up services that were registered previously, but aren't + // acknowledged within registrator + if b.config.Cleanup { + // Remove services if its corresponding container is not running + log.Println("Listing non-exited containers") + filters := map[string][]string{"status": {"created", "restarting", "running", "paused"}} + nonExitedContainers, err := b.docker.ListContainers(dockerapi.ListContainersOptions{Filters: filters}) + if err != nil { + log.Println("error listing nonExitedContainers, skipping sync", err) + return + } + for listingId, _ := range b.services { + found := false + for _, container := range nonExitedContainers { + if listingId == container.ID { + found = true + break + } + } + // This is a container that does not exist + if !found { + log.Printf("stale: Removing service %s because it does not exist", listingId) + go b.RemoveOnExit(listingId) + } + } + + log.Println("Cleaning up dangling services") + extServices, err := b.registry.Services() + if err != nil { + log.Println("cleanup failed:", err) + return + } + + Outer: + for _, extService := range extServices { + matches := serviceIDPattern.FindStringSubmatch(extService.ID) + if len(matches) != 3 { + // There's no way this was registered by us, so leave it + continue + } + serviceHostname := matches[1] + if serviceHostname != Hostname { + // ignore because registered on a different host + continue + } + serviceContainerName := matches[2] + for _, listing := range b.services { + for _, service := range listing { + if service.Name == extService.Name && serviceContainerName == service.Origin.container.Name[1:] { + continue Outer + } + } + } + log.Println("dangling:", extService.ID) + err := b.registry.Deregister(extService) + if err != nil { + log.Println("deregister failed:", extService.ID, err) + continue + } + log.Println(extService.ID, "removed") + } + } } func (b *Bridge) add(containerId string, quiet bool) { @@ -135,7 +201,8 @@ func (b *Bridge) add(containerId string, quiet bool) { ports := make(map[string]ServicePort) // Extract configured host port mappings, relevant when using --net=host - for port, published := range container.HostConfig.PortBindings { + for port, _ := range container.Config.ExposedPorts { + published := []dockerapi.PortBinding{ {"0.0.0.0", port.Port()}, } ports[string(port)] = servicePort(container, port, published) } @@ -149,14 +216,20 @@ func (b *Bridge) add(containerId string, quiet bool) { return } - for _, port := range ports { + servicePorts := make(map[string]ServicePort) + for key, port := range ports { if b.config.Internal != true && port.HostPort == "" { if !quiet { log.Println("ignored:", container.ID[:12], "port", port.ExposedPort, "not published on host") } continue } - service := b.newService(port, len(ports) > 1) + servicePorts[key] = port + } + + isGroup := len(servicePorts) > 1 + for _, port := range servicePorts { + service := b.newService(port, isGroup) if service == nil { if !quiet { log.Println("ignored:", container.ID[:12], "service on port", port.ExposedPort) @@ -176,20 +249,16 @@ func (b *Bridge) add(containerId string, quiet bool) { func (b *Bridge) newService(port ServicePort, isgroup bool) *Service { container := port.container defaultName := strings.Split(path.Base(container.Config.Image), ":")[0] - if isgroup { - defaultName = defaultName + "-" + port.ExposedPort - } // not sure about this logic. kind of want to remove it. - hostname, err := os.Hostname() - if err != nil { + hostname := Hostname + if hostname == "" { hostname = port.HostIP - } else { - if port.HostIP == "0.0.0.0" { - ip, err := net.ResolveIPAddr("ip", hostname) - if err == nil { - port.HostIP = ip.String() - } + } + if port.HostIP == "0.0.0.0" { + ip, err := net.ResolveIPAddr("ip", hostname) + if err == nil { + port.HostIP = ip.String() } } @@ -197,18 +266,30 @@ func (b *Bridge) newService(port ServicePort, isgroup bool) *Service { port.HostIP = b.config.HostIp } - metadata := serviceMetaData(container.Config, port.ExposedPort) + metadata, metadataFromPort := serviceMetaData(container.Config, port.ExposedPort) ignore := mapDefault(metadata, "ignore", "") if ignore != "" { return nil } + serviceName := mapDefault(metadata, "name", "") + if serviceName == "" { + if b.config.Explicit { + return nil + } + serviceName = defaultName + } + service := new(Service) service.Origin = port service.ID = hostname + ":" + container.Name[1:] + ":" + port.ExposedPort - service.Name = mapDefault(metadata, "name", defaultName) + service.Name = serviceName + if isgroup && !metadataFromPort["name"] { + service.Name += "-" + port.ExposedPort + } var p int + if b.config.Internal == true { service.IP = port.ExposedIP p, _ = strconv.Atoi(port.ExposedPort) @@ -218,6 +299,39 @@ func (b *Bridge) newService(port ServicePort, isgroup bool) *Service { } service.Port = p + if b.config.UseIpFromLabel != "" { + containerIp := container.Config.Labels[b.config.UseIpFromLabel] + if containerIp != "" { + slashIndex := strings.LastIndex(containerIp, "/") + if slashIndex > -1 { + service.IP = containerIp[:slashIndex] + } else { + service.IP = containerIp + } + log.Println("using container IP " + service.IP + " from label '" + + b.config.UseIpFromLabel + "'") + } else { + log.Println("Label '" + b.config.UseIpFromLabel + + "' not found in container configuration") + } + } + + // NetworkMode can point to another container (kuberenetes pods) + networkMode := container.HostConfig.NetworkMode + if networkMode != "" { + if strings.HasPrefix(networkMode, "container:") { + networkContainerId := strings.Split(networkMode, ":")[1] + log.Println(service.Name + ": detected container NetworkMode, linked to: " + networkContainerId[:12]) + networkContainer, err := b.docker.InspectContainer(networkContainerId) + if err != nil { + log.Println("unable to inspect network container:", networkContainerId[:12], err) + } else { + service.IP = networkContainer.NetworkSettings.IPAddress + log.Println(service.Name + ": using network container IP " + service.IP) + } + } + } + if port.PortType == "udp" { service.Tags = combineTags( mapDefault(metadata, "tags", ""), b.config.ForceTags, "udp") @@ -268,7 +382,13 @@ func (b *Bridge) remove(containerId string, deregister bool) { delete(b.services, containerId) } -func (b *Bridge) didExitCleanly(containerId string) bool { +// bit set on ExitCode if it represents an exit via a signal +var dockerSignaledBit = 128 + +func (b *Bridge) shouldRemove(containerId string) bool { + if b.config.DeregisterCheck == "always" { + return true + } container, err := b.docker.InspectContainer(containerId) if _, ok := err.(*dockerapi.NoSuchContainer); ok { // the container has already been removed from Docker @@ -276,9 +396,27 @@ func (b *Bridge) didExitCleanly(containerId string) bool { // so its exit code is not accessible log.Printf("registrator: container %v was removed, could not fetch exit code", containerId[:12]) return true - } else if err != nil { + } + + switch { + case err != nil: log.Printf("registrator: error fetching status for container %v on \"die\" event: %v\n", containerId[:12], err) return false + case container.State.Running: + log.Printf("registrator: not removing container %v, still running", containerId[:12]) + return false + case container.State.ExitCode == 0: + return true + case container.State.ExitCode&dockerSignaledBit == dockerSignaledBit: + return true } - return !container.State.Running && container.State.ExitCode == 0 + return false +} + +var Hostname string + +func init() { + // It's ok for Hostname to ultimately be an empty string + // An empty string will fall back to trying to make a best guess + Hostname, _ = os.Hostname() } diff --git a/bridge/bridge_test.go b/bridge/bridge_test.go new file mode 100644 index 000000000..7aa67e82e --- /dev/null +++ b/bridge/bridge_test.go @@ -0,0 +1,23 @@ +package bridge + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewError(t *testing.T) { + bridge, err := New(nil, "", Config{}) + assert.Nil(t, bridge) + assert.Error(t, err) +} + +func TestNewValid(t *testing.T) { + Register(new(fakeFactory), "fake") + // Note: the following is valid for New() since it does not + // actually connect to docker. + bridge, err := New(nil, "fake://", Config{}) + + assert.NotNil(t, bridge) + assert.NoError(t, err) +} diff --git a/bridge/extpoints.go b/bridge/extpoints.go index bf82346e2..dd46dcf31 100644 --- a/bridge/extpoints.go +++ b/bridge/extpoints.go @@ -139,4 +139,3 @@ func (ep *adapterFactoryExt) All() map[string]AdapterFactory { } return all } - diff --git a/bridge/types.go b/bridge/types.go index 6560f4683..e643ed3f5 100644 --- a/bridge/types.go +++ b/bridge/types.go @@ -16,15 +16,19 @@ type RegistryAdapter interface { Register(service *Service) error Deregister(service *Service) error Refresh(service *Service) error + Services() ([]*Service, error) } type Config struct { HostIp string Internal bool + Explicit bool + UseIpFromLabel string ForceTags string RefreshTtl int RefreshInterval int DeregisterCheck string + Cleanup bool } type Service struct { @@ -52,5 +56,6 @@ type ServicePort struct { PortType string ContainerHostname string ContainerID string + ContainerName string container *dockerapi.Container } diff --git a/bridge/types_test.go b/bridge/types_test.go new file mode 100644 index 000000000..0e3e63ef8 --- /dev/null +++ b/bridge/types_test.go @@ -0,0 +1,28 @@ +package bridge + +import "net/url" + +type fakeFactory struct{} + +func (f *fakeFactory) New(uri *url.URL) RegistryAdapter { + + return &fakeAdapter{} +} + +type fakeAdapter struct{} + +func (f *fakeAdapter) Ping() error { + return nil +} +func (f *fakeAdapter) Register(service *Service) error { + return nil +} +func (f *fakeAdapter) Deregister(service *Service) error { + return nil +} +func (f *fakeAdapter) Refresh(service *Service) error { + return nil +} +func (f *fakeAdapter) Services() ([]*Service, error) { + return nil, nil +} diff --git a/bridge/util.go b/bridge/util.go index b151e533c..e3c295cfb 100644 --- a/bridge/util.go +++ b/bridge/util.go @@ -20,26 +20,54 @@ func mapDefault(m map[string]string, key, default_ string) string { return v } +// Golang regexp module does not support /(?!\\),/ syntax for spliting by not escaped comma +// Then this function is reproducing it +func recParseEscapedComma(str string) []string { + if len(str) == 0 { + return []string{} + } else if str[0] == ',' { + return recParseEscapedComma(str[1:]) + } + + offset := 0 + for len(str[offset:]) > 0 { + index := strings.Index(str[offset:], ",") + + if index == -1 { + break + } else if str[offset+index-1:offset+index] != "\\" { + return append(recParseEscapedComma(str[offset+index+1:]), str[:offset+index]) + } + + str = str[:offset+index-1] + str[offset+index:] + offset += index + } + + return []string{str} +} + func combineTags(tagParts ...string) []string { tags := make([]string, 0) for _, element := range tagParts { - if element != "" { - tags = append(tags, strings.Split(element, ",")...) - } + tags = append(tags, recParseEscapedComma(element)...) } return tags } -func serviceMetaData(config *dockerapi.Config, port string) map[string]string { +func serviceMetaData(config *dockerapi.Config, port string) (map[string]string, map[string]bool) { meta := config.Env for k, v := range config.Labels { - meta = append(meta, k + "=" + v) + meta = append(meta, k+"="+v) } metadata := make(map[string]string) + metadataFromPort := make(map[string]bool) for _, kv := range meta { kvp := strings.SplitN(kv, "=", 2) if strings.HasPrefix(kvp[0], "SERVICE_") && len(kvp) > 1 { key := strings.ToLower(strings.TrimPrefix(kvp[0], "SERVICE_")) + if metadataFromPort[key] { + continue + } portkey := strings.SplitN(key, "_", 2) _, err := strconv.Atoi(portkey[0]) if err == nil && len(portkey) > 1 { @@ -47,16 +75,17 @@ func serviceMetaData(config *dockerapi.Config, port string) map[string]string { continue } metadata[portkey[1]] = kvp[1] + metadataFromPort[portkey[1]] = true } else { metadata[key] = kvp[1] } } } - return metadata + return metadata, metadataFromPort } func servicePort(container *dockerapi.Container, port dockerapi.Port, published []dockerapi.PortBinding) ServicePort { - var hp, hip string + var hp, hip, ep, ept, eip, nm string if len(published) > 0 { hp = published[0].HostPort hip = published[0].HostIP @@ -64,13 +93,37 @@ func servicePort(container *dockerapi.Container, port dockerapi.Port, published if hip == "" { hip = "0.0.0.0" } - p := strings.Split(string(port), "/") + + //for overlay networks + //detect if container use overlay network, than set HostIP into NetworkSettings.Network[string].IPAddress + //better to use registrator with -internal flag + nm = container.HostConfig.NetworkMode + if nm != "bridge" && nm != "default" && nm != "host" { + hip = container.NetworkSettings.Networks[nm].IPAddress + } + + exposedPort := strings.Split(string(port), "/") + ep = exposedPort[0] + if len(exposedPort) == 2 { + ept = exposedPort[1] + } else { + ept = "tcp" // default + } + + // Nir: support docker NetworkSettings + eip = container.NetworkSettings.IPAddress + if eip == "" { + for _, network := range container.NetworkSettings.Networks { + eip = network.IPAddress + } + } + return ServicePort{ HostPort: hp, HostIP: hip, - ExposedPort: p[0], - ExposedIP: container.NetworkSettings.IPAddress, - PortType: p[1], + ExposedPort: ep, + ExposedIP: eip, + PortType: ept, ContainerID: container.ID, ContainerHostname: container.Config.Hostname, container: container, diff --git a/bridge/util_test.go b/bridge/util_test.go new file mode 100644 index 000000000..149186a9b --- /dev/null +++ b/bridge/util_test.go @@ -0,0 +1,59 @@ +package bridge + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEscapedComma(t *testing.T) { + cases := []struct { + Tag string + Expected []string + }{ + { + Tag: "", + Expected: []string{}, + }, + { + Tag: "foobar", + Expected: []string{"foobar"}, + }, + { + Tag: "foo,bar", + Expected: []string{"foo", "bar"}, + }, + { + Tag: "foo\\,bar", + Expected: []string{"foo,bar"}, + }, + { + Tag: "foo,bar\\,baz", + Expected: []string{"foo", "bar,baz"}, + }, + { + Tag: "\\,foobar\\,", + Expected: []string{",foobar,"}, + }, + { + Tag: ",,,,foo,,,bar,,,", + Expected: []string{"foo", "bar"}, + }, + { + Tag: ",,,,", + Expected: []string{}, + }, + { + Tag: ",,\\,,", + Expected: []string{","}, + }, + } + + for _, c := range cases { + results := recParseEscapedComma(c.Tag) + sort.Strings(c.Expected) + sort.Strings(results) + assert.EqualValues(t, c.Expected, results) + } +} diff --git a/circle.yml b/circle.yml index a1489c4df..e72781134 100644 --- a/circle.yml +++ b/circle.yml @@ -1,25 +1,27 @@ -machine: - services: - - docker - -dependencies: - pre: - - make circleci - override: - - make build - post: - - cp build/* $CIRCLE_ARTIFACTS - -test: - override: - - /bin/true - -deployment: - master: - branch: master - commands: - - eval $(docker run gliderlabs/pagebuilder circleci-cmd) - release: - branch: release - commands: - - make release +version: 2 +jobs: + build: + machine: true + steps: + - checkout + - run: + command: make circleci + - run: + name: Build + command: make build + - store_artifacts: + path: build + - deploy: + name: Deploy website + command: | + if [[ "$CIRCLE_BRANCH" == "master" ]]; then + mv .dockerignore .dockerignore-repo + docker run -v /home/circleci/.ssh:/tmp/ssh -v $PWD:/work -e MASTER=$MASTER -e TAG=$TAG gliderlabs/pagebuilder deploy "build $CIRCLE_BUILD_NUM" + mv .dockerignore-repo .dockerignore + fi + - deploy: + name: Deploy beta channel + command: | + if [[ "$CIRCLE_BRANCH" == "release" ]]; then + make release + fi diff --git a/consul/consul.go b/consul/consul.go index 8c1bc5eac..0348c9a06 100644 --- a/consul/consul.go +++ b/consul/consul.go @@ -4,21 +4,27 @@ import ( "fmt" "log" "net/url" + "os" + "strconv" "strings" "github.com/gliderlabs/registrator/bridge" consulapi "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-cleanhttp" ) const DefaultInterval = "10s" func init() { - bridge.Register(new(Factory), "consul") + f := new(Factory) + bridge.Register(f, "consul") + bridge.Register(f, "consul-tls") + bridge.Register(f, "consul-unix") } func (r *ConsulAdapter) interpolateService(script string, service *bridge.Service) string { - withIp := strings.Replace(script, "$SERVICE_IP", service.Origin.HostIP, -1) - withPort := strings.Replace(withIp, "$SERVICE_PORT", service.Origin.HostPort, -1) + withIp := strings.Replace(script, "$SERVICE_IP", service.IP, -1) + withPort := strings.Replace(withIp, "$SERVICE_PORT", strconv.Itoa(service.Port), -1) return withPort } @@ -26,7 +32,26 @@ type Factory struct{} func (f *Factory) New(uri *url.URL) bridge.RegistryAdapter { config := consulapi.DefaultConfig() - if uri.Host != "" { + if uri.Scheme == "consul-unix" { + config.Address = strings.TrimPrefix(uri.String(), "consul-") + } else if uri.Scheme == "consul-tls" { + tlsConfigDesc := &consulapi.TLSConfig{ + Address: uri.Host, + CAFile: os.Getenv("CONSUL_CACERT"), + CertFile: os.Getenv("CONSUL_TLSCERT"), + KeyFile: os.Getenv("CONSUL_TLSKEY"), + InsecureSkipVerify: false, + } + tlsConfig, err := consulapi.SetupTLSConfig(tlsConfigDesc) + if err != nil { + log.Fatal("Cannot set up Consul TLSConfig", err) + } + config.Scheme = "https" + transport := cleanhttp.DefaultPooledTransport() + transport.TLSClientConfig = tlsConfig + config.HttpClient.Transport = transport + config.Address = uri.Host + } else if uri.Host != "" { config.Address = uri.Host } client, err := consulapi.NewClient(config) @@ -60,32 +85,49 @@ func (r *ConsulAdapter) Register(service *bridge.Service) error { registration.Tags = service.Tags registration.Address = service.IP registration.Check = r.buildCheck(service) + registration.Meta = service.Attrs return r.client.Agent().ServiceRegister(registration) } func (r *ConsulAdapter) buildCheck(service *bridge.Service) *consulapi.AgentServiceCheck { check := new(consulapi.AgentServiceCheck) + if status := service.Attrs["check_initial_status"]; status != "" { + check.Status = status + } if path := service.Attrs["check_http"]; path != "" { check.HTTP = fmt.Sprintf("http://%s:%d%s", service.IP, service.Port, path) if timeout := service.Attrs["check_timeout"]; timeout != "" { check.Timeout = timeout } + } else if path := service.Attrs["check_https"]; path != "" { + check.HTTP = fmt.Sprintf("https://%s:%d%s", service.IP, service.Port, path) + if timeout := service.Attrs["check_timeout"]; timeout != "" { + check.Timeout = timeout + } } else if cmd := service.Attrs["check_cmd"]; cmd != "" { check.Script = fmt.Sprintf("check-cmd %s %s %s", service.Origin.ContainerID[:12], service.Origin.ExposedPort, cmd) } else if script := service.Attrs["check_script"]; script != "" { check.Script = r.interpolateService(script, service) } else if ttl := service.Attrs["check_ttl"]; ttl != "" { check.TTL = ttl + } else if tcp := service.Attrs["check_tcp"]; tcp != "" { + check.TCP = fmt.Sprintf("%s:%d", service.IP, service.Port) + if timeout := service.Attrs["check_timeout"]; timeout != "" { + check.Timeout = timeout + } } else { return nil } - if check.Script != "" || check.HTTP != "" { + if check.Script != "" || check.HTTP != "" || check.TCP != "" { if interval := service.Attrs["check_interval"]; interval != "" { check.Interval = interval } else { check.Interval = DefaultInterval } } + if deregister_after := service.Attrs["check_deregister_after"]; deregister_after != "" { + check.DeregisterCriticalServiceAfter = deregister_after + } return check } @@ -96,3 +138,24 @@ func (r *ConsulAdapter) Deregister(service *bridge.Service) error { func (r *ConsulAdapter) Refresh(service *bridge.Service) error { return nil } + +func (r *ConsulAdapter) Services() ([]*bridge.Service, error) { + services, err := r.client.Agent().Services() + if err != nil { + return []*bridge.Service{}, err + } + out := make([]*bridge.Service, len(services)) + i := 0 + for _, v := range services { + s := &bridge.Service{ + ID: v.ID, + Name: v.Service, + Port: v.Port, + Tags: v.Tags, + IP: v.Address, + } + out[i] = s + i++ + } + return out, nil +} diff --git a/consulkv/consulkv.go b/consulkv/consulkv.go index 6947a7666..7d5cc12b3 100644 --- a/consulkv/consulkv.go +++ b/consulkv/consulkv.go @@ -5,27 +5,34 @@ import ( "net" "net/url" "strconv" + "strings" "github.com/gliderlabs/registrator/bridge" consulapi "github.com/hashicorp/consul/api" ) func init() { - bridge.Register(new(Factory), "consulkv") + f := new(Factory) + bridge.Register(f, "consulkv") + bridge.Register(f, "consulkv-unix") } type Factory struct{} func (f *Factory) New(uri *url.URL) bridge.RegistryAdapter { config := consulapi.DefaultConfig() - if uri.Host != "" { + path := uri.Path + if uri.Scheme == "consulkv-unix" { + spl := strings.SplitN(uri.Path, ":", 2) + config.Address, path = "unix://"+spl[0], spl[1] + } else if uri.Host != "" { config.Address = uri.Host } client, err := consulapi.NewClient(config) if err != nil { log.Fatal("consulkv: ", uri.Scheme) } - return &ConsulKVAdapter{client: client, path: uri.Path} + return &ConsulKVAdapter{client: client, path: path} } type ConsulKVAdapter struct { @@ -46,9 +53,11 @@ func (r *ConsulKVAdapter) Ping() error { } func (r *ConsulKVAdapter) Register(service *bridge.Service) error { + log.Println("Register") path := r.path[1:] + "/" + service.Name + "/" + service.ID port := strconv.Itoa(service.Port) addr := net.JoinHostPort(service.IP, port) + log.Printf("path: %s", path) _, err := r.client.KV().Put(&consulapi.KVPair{Key: path, Value: []byte(addr)}, nil) if err != nil { log.Println("consulkv: failed to register service:", err) @@ -68,3 +77,7 @@ func (r *ConsulKVAdapter) Deregister(service *bridge.Service) error { func (r *ConsulKVAdapter) Refresh(service *bridge.Service) error { return nil } + +func (r *ConsulKVAdapter) Services() ([]*bridge.Service, error) { + return []*bridge.Service{}, nil +} diff --git a/docs/index.md b/docs/index.md index 56b7ccc70..757c11e92 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,6 +4,7 @@ Service registry bridge for Docker, sponsored by [Weave](http://weave.works). [![Circle CI](https://circleci.com/gh/gliderlabs/registrator.png?style=shield)](https://circleci.com/gh/gliderlabs/registrator) [![Docker Hub](https://img.shields.io/badge/docker-ready-blue.svg)](https://registry.hub.docker.com/u/gliderlabs/registrator/) +[![ImageLayers Size](https://img.shields.io/imagelayers/image-size/gliderlabs/registrator/latest.svg)](https://imagelayers.io/?images=gliderlabs%2Fregistrator:latest) [![IRC Channel](https://img.shields.io/badge/irc-%23gliderlabs-blue.svg)](https://kiwiirc.com/client/irc.freenode.net/#gliderlabs)

@@ -19,6 +20,9 @@ Get the latest release, master, or any version of Registrator via [Docker Hub](h $ docker pull gliderlabs/registrator:latest +Latest tag always points to the latest release. There is also a `:master` tag +and version tags to pin to specific releases. + ## Using Registrator The quickest way to see Registrator in action is our @@ -44,8 +48,7 @@ and [Staging Releases](dev/releases.md). ## Sponsors and Thanks -Ongoing support of this project is made possible by [Weave](http://weave.works), -the Docker SDN. Big thanks to Michael Crosby for +Ongoing support of this project is made possible by [Weave](http://weave.works), the easiest way to connect, observe and control your containers. Big thanks to Michael Crosby for [skydock](https://github.com/crosbymichael/skydock) and the Consul mailing list for inspiration. diff --git a/docs/user/backends.md b/docs/user/backends.md index 07a737b05..55233e6fd 100644 --- a/docs/user/backends.md +++ b/docs/user/backends.md @@ -9,6 +9,8 @@ See also [Contributing Backends](../dev/backends.md). ## Consul consul://
: + consul-unix:// + consul-tls://
: Consul is the recommended registry since it specifically models services for service discovery with health checks. @@ -17,6 +19,13 @@ If no address and port is specified, it will default to `127.0.0.1:8500`. Consul supports tags but no arbitrary service attributes. +When using the `consul-tls` scheme, registrator communicates with Consul through TLS. You must set the following environment variables: + * `CONSUL_CACERT` : CA file location + * `CONSUL_TLSCERT` : Certificate file location + * `CONSUL_TLSKEY` : Key location + +For more information on the Consul check parameters below, see the [API documentation](https://www.consul.io/api/agent/check.html#register-check). + ### Consul HTTP Check This feature is only available when using Consul 0.5 or newer. Containers @@ -32,6 +41,30 @@ SERVICE_80_CHECK_TIMEOUT=1s # optional, Consul default used otherwise It works for services on any port, not just 80. If its the only service, you can also use `SERVICE_CHECK_HTTP`. +### Consul HTTPS Check + +This feature is only available when using Consul 0.5 or newer. Containers +specifying these extra metedata in labels or environment will be used to +register an HTTPS health check with the service. + +```bash +SERVICE_443_CHECK_HTTPS=/health/endpoint/path +SERVICE_443_CHECK_INTERVAL=15s +SERVICE_443_CHECK_TIMEOUT=1s # optional, Consul default used otherwise +``` + +### Consul TCP Check + +This feature is only available when using Consul 0.6 or newer. Containers +specifying these extra metadata in labels or environment will be used to +register an TCP health check with the service. + +```bash +SERVICE_443_CHECK_TCP=true +SERVICE_443_CHECK_INTERVAL=15s +SERVICE_443_CHECK_TIMEOUT=3s # optional, Consul default used otherwise +``` + ### Consul Script Check This feature is tricky because it lets you specify a script check to run from @@ -60,9 +93,27 @@ healthy. SERVICE_CHECK_TTL=30s ``` +### Consul Initial Health Check Status + +By default when a service is registered against Consul, the state is set to "critical". You can specify the initial health check status. + +```bash +SERVICE_CHECK_INITIAL_STATUS=passing +``` + +### Consul Critical Service Deregistration + +Consul can deregister a service if the check is in the critical state for more than a configurable amount of time. +If enabled this should be much longer than any expected recoverable outage. + +```bash +SERVICE_CHECK_DEREGISTER_AFTER=10m +``` + ## Consul KV consulkv://
:/ + consulkv-unix://:/ This is a separate backend to use Consul's key-value store instead of its native service catalog. This behaves more like etcd since it has similar semantics, but @@ -81,7 +132,7 @@ Using the prefix from the Registry URI, service definitions are stored as: Etcd works similar to Consul KV, except supports service TTLs. It also currently doesn't support service attributes/tags. -If no address and port is specified, it will default to `127.0.0.1:4001`. +If no address and port is specified, it will default to `127.0.0.1:2379`. Using the prefix from the Registry URI, service definitions are stored as: @@ -94,7 +145,7 @@ Using the prefix from the Registry URI, service definitions are stored as: SkyDNS 2 uses etcd, so this backend writes service definitions in a format compatible with SkyDNS 2. The path may not be omitted and must be a valid DNS domain for SkyDNS. -If no address and port is specified, it will default to `127.0.0.1:4001`. +If no address and port is specified, it will default to `127.0.0.1:2379`. Using a Registry URI with the domain `cluster.local`, service definitions are stored as: @@ -104,3 +155,22 @@ SkyDNS requires the service ID to be a valid DNS hostname, so this backend requi override service ID to a valid DNS name. Example: $ docker run -d --name redis-1 -e SERVICE_ID=redis-1 -p 6379:6379 redis + +## Zookeeper Store + +The Zookeeper backend lets you publish ephemeral znodes into zookeeper. This mode is enabled by specifying a zookeeper path. The zookeeper backend supports publishing a json znode body complete with defined service attributes/tags as well as the service name and container id. Example URIs: + + $ registrator zookeeper://zookeeper.host/basepath + $ registrator zookeeper://192.168.1.100:9999/basepath + +Within the base path specified in the zookeeper URI, registrator will create the following path tree containing a JSON entry for the service: + + / = + +The JSON will contain all infromation about the published container service. As an example, the following container start: + + docker run -i -p 80 -e 'SERVICE_80_NAME=www' -t ubuntu:14.04 /bin/bash + +Will result in the zookeeper path and JSON znode body: + + /basepath/www/80 = {"Name":"www","IP":"192.168.1.123","PublicPort":49153,"PrivatePort":80,"ContainerID":"9124853ff0d1","Tags":[],"Attrs":{}} diff --git a/docs/user/quickstart.md b/docs/user/quickstart.md index 3f854203a..c0eafd3e7 100644 --- a/docs/user/quickstart.md +++ b/docs/user/quickstart.md @@ -16,7 +16,8 @@ container that will automatically get added to Consul. ## Before Starting We're going to need a host running Docker, which could just be a local -boot2docker VM, and a shell with the `docker` client pointed to that host. +[boot2docker](http://boot2docker.io/) VM, and a shell with the `docker` client +pointed to that host. We'll also need to have Consul running, which can just be running in a container. Let's run a single instance of Consul in server bootstrap mode: diff --git a/docs/user/run.md b/docs/user/run.md index 86f10a059..5c084474b 100644 --- a/docs/user/run.md +++ b/docs/user/run.md @@ -31,15 +31,19 @@ hostname (`-h $HOSTNAME`) and using the `-ip` Registrator option below. ## Registrator Options -Option | Description ------- | ----------- -`-internal` | Use exposed ports instead of published ports -`-ip ` | Force IP address used for registering services -`-tags ` | Force comma-separated tags on all registered services -`-deregister ` | Deregister existed services "always" or "on-success". Default: always -`-ttl ` | TTL for services. Default: 0, no expiry (supported backends only) -`-ttl-refresh ` | Frequency service TTLs are refreshed (supported backends only) -`-resync ` | Frequency all services are resynchronized. Default: 0, never +Option | Since | Description +------ | ----- | ----------- +`-cleanup` | v7 | Cleanup dangling services +`-deregister ` | v6 | Deregister exited services "always" or "on-success". Default: always +`-internal` | | Use exposed ports instead of published ports +`-ip ` | | Force IP address used for registering services +`-resync ` | v6 | Frequency all services are resynchronized. Default: 0, never +`-retry-attempts ` | v7 | Max retry attempts to establish a connection with the backend +`-retry-interval ` | v7 | Interval (in millisecond) between retry-attempts +`-tags ` | v5 | Force comma-separated tags on all registered services +`-ttl ` | | TTL for services. Default: 0, no expiry (supported backends only) +`-ttl-refresh ` | | Frequency service TTLs are refreshed (supported backends only) +`-useIpFromLabel