Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 46 additions & 11 deletions bridge/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,24 +200,48 @@ func (b *Bridge) add(containerId string, quiet bool) {
}
continue
}
service := b.newService(port, len(ports) > 1)
if service == nil {
services := b.newServices(port, len(ports) > 1, quiet)
if len(services) == 0 {
if !quiet {
log.Println("ignored:", container.ID[:12], "service on port", port.ExposedPort)
}
continue
}
err := b.registry.Register(service)
if err != nil {
log.Println("register failed:", service, err)
continue
for _, service := range services {
err := b.registry.Register(&service)
if err != nil {
log.Println("register failed:", service, err)
continue
}
b.services[container.ID] = append(b.services[container.ID], &service)
log.Println("added:", container.ID[:12], service.ID)
}
}
}

func (b *Bridge) newServices(port ServicePort, isgroup, quiet bool) []Service {
services := make([]Service, 0)

if b.config.IPv4 {
svc := b.newService(port, isgroup, quiet, false)

if svc != nil {
services = append(services, *svc)
}
}

if b.config.IPv6 && port.container.NetworkSettings.GlobalIPv6Address != "" {
svc := b.newService(port, isgroup, quiet, true)

if svc != nil {
services = append(services, *svc)
}
b.services[container.ID] = append(b.services[container.ID], service)
log.Println("added:", container.ID[:12], service.ID)
}

return services
}

func (b *Bridge) newService(port ServicePort, isgroup bool) *Service {
func (b *Bridge) newService(port ServicePort, isgroup, quiet, ipv6 bool) *Service {
container := port.container
defaultName := strings.Split(path.Base(container.Config.Image), ":")[0]

Expand All @@ -237,7 +261,7 @@ func (b *Bridge) newService(port ServicePort, isgroup bool) *Service {
port.HostIP = b.config.HostIp
}

metadata, metadataFromPort := serviceMetaData(container.Config, port.ExposedPort)
metadata, metadataFromPort := serviceMetaData(container.Config, port.ExposedPort, ipv6)

ignore := mapDefault(metadata, "ignore", "")
if ignore != "" {
Expand All @@ -247,19 +271,30 @@ func (b *Bridge) newService(port ServicePort, isgroup bool) *Service {
service := new(Service)
service.Origin = port
service.ID = hostname + ":" + container.Name[1:] + ":" + port.ExposedPort
if ipv6 {
service.ID += ":ipv6"
}
service.Name = mapDefault(metadata, "name", defaultName)
if isgroup && !metadataFromPort["name"] {
service.Name += "-" + port.ExposedPort
}
var p int
if b.config.Internal == true {
// We *always* expose the container's address and port for IPv6. It's
// pointless having an address that supports end-to-end if we ignore it.
if ipv6 {
service.IP = port.container.NetworkSettings.GlobalIPv6Address
p, _ = strconv.Atoi(port.ExposedPort)
} else if b.config.Internal == true {
service.IP = port.ExposedIP
p, _ = strconv.Atoi(port.ExposedPort)
} else {
service.IP = port.HostIP
p, _ = strconv.Atoi(port.HostPort)
}
service.Port = p
if !quiet {
log.Println("service:", service.ID, "is named", service.Name, "and is on", service.IP, "port", service.Port)
}

if port.PortType == "udp" {
service.Tags = combineTags(
Expand Down
2 changes: 2 additions & 0 deletions bridge/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type RegistryAdapter interface {
type Config struct {
HostIp string
Internal bool
IPv4 bool
IPv6 bool
ForceTags string
RefreshTtl int
RefreshInterval int
Expand Down
31 changes: 26 additions & 5 deletions bridge/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,32 @@ func combineTags(tagParts ...string) []string {
return tags
}

func serviceMetaData(config *dockerapi.Config, port string) (map[string]string, map[string]bool) {
func serviceMetaData(config *dockerapi.Config, port string, ipv6 bool) (map[string]string, map[string]bool) {
meta := config.Env
for k, v := range config.Labels {
meta = append(meta, k+"="+v)
}
metadata := make(map[string]string)
metadataFromPort := make(map[string]bool)
metadata := make(map[string]string)
metadataFromPort := make(map[string]bool)
metadataFromAF := make(map[string]bool)
metadataFromPortAF := make(map[string]bool)
for _, kv := range meta {
kvp := strings.SplitN(kv, "=", 2)
if (strings.HasSuffix(kvp[0], "_IPV4") && ipv6) || (strings.HasSuffix(kvp[0], "_IPV6") && !ipv6) {
// We can definitely ignore any key with the wrong address family suffix
continue
}
if strings.HasPrefix(kvp[0], "SERVICE_") && len(kvp) > 1 {
af := false
if strings.HasSuffix(kvp[0], "_IPV4") {
af = true
kvp[0] = strings.TrimSuffix(kvp[0], "_IPV4")
} else if strings.HasSuffix(kvp[0], "_IPV6") {
af = true
kvp[0] = strings.TrimSuffix(kvp[0], "_IPV6")
}
key := strings.ToLower(strings.TrimPrefix(kvp[0], "SERVICE_"))
if metadataFromPort[key] {
if metadataFromPortAF[key] || (!af && metadataFromPort[key]) {
continue
}
portkey := strings.SplitN(key, "_", 2)
Expand All @@ -51,9 +65,16 @@ func serviceMetaData(config *dockerapi.Config, port string) (map[string]string,
continue
}
metadata[portkey[1]] = kvp[1]
metadataFromPort[portkey[1]] = true
if af {
metadataFromPortAF[portkey[1]] = true
} else {
metadataFromPort[portkey[1]] = true
}
} else {
metadata[key] = kvp[1]
if af {
metadataFromAF[key] = true
}
}
}
}
Expand Down
105 changes: 105 additions & 0 deletions bridge/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package bridge

import (
"testing"

"github.com/stretchr/testify/assert"
dockerapi "github.com/fsouza/go-dockerclient"
)

func containerMetaDataGlobalOnly() *dockerapi.Config {
cfg := new(dockerapi.Config)
cfg.Env = []string {
"SERVICE_NAME=something",
}
return cfg
}

func containerMetaDataGlobalAF() *dockerapi.Config {
cfg := new(dockerapi.Config)
cfg.Env = []string {
"SERVICE_NAME=something",
"SERVICE_NAME_IPV4=lollerskates",
"SERVICE_NAME_IPV6=loll:ersk:ates",
}
return cfg
}

func containerMetaDataPort80() *dockerapi.Config {
cfg := new(dockerapi.Config)
cfg.Env = []string {
"SERVICE_NAME=something",
"SERVICE_NAME_IPV6=loll:ersk:ates",
"SERVICE_80_NAME=somethingelse",
}
return cfg
}

func containerMetaDataPort80AF() *dockerapi.Config {
cfg := new(dockerapi.Config)
cfg.Env = []string {
"SERVICE_NAME=something",
"SERVICE_NAME_IPV4=ermahgerd",
"SERVICE_NAME_IPV4=ermahgerd6",
"SERVICE_80_NAME=somethingelse",
"SERVICE_80_NAME_IPV4=lollerskates",
"SERVICE_80_NAME_IPV6=loll:ersk:ates",
}
return cfg
}

func TestServiceMetaDataCustomName(t *testing.T) {
metadata, _ := serviceMetaData(containerMetaDataGlobalOnly(), "80", false)

assert.NotNil(t, metadata)
assert.Equal(t, metadata, map[string]string { "name": "something" })
}

func TestServiceMetaDataCustomPortName(t *testing.T) {
metadata, _ := serviceMetaData(containerMetaDataPort80(), "80", false)

assert.NotNil(t, metadata)
assert.Equal(t, metadata, map[string]string { "name": "somethingelse" })
}

func TestServiceMetaDataDifferentPort(t *testing.T) {
metadata, _ := serviceMetaData(containerMetaDataPort80(), "443", false)

assert.NotNil(t, metadata)
assert.Equal(t, metadata, map[string]string { "name": "something" })
}

func TestServiceMetaDataDifferentPortIPv6(t *testing.T) {
metadata, _ := serviceMetaData(containerMetaDataPort80(), "443", true)

assert.NotNil(t, metadata)
assert.Equal(t, metadata, map[string]string { "name": "loll:ersk:ates" })
}

func TestServiceMetaDataIPv4Override(t *testing.T) {
metadata, _ := serviceMetaData(containerMetaDataGlobalAF(), "80", false)

assert.NotNil(t, metadata)
assert.Equal(t, metadata, map[string]string { "name": "lollerskates" })
}

func TestServiceMetaDataIPv6Override(t *testing.T) {
metadata, _ := serviceMetaData(containerMetaDataGlobalAF(), "80", true)

assert.NotNil(t, metadata)
assert.Equal(t, metadata, map[string]string { "name": "loll:ersk:ates" })
}

func TestServiceMetaDataCustomPortIPv4Override(t *testing.T) {
metadata, _ := serviceMetaData(containerMetaDataPort80AF(), "80", false)

assert.NotNil(t, metadata)
assert.Equal(t, metadata, map[string]string { "name": "lollerskates" })
}

func TestServiceMetaDataCustomPortIPv6Override(t *testing.T) {
metadata, _ := serviceMetaData(containerMetaDataPort80AF(), "80", true)

assert.NotNil(t, metadata)
assert.Equal(t, metadata, map[string]string { "name": "loll:ersk:ates" })
}
20 changes: 20 additions & 0 deletions docs/user/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ differentiate from a TCP service that could be listening on the same port.
Although this can be overridden on containers with `SERVICE_ID` or
`SERVICE_x_ID`, it is not recommended.

## IPv6 Support

If you have Docker running with IPv6 enabled, and you pass the `-ipv6` flag
to Registrator, then Registrator will register services with the IPv6
address of the container.

All the same container override parameters are available to control naming,
tagging, etc of your services, and they apply by default to both the IPv4
and IPv6 versions of the service. If you wish to apply an override to only
the IPv4 or IPv6 version of the service, you may suffix your environment
variable or label with `_IPV6` or `_IPV4`, as appropriate, to either the
port-specific key (`SERVICE_<port>_<key>`) or the global key
(`SERVICE_<key>`). The order of precedence, from highest to lowest, is:

1. `SERVICE_<port>_<key>_(IPV4|IPV6)`
1. `SERVICE_<port>_<key>`
1. `SERVICE_<key>_(IPV4|IPV6)`
1. `SERVICE_<key>`


## Examples

### Single service with defaults
Expand Down
4 changes: 4 additions & 0 deletions registrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ var versionChecker = usage.NewChecker("registrator", Version)

var hostIp = flag.String("ip", "", "IP for ports mapped to the host")
var internal = flag.Bool("internal", false, "Use internal ports instead of published ones")
var ipv6 = flag.Bool("ipv6", false, "Register services with container IPv6 addresses, if available")
var ipv4 = flag.Bool("ipv4", true, "Register services with IPv4 addresses")
var refreshInterval = flag.Int("ttl-refresh", 0, "Frequency with which service TTLs are refreshed")
var refreshTtl = flag.Int("ttl", 0, "TTL for services (default is no expiry)")
var forceTags = flag.String("tags", "", "Append tags for all registered services")
Expand Down Expand Up @@ -98,6 +100,8 @@ func main() {
b, err := bridge.New(docker, flag.Arg(0), bridge.Config{
HostIp: *hostIp,
Internal: *internal,
IPv4: *ipv4,
IPv6: *ipv6,
ForceTags: *forceTags,
RefreshTtl: *refreshTtl,
RefreshInterval: *refreshInterval,
Expand Down