Rename traefik.docker.* labels for Docker Swarm to traefik.swarm.*

This commit is contained in:
Anchal Sharma 2024-12-10 14:18:05 +05:30 committed by GitHub
parent f547f1b22b
commit 514914639a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 152 additions and 93 deletions

View file

@ -160,3 +160,10 @@ the `backendtlspolicies` and `backendtlspolicies/status` rights have to be added
In `v3.2.1`, the `X-Forwarded-Prefix` header is now handled like the other `X-Forwarded-*` headers: Traefik removes it when it's sent from an untrusted source. In `v3.2.1`, the `X-Forwarded-Prefix` header is now handled like the other `X-Forwarded-*` headers: Traefik removes it when it's sent from an untrusted source.
Please refer to the Forwarded headers [documentation](../routing/entrypoints.md#forwarded-headers) for more details. Please refer to the Forwarded headers [documentation](../routing/entrypoints.md#forwarded-headers) for more details.
## v3.2.2
### Swarm Provider
In `v3.2.2`, the `traefik.docker.network` and `traefik.docker.lbswarm` labels have been deprecated,
please use the `traefik.swarm.network` and `traefik.swarm.lbswarm` labels instead.

View file

@ -1,3 +1,3 @@
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=foobar" - "traefik.swarm.network=foobar"
- "traefik.docker.lbswarm=true" - "traefik.swarm.lbswarm=true"

View file

@ -648,10 +648,10 @@ You can tell Traefik to consider (or not) the container by setting `traefik.enab
This option overrides the value of `exposedByDefault`. This option overrides the value of `exposedByDefault`.
#### `traefik.docker.network` #### `traefik.swarm.network`
```yaml ```yaml
- "traefik.docker.network=mynetwork" - "traefik.swarm.network=mynetwork"
``` ```
Overrides the default docker network to use for connections to the container. Overrides the default docker network to use for connections to the container.
@ -659,13 +659,10 @@ Overrides the default docker network to use for connections to the container.
If a container is linked to several networks, be sure to set the proper network name (you can check this with `docker inspect <container_id>`), If a container is linked to several networks, be sure to set the proper network name (you can check this with `docker inspect <container_id>`),
otherwise it will randomly pick one (depending on how docker is returning them). otherwise it will randomly pick one (depending on how docker is returning them).
!!! warning
The Docker Swarm provider still uses the same per-container mechanism as the Docker provider, so therefore the label still uses the `docker` keyword intentionally.
!!! warning !!! warning
When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`. When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`.
#### `traefik.docker.lbswarm` #### `traefik.swarm.lbswarm`
```yaml ```yaml
- "traefik.docker.lbswarm=true" - "traefik.docker.lbswarm=true"
@ -675,6 +672,3 @@ Enables Swarm's inbuilt load balancer (only relevant in Swarm Mode).
If you enable this option, Traefik will use the virtual IP provided by docker swarm instead of the containers IPs. If you enable this option, Traefik will use the virtual IP provided by docker swarm instead of the containers IPs.
Which means that Traefik will not perform any kind of load balancing and will delegate this task to swarm. Which means that Traefik will not perform any kind of load balancing and will delegate this task to swarm.
!!! warning
The Docker Swarm provider still uses the same per-container mechanism as the Docker provider, so therefore the label still uses the `docker` keyword intentionally.

View file

@ -21,10 +21,11 @@ import (
type DynConfBuilder struct { type DynConfBuilder struct {
Shared Shared
apiClient client.APIClient apiClient client.APIClient
swarm bool
} }
func NewDynConfBuilder(configuration Shared, apiClient client.APIClient) *DynConfBuilder { func NewDynConfBuilder(configuration Shared, apiClient client.APIClient, swarm bool) *DynConfBuilder {
return &DynConfBuilder{Shared: configuration, apiClient: apiClient} return &DynConfBuilder{Shared: configuration, apiClient: apiClient, swarm: swarm}
} }
func (p *DynConfBuilder) build(ctx context.Context, containersInspected []dockerData) *dynamic.Configuration { func (p *DynConfBuilder) build(ctx context.Context, containersInspected []dockerData) *dynamic.Configuration {
@ -321,16 +322,16 @@ func (p *DynConfBuilder) getIPAddress(ctx context.Context, container dockerData)
logger := log.Ctx(ctx) logger := log.Ctx(ctx)
netNotFound := false netNotFound := false
if container.ExtraConf.Docker.Network != "" { if container.ExtraConf.Network != "" {
settings := container.NetworkSettings settings := container.NetworkSettings
if settings.Networks != nil { if settings.Networks != nil {
network := settings.Networks[container.ExtraConf.Docker.Network] network := settings.Networks[container.ExtraConf.Network]
if network != nil { if network != nil {
return network.Addr return network.Addr
} }
netNotFound = true netNotFound = true
logger.Warn().Msgf("Could not find network named %q for container %q. Maybe you're missing the project's prefix in the label?", container.ExtraConf.Docker.Network, container.Name) logger.Warn().Msgf("Could not find network named %q for container %q. Maybe you're missing the project's prefix in the label?", container.ExtraConf.Network, container.Name)
} }
} }
@ -360,12 +361,12 @@ func (p *DynConfBuilder) getIPAddress(ctx context.Context, container dockerData)
containerParsed := parseContainer(containerInspected) containerParsed := parseContainer(containerInspected)
extraConf, err := p.extractLabels(containerParsed) extraConf, err := p.extractLabels(containerParsed)
if err != nil { if err != nil {
logger.Warn().Err(err).Msgf("Unable to get IP address for container %s : failed to get extra configuration for container %s", container.Name, containerInspected.Name) logger.Warn().Err(err).Msgf("Unable to get IP address for container %s: failed to get extra configuration for container %s", container.Name, containerInspected.Name)
return "" return ""
} }
if extraConf.Docker.Network == "" { if extraConf.Network == "" {
extraConf.Docker.Network = container.ExtraConf.Docker.Network extraConf.Network = container.ExtraConf.Network
} }
containerParsed.ExtraConf = extraConf containerParsed.ExtraConf = extraConf
@ -396,3 +397,10 @@ func (p *DynConfBuilder) getPortBinding(container dockerData, serverPort string)
return nil, fmt.Errorf("unable to find the external IP:Port for the container %q", container.Name) return nil, fmt.Errorf("unable to find the external IP:Port for the container %q", container.Name)
} }
func (p *DynConfBuilder) extractLabels(container dockerData) (configuration, error) {
if p.swarm {
return p.Shared.extractSwarmLabels(container)
}
return p.Shared.extractDockerLabels(container)
}

View file

@ -405,18 +405,16 @@ func TestDynConfBuilder_DefaultRule(t *testing.T) {
DefaultRule: test.defaultRule, DefaultRule: test.defaultRule,
}, },
} }
require.NoError(t, p.Init())
err := p.Init() builder := NewDynConfBuilder(p.Shared, nil, false)
require.NoError(t, err)
for i := range len(test.containers) { for i := range len(test.containers) {
var err error var err error
test.containers[i].ExtraConf, err = p.extractLabels(test.containers[i]) test.containers[i].ExtraConf, err = builder.extractLabels(test.containers[i])
require.NoError(t, err) require.NoError(t, err)
} }
builder := NewDynConfBuilder(p.Shared, nil)
configuration := builder.build(context.Background(), test.containers) configuration := builder.build(context.Background(), test.containers)
assert.Equal(t, test.expected, configuration) assert.Equal(t, test.expected, configuration)
@ -3662,17 +3660,16 @@ func TestDynConfBuilder_build(t *testing.T) {
} }
p.Constraints = test.constraints p.Constraints = test.constraints
err := p.Init() require.NoError(t, p.Init())
require.NoError(t, err)
builder := NewDynConfBuilder(p.Shared, nil, false)
for i := range len(test.containers) { for i := range len(test.containers) {
var err error var err error
test.containers[i].ExtraConf, err = p.extractLabels(test.containers[i]) test.containers[i].ExtraConf, err = builder.extractLabels(test.containers[i])
require.NoError(t, err) require.NoError(t, err)
} }
builder := NewDynConfBuilder(p.Shared, nil)
configuration := builder.build(context.Background(), test.containers) configuration := builder.build(context.Background(), test.containers)
assert.Equal(t, test.expected, configuration) assert.Equal(t, test.expected, configuration)
@ -3843,7 +3840,7 @@ func TestDynConfBuilder_getIPPort_docker(t *testing.T) {
builder := NewDynConfBuilder(Shared{ builder := NewDynConfBuilder(Shared{
Network: "testnet", Network: "testnet",
UseBindPortIP: true, UseBindPortIP: true,
}, nil) }, nil, false)
actualIP, actualPort, actualError := builder.getIPPort(context.Background(), dData, test.serverPort) actualIP, actualPort, actualError := builder.getIPPort(context.Background(), dData, test.serverPort)
if test.expected.error { if test.expected.error {
@ -3956,12 +3953,12 @@ func TestDynConfBuilder_getIPAddress_docker(t *testing.T) {
dData := parseContainer(test.container) dData := parseContainer(test.container)
dData.ExtraConf.Docker.Network = conf.Network dData.ExtraConf.Network = conf.Network
if len(test.network) > 0 { if len(test.network) > 0 {
dData.ExtraConf.Docker.Network = test.network dData.ExtraConf.Network = test.network
} }
builder := NewDynConfBuilder(conf, nil) builder := NewDynConfBuilder(conf, nil, false)
actual := builder.getIPAddress(context.Background(), dData) actual := builder.getIPAddress(context.Background(), dData)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
@ -3995,7 +3992,7 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
{ {
service: swarmService( service: swarmService(
serviceLabels(map[string]string{ serviceLabels(map[string]string{
"traefik.docker.network": "barnet", "traefik.swarm.network": "barnet",
}), }),
withEndpointSpec(modeVIP), withEndpointSpec(modeVIP),
withEndpoint( withEndpoint(
@ -4019,12 +4016,13 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel() t.Parallel()
p := &SwarmProvider{} var p SwarmProvider
require.NoError(t, p.Init())
dData, err := p.parseService(context.Background(), test.service, test.networks) dData, err := p.parseService(context.Background(), test.service, test.networks)
require.NoError(t, err) require.NoError(t, err)
builder := NewDynConfBuilder(p.Shared, nil) builder := NewDynConfBuilder(p.Shared, nil, false)
actual := builder.getIPAddress(context.Background(), dData) actual := builder.getIPAddress(context.Background(), dData)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })

View file

@ -79,7 +79,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
} }
defer func() { _ = dockerClient.Close() }() defer func() { _ = dockerClient.Close() }()
builder := NewDynConfBuilder(p.Shared, dockerClient) builder := NewDynConfBuilder(p.Shared, dockerClient, false)
serverVersion, err := dockerClient.ServerVersion(ctx) serverVersion, err := dockerClient.ServerVersion(ctx)
if err != nil { if err != nil {
@ -179,7 +179,7 @@ func (p *Provider) listContainers(ctx context.Context, dockerClient client.Conta
continue continue
} }
extraConf, err := p.extractLabels(dData) extraConf, err := p.extractDockerLabels(dData)
if err != nil { if err != nil {
log.Ctx(ctx).Error().Err(err).Msgf("Skip container %s", getServiceName(dData)) log.Ctx(ctx).Error().Err(err).Msgf("Skip container %s", getServiceName(dData))
continue continue

View file

@ -82,7 +82,7 @@ func (p *SwarmProvider) Provide(configurationChan chan<- dynamic.Message, pool *
} }
defer func() { _ = dockerClient.Close() }() defer func() { _ = dockerClient.Close() }()
builder := NewDynConfBuilder(p.Shared, dockerClient) builder := NewDynConfBuilder(p.Shared, dockerClient, true)
serverVersion, err := dockerClient.ServerVersion(ctx) serverVersion, err := dockerClient.ServerVersion(ctx)
if err != nil { if err != nil {
@ -200,7 +200,7 @@ func (p *SwarmProvider) listServices(ctx context.Context, dockerClient client.AP
continue continue
} }
if dData.ExtraConf.Docker.LBSwarm { if dData.ExtraConf.LBSwarm {
if len(dData.NetworkSettings.Networks) > 0 { if len(dData.NetworkSettings.Networks) > 0 {
dockerDataList = append(dockerDataList, dData) dockerDataList = append(dockerDataList, dData)
} }
@ -229,37 +229,38 @@ func (p *SwarmProvider) parseService(ctx context.Context, service swarmtypes.Ser
NetworkSettings: networkSettings{}, NetworkSettings: networkSettings{},
} }
extraConf, err := p.extractLabels(dData) extraConf, err := p.extractSwarmLabels(dData)
if err != nil { if err != nil {
return dockerData{}, err return dockerData{}, err
} }
dData.ExtraConf = extraConf dData.ExtraConf = extraConf
if service.Spec.EndpointSpec != nil { if service.Spec.EndpointSpec == nil {
if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR { return dData, nil
if dData.ExtraConf.Docker.LBSwarm { }
logger.Warn().Msgf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name) if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
if dData.ExtraConf.LBSwarm {
logger.Warn().Msgf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name)
}
} else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP {
dData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range service.Endpoint.VirtualIPs {
networkService := networkMap[virtualIP.NetworkID]
if networkService == nil {
logger.Debug().Msgf("Network not found, id: %s", virtualIP.NetworkID)
continue
} }
} else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP { if len(virtualIP.Addr) == 0 {
dData.NetworkSettings.Networks = make(map[string]*networkData) logger.Debug().Msgf("No virtual IPs found in network %s", virtualIP.NetworkID)
for _, virtualIP := range service.Endpoint.VirtualIPs { continue
networkService := networkMap[virtualIP.NetworkID]
if networkService != nil {
if len(virtualIP.Addr) > 0 {
ip, _, _ := net.ParseCIDR(virtualIP.Addr)
network := &networkData{
Name: networkService.Name,
ID: virtualIP.NetworkID,
Addr: ip.String(),
}
dData.NetworkSettings.Networks[network.Name] = network
} else {
logger.Debug().Msgf("No virtual IPs found in network %s", virtualIP.NetworkID)
}
} else {
logger.Debug().Msgf("Network not found, id: %s", virtualIP.NetworkID)
}
} }
ip, _, _ := net.ParseCIDR(virtualIP.Addr)
network := &networkData{
Name: networkService.Name,
ID: virtualIP.NetworkID,
Addr: ip.String(),
}
dData.NetworkSettings.Networks[network.Name] = network
} }
} }
return dData, nil return dData, nil

View file

@ -65,7 +65,9 @@ func TestListTasks(t *testing.T) {
t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Run(strconv.Itoa(caseID), func(t *testing.T) {
t.Parallel() t.Parallel()
p := SwarmProvider{} var p SwarmProvider
require.NoError(t, p.Init())
dockerData, err := p.parseService(context.Background(), test.service, test.networks) dockerData, err := p.parseService(context.Background(), test.service, test.networks)
require.NoError(t, err) require.NoError(t, err)
@ -100,8 +102,8 @@ func TestSwarmProvider_listServices(t *testing.T) {
swarmService( swarmService(
serviceName("service1"), serviceName("service1"),
serviceLabels(map[string]string{ serviceLabels(map[string]string{
"traefik.docker.network": "barnet", "traefik.swarm.network": "barnet",
"traefik.docker.LBSwarm": "true", "traefik.swarm.LBSwarm": "true",
}), }),
withEndpointSpec(modeVIP), withEndpointSpec(modeVIP),
withEndpoint( withEndpoint(
@ -111,8 +113,8 @@ func TestSwarmProvider_listServices(t *testing.T) {
swarmService( swarmService(
serviceName("service2"), serviceName("service2"),
serviceLabels(map[string]string{ serviceLabels(map[string]string{
"traefik.docker.network": "barnet", "traefik.swarm.network": "barnet",
"traefik.docker.LBSwarm": "true", "traefik.swarm.LBSwarm": "true",
}), }),
withEndpointSpec(modeDNSRR)), withEndpointSpec(modeDNSRR)),
}, },
@ -126,8 +128,8 @@ func TestSwarmProvider_listServices(t *testing.T) {
swarmService( swarmService(
serviceName("service1"), serviceName("service1"),
serviceLabels(map[string]string{ serviceLabels(map[string]string{
"traefik.docker.network": "barnet", "traefik.swarm.network": "barnet",
"traefik.docker.LBSwarm": "true", "traefik.swarm.LBSwarm": "true",
}), }),
withEndpointSpec(modeVIP), withEndpointSpec(modeVIP),
withEndpoint( withEndpoint(
@ -137,8 +139,8 @@ func TestSwarmProvider_listServices(t *testing.T) {
swarmService( swarmService(
serviceName("service2"), serviceName("service2"),
serviceLabels(map[string]string{ serviceLabels(map[string]string{
"traefik.docker.network": "barnet", "traefik.swarm.network": "barnet",
"traefik.docker.LBSwarm": "true", "traefik.swarm.LBSwarm": "true",
}), }),
withEndpointSpec(modeDNSRR)), withEndpointSpec(modeDNSRR)),
}, },
@ -173,7 +175,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
swarmService( swarmService(
serviceName("service1"), serviceName("service1"),
serviceLabels(map[string]string{ serviceLabels(map[string]string{
"traefik.docker.network": "barnet", "traefik.swarm.network": "barnet",
}), }),
withEndpointSpec(modeVIP), withEndpointSpec(modeVIP),
withEndpoint( withEndpoint(
@ -183,7 +185,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
swarmService( swarmService(
serviceName("service2"), serviceName("service2"),
serviceLabels(map[string]string{ serviceLabels(map[string]string{
"traefik.docker.network": "barnet", "traefik.swarm.network": "barnet",
}), }),
withEndpointSpec(modeDNSRR)), withEndpointSpec(modeDNSRR)),
}, },
@ -233,7 +235,8 @@ func TestSwarmProvider_listServices(t *testing.T) {
dockerClient := &fakeServicesClient{services: test.services, tasks: test.tasks, dockerVersion: test.dockerVersion, networks: test.networks} dockerClient := &fakeServicesClient{services: test.services, tasks: test.tasks, dockerVersion: test.dockerVersion, networks: test.networks}
p := SwarmProvider{} var p SwarmProvider
require.NoError(t, p.Init())
serviceDockerData, err := p.listServices(context.Background(), dockerClient) serviceDockerData, err := p.listServices(context.Background(), dockerClient)
assert.NoError(t, err) assert.NoError(t, err)
@ -351,7 +354,8 @@ func TestSwarmProvider_parseService_task(t *testing.T) {
t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Run(strconv.Itoa(caseID), func(t *testing.T) {
t.Parallel() t.Parallel()
p := SwarmProvider{} var p SwarmProvider
require.NoError(t, p.Init())
dData, err := p.parseService(context.Background(), test.service, test.networks) dData, err := p.parseService(context.Background(), test.service, test.networks)
require.NoError(t, err) require.NoError(t, err)

View file

@ -1,8 +1,10 @@
package docker package docker
import ( import (
"errors"
"fmt" "fmt"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/label" "github.com/traefik/traefik/v3/pkg/config/label"
) )
@ -11,29 +13,73 @@ const (
labelDockerComposeService = "com.docker.compose.service" labelDockerComposeService = "com.docker.compose.service"
) )
// configuration Contains information from the labels that are globals (not related to the dynamic configuration) // configuration contains information from the labels that are globals (not related to the dynamic configuration)
// or specific to the provider. // or specific to the provider.
type configuration struct { type configuration struct {
Enable bool Enable bool
Docker specificConfiguration
}
type specificConfiguration struct {
Network string Network string
LBSwarm bool LBSwarm bool
} }
func (p *Shared) extractLabels(container dockerData) (configuration, error) { type labelConfiguration struct {
conf := configuration{ Enable bool
Enable: p.ExposedByDefault, Docker *specificConfiguration
Docker: specificConfiguration{ Swarm *specificConfiguration
Network: p.Network, }
},
type specificConfiguration struct {
Network *string
LBSwarm bool
}
func (p *Shared) extractDockerLabels(container dockerData) (configuration, error) {
conf := labelConfiguration{Enable: p.ExposedByDefault}
if err := label.Decode(container.Labels, &conf, "traefik.docker.", "traefik.enable"); err != nil {
return configuration{}, fmt.Errorf("decoding Docker labels: %w", err)
} }
err := label.Decode(container.Labels, &conf, "traefik.docker.", "traefik.enable") network := p.Network
if err != nil { if conf.Docker != nil && conf.Docker.Network != nil {
return configuration{}, err network = *conf.Docker.Network
}
return configuration{
Enable: conf.Enable,
Network: network,
}, nil
}
func (p *Shared) extractSwarmLabels(container dockerData) (configuration, error) {
labelConf := labelConfiguration{Enable: p.ExposedByDefault}
if err := label.Decode(container.Labels, &labelConf, "traefik.enable", "traefik.docker.", "traefik.swarm."); err != nil {
return configuration{}, fmt.Errorf("decoding Swarm labels: %w", err)
}
if labelConf.Docker != nil && labelConf.Swarm != nil {
return configuration{}, errors.New("both Docker and Swarm labels are defined")
}
conf := configuration{
Enable: labelConf.Enable,
Network: p.Network,
}
if labelConf.Docker != nil {
log.Warn().Msg("Labels traefik.docker.* for Swarm provider are deprecated. Please use traefik.swarm.* labels instead")
conf.LBSwarm = labelConf.Docker.LBSwarm
if labelConf.Docker.Network != nil {
conf.Network = *labelConf.Docker.Network
}
}
if labelConf.Swarm != nil {
conf.LBSwarm = labelConf.Swarm.LBSwarm
if labelConf.Swarm.Network != nil {
conf.Network = *labelConf.Swarm.Network
}
} }
return conf, nil return conf, nil

View file

@ -98,7 +98,8 @@ func Test_getPort_swarm(t *testing.T) {
t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel() t.Parallel()
p := SwarmProvider{} var p SwarmProvider
require.NoError(t, p.Init())
dData, err := p.parseService(context.Background(), test.service, test.networks) dData, err := p.parseService(context.Background(), test.service, test.networks)
require.NoError(t, err) require.NoError(t, err)