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