mirror of
https://github.com/valitydev/registrator.git
synced 2024-11-06 02:45:17 +00:00
Automatically register IPv6 container addresses for services
You need to pass a new flag, `-ipv6`, to registrator, in order to enable this support, to prevent surprising accidents, but once you do, everything Just Works with feature parity between IPv4 and IPv6 addresses. You can also manage the overrides on a per-address-family basis, if you want to go super-fancy. Also has documentation!
This commit is contained in:
parent
1a1a9ae684
commit
d6f6d9e8e6
@ -229,24 +229,46 @@ func (b *Bridge) add(containerId string, quiet bool) {
|
||||
|
||||
isGroup := len(servicePorts) > 1
|
||||
for _, port := range servicePorts {
|
||||
service := b.newService(port, isGroup)
|
||||
if service == nil {
|
||||
services := b.newServices(port, isGroup, 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)
|
||||
}
|
||||
b.services[container.ID] = append(b.services[container.ID], service)
|
||||
log.Println("added:", container.ID[:12], service.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bridge) newService(port ServicePort, isgroup bool) *Service {
|
||||
func (b *Bridge) newServices(port ServicePort, isgroup, quiet bool) []Service {
|
||||
services := make([]Service, 0)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
func (b *Bridge) newService(port ServicePort, isgroup, quiet, ipv6 bool) *Service {
|
||||
container := port.container
|
||||
defaultName := strings.Split(path.Base(container.Config.Image), ":")[0]
|
||||
|
||||
@ -266,7 +288,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 != "" {
|
||||
@ -276,13 +298,21 @@ 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 {
|
||||
@ -290,6 +320,9 @@ func (b *Bridge) newService(port ServicePort, isgroup bool) *Service {
|
||||
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 b.config.UseIpFromLabel != "" {
|
||||
containerIp := container.Config.Labels[b.config.UseIpFromLabel]
|
||||
|
@ -23,6 +23,7 @@ type Config struct {
|
||||
HostIp string
|
||||
Internal bool
|
||||
UseIpFromLabel string
|
||||
IPv6 bool
|
||||
ForceTags string
|
||||
RefreshTtl int
|
||||
RefreshInterval int
|
||||
|
@ -54,18 +54,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)
|
||||
@ -75,9 +89,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
dockerapi "github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
func TestEscapedComma(t *testing.T) {
|
||||
@ -57,3 +58,100 @@ func TestEscapedComma(t *testing.T) {
|
||||
assert.EqualValues(t, c.Expected, results)
|
||||
}
|
||||
}
|
||||
|
||||
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" })
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -21,6 +21,7 @@ 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 useIpFromLabel = flag.String("useIpFromLabel", "", "Use IP which is stored in a label assigned to the container")
|
||||
var ipv6 = flag.Bool("ipv6", false, "Register services with container IPv6 addresses, if available")
|
||||
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")
|
||||
@ -100,6 +101,7 @@ func main() {
|
||||
HostIp: *hostIp,
|
||||
Internal: *internal,
|
||||
UseIpFromLabel: *useIpFromLabel,
|
||||
IPv6: *ipv6,
|
||||
ForceTags: *forceTags,
|
||||
RefreshTtl: *refreshTtl,
|
||||
RefreshInterval: *refreshInterval,
|
||||
|
Loading…
Reference in New Issue
Block a user