Uses both binded HostIP and HostPort when useBindPortIP=true

This commit is contained in:
Gérald Croës 2018-07-19 16:40:03 +02:00 committed by Traefiker Bot
parent 853be929bc
commit d50b6a34bc
6 changed files with 279 additions and 21 deletions

View file

@ -33,7 +33,7 @@ func (p *Provider) buildConfigurationV2(containersInspected []dockerData) *types
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain),
// Backend functions
"getIPAddress": p.getIPAddress,
"getIPAddress": p.getDeprecatedIPAddress, // TODO: Should we expose getIPPort instead?
"getServers": p.getServers,
"getMaxConn": label.GetMaxConn,
"getHealthCheck": label.GetHealthCheck,
@ -235,17 +235,6 @@ func (p Provider) getIPAddress(container dockerData) string {
return p.getIPAddress(parseContainer(containerInspected))
}
if p.UseBindPortIP {
port := getPortV1(container)
for netPort, portBindings := range container.NetworkSettings.Ports {
if string(netPort) == port+"/TCP" || string(netPort) == port+"/UDP" {
for _, p := range portBindings {
return p.HostIP
}
}
}
}
for _, network := range container.NetworkSettings.Networks {
return network.Addr
}
@ -254,6 +243,16 @@ func (p Provider) getIPAddress(container dockerData) string {
return ""
}
// Deprecated: Please use getIPPort instead
func (p *Provider) getDeprecatedIPAddress(container dockerData) string {
ip, _, err := p.getIPPort(container)
if err != nil {
log.Warn(err)
return ""
}
return ip
}
// Escape beginning slash "/", convert all others to dash "-", and convert underscores "_" to dash "-"
func getSubDomain(name string) string {
return strings.Replace(strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1), "_", "-", -1)
@ -322,13 +321,53 @@ func getPort(container dockerData) string {
return ""
}
func (p *Provider) getPortBinding(container dockerData) (*nat.PortBinding, error) {
port := getPort(container)
for netPort, portBindings := range container.NetworkSettings.Ports {
if strings.EqualFold(string(netPort), port+"/TCP") || strings.EqualFold(string(netPort), port+"/UDP") {
for _, p := range portBindings {
return &p, nil
}
}
}
return nil, fmt.Errorf("unable to find the external IP:Port for the container %q", container.Name)
}
func (p *Provider) getIPPort(container dockerData) (string, string, error) {
var ip, port string
if p.UseBindPortIP {
portBinding, err := p.getPortBinding(container)
if err != nil {
return "", "", fmt.Errorf("unable to find a binding for the container %q: ignoring server", container.Name)
}
if portBinding.HostIP == "0.0.0.0" {
return "", "", fmt.Errorf("cannot determine the IP address (got 0.0.0.0) for the container %q: ignoring server", container.Name)
}
ip = portBinding.HostIP
port = portBinding.HostPort
} else {
ip = p.getIPAddress(container)
port = getPort(container)
}
if len(ip) == 0 {
return "", "", fmt.Errorf("unable to find the IP address for the container %q: the server is ignored", container.Name)
}
return ip, port, nil
}
func (p *Provider) getServers(containers []dockerData) map[string]types.Server {
var servers map[string]types.Server
for _, container := range containers {
ip := p.getIPAddress(container)
if len(ip) == 0 {
log.Warnf("Unable to find the IP address for the container %q: the server is ignored.", container.Name)
ip, port, err := p.getIPPort(container)
if err != nil {
log.Warn(err)
continue
}
@ -337,7 +376,6 @@ func (p *Provider) getServers(containers []dockerData) map[string]types.Server {
}
protocol := label.GetStringValue(container.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
port := getPort(container)
serverURL := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(ip, port))

View file

@ -1287,12 +1287,173 @@ func TestDockerGetIPAddress(t *testing.T) {
Network: "webnet",
}
actual := provider.getIPAddress(dData)
actual := provider.getDeprecatedIPAddress(dData)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetIPPort(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
ip, port string
expectsError bool
}{
{
desc: "label traefik.port not set, binding with ip:port should create a route to the bound ip:port",
container: containerJSON(
ports(nat.PortMap{
"80/tcp": []nat.PortBinding{
{
HostIP: "1.2.3.4",
HostPort: "8081",
},
},
}),
withNetwork("testnet", ipv4("10.11.12.13"))),
ip: "1.2.3.4",
port: "8081",
},
{
desc: "label traefik.port set, multiple bindings on different ports, uses the label to select the correct (first) binding",
container: containerJSON(
labels(map[string]string{
label.TraefikPort: "80",
}),
ports(nat.PortMap{
"80/tcp": []nat.PortBinding{
{
HostIP: "1.2.3.4",
HostPort: "8081",
},
},
"443/tcp": []nat.PortBinding{
{
HostIP: "5.6.7.8",
HostPort: "8082",
},
},
}),
withNetwork("testnet", ipv4("10.11.12.13"))),
ip: "1.2.3.4",
port: "8081",
},
{
desc: "label traefik.port set, multiple bindings on different ports, uses the label to select the correct (second) binding",
container: containerJSON(
labels(map[string]string{
label.TraefikPort: "443",
}),
ports(nat.PortMap{
"80/tcp": []nat.PortBinding{
{
HostIP: "1.2.3.4",
HostPort: "8081",
},
},
"443/tcp": []nat.PortBinding{
{
HostIP: "5.6.7.8",
HostPort: "8082",
},
},
}),
withNetwork("testnet", ipv4("10.11.12.13"))),
ip: "5.6.7.8",
port: "8082",
},
{
desc: "label traefik.port set, single binding with ip:port for the label, creates the route",
container: containerJSON(
labels(map[string]string{
label.TraefikPort: "443",
}),
ports(nat.PortMap{
"443/tcp": []nat.PortBinding{
{
HostIP: "5.6.7.8",
HostPort: "8082",
},
},
}),
withNetwork("testnet", ipv4("10.11.12.13"))),
ip: "5.6.7.8",
port: "8082",
},
{
desc: "label traefik.port not set, single binding with port only, server ignored",
container: containerJSON(
ports(nat.PortMap{
"80/tcp": []nat.PortBinding{
{
HostPort: "8082",
},
},
}),
withNetwork("testnet", ipv4("10.11.12.13"))),
expectsError: true,
},
{
desc: "label traefik.port not set, no binding, server ignored",
container: containerJSON(
withNetwork("testnet", ipv4("10.11.12.13"))),
expectsError: true,
},
{
desc: "label traefik.port set, no binding on the corresponding port, server ignored",
container: containerJSON(
labels(map[string]string{
label.TraefikPort: "80",
}),
ports(nat.PortMap{
"443/tcp": []nat.PortBinding{
{
HostIP: "5.6.7.8",
HostPort: "8082",
},
},
}),
withNetwork("testnet", ipv4("10.11.12.13"))),
expectsError: true,
},
{
desc: "label traefik.port set, no binding, server ignored",
container: containerJSON(
labels(map[string]string{
label.TraefikPort: "80",
}),
withNetwork("testnet", ipv4("10.11.12.13"))),
expectsError: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
provider := &Provider{
Network: "webnet",
UseBindPortIP: true,
}
actualIP, actualPort, actualError := provider.getIPPort(dData)
if test.expectsError {
require.Error(t, actualError)
} else {
require.NoError(t, actualError)
}
assert.Equal(t, test.ip, actualIP)
assert.Equal(t, test.port, actualPort)
})
}
}
func TestDockerGetPort(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON

View file

@ -933,7 +933,7 @@ func TestSwarmGetIPAddress(t *testing.T) {
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
actual := provider.getIPAddress(dData)
actual := provider.getDeprecatedIPAddress(dData)
assert.Equal(t, test.expected, actual)
})
}

View file

@ -1,8 +1,10 @@
package docker
import (
"context"
"math"
"strconv"
"strings"
"text/template"
"github.com/BurntSushi/ty/fun"
@ -19,7 +21,7 @@ func (p *Provider) buildConfigurationV1(containersInspected []dockerData) *types
"isBackendLBSwarm": isBackendLBSwarm,
// Backend functions
"getIPAddress": p.getIPAddress,
"getIPAddress": p.getIPAddressV1,
"getPort": getPortV1,
"getWeight": getFuncIntLabelV1(label.TraefikWeight, label.DefaultWeight),
"getProtocol": getFuncStringLabelV1(label.TraefikProtocol, label.DefaultProtocol),
@ -202,3 +204,60 @@ func (p Provider) containerFilterV1(container dockerData) bool {
return true
}
func (p Provider) getIPAddressV1(container dockerData) string {
if value := label.GetStringValue(container.Labels, labelDockerNetwork, p.Network); value != "" {
networkSettings := container.NetworkSettings
if networkSettings.Networks != nil {
network := networkSettings.Networks[value]
if network != nil {
return network.Addr
}
log.Warnf("Could not find network named '%s' for container '%s'! Maybe you're missing the project's prefix in the label? Defaulting to first available network.", value, container.Name)
}
}
if container.NetworkSettings.NetworkMode.IsHost() {
if container.Node != nil {
if container.Node.IPAddress != "" {
return container.Node.IPAddress
}
}
return "127.0.0.1"
}
if container.NetworkSettings.NetworkMode.IsContainer() {
dockerClient, err := p.createClient()
if err != nil {
log.Warnf("Unable to get IP address for container %s, error: %s", container.Name, err)
return ""
}
connectedContainer := container.NetworkSettings.NetworkMode.ConnectedContainer()
containerInspected, err := dockerClient.ContainerInspect(context.Background(), connectedContainer)
if err != nil {
log.Warnf("Unable to get IP address for container %s : Failed to inspect container ID %s, error: %s", container.Name, connectedContainer, err)
return ""
}
return p.getIPAddress(parseContainer(containerInspected))
}
if p.UseBindPortIP {
port := getPortV1(container)
for netPort, portBindings := range container.NetworkSettings.Ports {
if strings.EqualFold(string(netPort), port+"/TCP") || strings.EqualFold(string(netPort), port+"/UDP") {
for _, p := range portBindings {
return p.HostIP
}
}
}
}
for _, network := range container.NetworkSettings.Networks {
return network.Addr
}
log.Warnf("Unable to find the IP address for the container %q.", container.Name)
return ""
}

View file

@ -898,7 +898,7 @@ func TestDockerGetIPAddressV1(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
provider := &Provider{}
actual := provider.getIPAddress(dData)
actual := provider.getDeprecatedIPAddress(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}

View file

@ -667,7 +667,7 @@ func TestSwarmGetIPAddressV1(t *testing.T) {
SwarmMode: true,
}
actual := provider.getIPAddress(dData)
actual := provider.getDeprecatedIPAddress(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}