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.
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.docker.network=foobar"
- "traefik.docker.lbswarm=true"
- "traefik.swarm.network=foobar"
- "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`.
#### `traefik.docker.network`
#### `traefik.swarm.network`
```yaml
- "traefik.docker.network=mynetwork"
- "traefik.swarm.network=mynetwork"
```
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>`),
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
When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`.
#### `traefik.docker.lbswarm`
#### `traefik.swarm.lbswarm`
```yaml
- "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.
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 {
Shared
apiClient client.APIClient
swarm bool
}
func NewDynConfBuilder(configuration Shared, apiClient client.APIClient) *DynConfBuilder {
return &DynConfBuilder{Shared: configuration, apiClient: apiClient}
func NewDynConfBuilder(configuration Shared, apiClient client.APIClient, swarm bool) *DynConfBuilder {
return &DynConfBuilder{Shared: configuration, apiClient: apiClient, swarm: swarm}
}
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)
netNotFound := false
if container.ExtraConf.Docker.Network != "" {
if container.ExtraConf.Network != "" {
settings := container.NetworkSettings
if settings.Networks != nil {
network := settings.Networks[container.ExtraConf.Docker.Network]
network := settings.Networks[container.ExtraConf.Network]
if network != nil {
return network.Addr
}
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)
extraConf, err := p.extractLabels(containerParsed)
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 ""
}
if extraConf.Docker.Network == "" {
extraConf.Docker.Network = container.ExtraConf.Docker.Network
if extraConf.Network == "" {
extraConf.Network = container.ExtraConf.Network
}
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)
}
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,
},
}
require.NoError(t, p.Init())
err := p.Init()
require.NoError(t, err)
builder := NewDynConfBuilder(p.Shared, nil, false)
for i := range len(test.containers) {
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)
}
builder := NewDynConfBuilder(p.Shared, nil)
configuration := builder.build(context.Background(), test.containers)
assert.Equal(t, test.expected, configuration)
@ -3662,17 +3660,16 @@ func TestDynConfBuilder_build(t *testing.T) {
}
p.Constraints = test.constraints
err := p.Init()
require.NoError(t, err)
require.NoError(t, p.Init())
builder := NewDynConfBuilder(p.Shared, nil, false)
for i := range len(test.containers) {
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)
}
builder := NewDynConfBuilder(p.Shared, nil)
configuration := builder.build(context.Background(), test.containers)
assert.Equal(t, test.expected, configuration)
@ -3843,7 +3840,7 @@ func TestDynConfBuilder_getIPPort_docker(t *testing.T) {
builder := NewDynConfBuilder(Shared{
Network: "testnet",
UseBindPortIP: true,
}, nil)
}, nil, false)
actualIP, actualPort, actualError := builder.getIPPort(context.Background(), dData, test.serverPort)
if test.expected.error {
@ -3956,12 +3953,12 @@ func TestDynConfBuilder_getIPAddress_docker(t *testing.T) {
dData := parseContainer(test.container)
dData.ExtraConf.Docker.Network = conf.Network
dData.ExtraConf.Network = conf.Network
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)
assert.Equal(t, test.expected, actual)
@ -3995,7 +3992,7 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
{
service: swarmService(
serviceLabels(map[string]string{
"traefik.docker.network": "barnet",
"traefik.swarm.network": "barnet",
}),
withEndpointSpec(modeVIP),
withEndpoint(
@ -4019,12 +4016,13 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
p := &SwarmProvider{}
var p SwarmProvider
require.NoError(t, p.Init())
dData, err := p.parseService(context.Background(), test.service, test.networks)
require.NoError(t, err)
builder := NewDynConfBuilder(p.Shared, nil)
builder := NewDynConfBuilder(p.Shared, nil, false)
actual := builder.getIPAddress(context.Background(), dData)
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() }()
builder := NewDynConfBuilder(p.Shared, dockerClient)
builder := NewDynConfBuilder(p.Shared, dockerClient, false)
serverVersion, err := dockerClient.ServerVersion(ctx)
if err != nil {
@ -179,7 +179,7 @@ func (p *Provider) listContainers(ctx context.Context, dockerClient client.Conta
continue
}
extraConf, err := p.extractLabels(dData)
extraConf, err := p.extractDockerLabels(dData)
if err != nil {
log.Ctx(ctx).Error().Err(err).Msgf("Skip container %s", getServiceName(dData))
continue

View File

@ -82,7 +82,7 @@ func (p *SwarmProvider) Provide(configurationChan chan<- dynamic.Message, pool *
}
defer func() { _ = dockerClient.Close() }()
builder := NewDynConfBuilder(p.Shared, dockerClient)
builder := NewDynConfBuilder(p.Shared, dockerClient, true)
serverVersion, err := dockerClient.ServerVersion(ctx)
if err != nil {
@ -200,7 +200,7 @@ func (p *SwarmProvider) listServices(ctx context.Context, dockerClient client.AP
continue
}
if dData.ExtraConf.Docker.LBSwarm {
if dData.ExtraConf.LBSwarm {
if len(dData.NetworkSettings.Networks) > 0 {
dockerDataList = append(dockerDataList, dData)
}
@ -229,37 +229,38 @@ func (p *SwarmProvider) parseService(ctx context.Context, service swarmtypes.Ser
NetworkSettings: networkSettings{},
}
extraConf, err := p.extractLabels(dData)
extraConf, err := p.extractSwarmLabels(dData)
if err != nil {
return dockerData{}, err
}
dData.ExtraConf = extraConf
if service.Spec.EndpointSpec != nil {
if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
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 == nil {
return dData, nil
}
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 {
dData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range service.Endpoint.VirtualIPs {
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)
}
if len(virtualIP.Addr) == 0 {
logger.Debug().Msgf("No virtual IPs found in network %s", virtualIP.NetworkID)
continue
}
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

View File

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

View File

@ -1,8 +1,10 @@
package docker
import (
"errors"
"fmt"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/label"
)
@ -11,29 +13,73 @@ const (
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.
type configuration struct {
Enable bool
Docker specificConfiguration
}
type specificConfiguration struct {
Enable bool
Network string
LBSwarm bool
}
func (p *Shared) extractLabels(container dockerData) (configuration, error) {
conf := configuration{
Enable: p.ExposedByDefault,
Docker: specificConfiguration{
Network: p.Network,
},
type labelConfiguration struct {
Enable bool
Docker *specificConfiguration
Swarm *specificConfiguration
}
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")
if err != nil {
return configuration{}, err
network := p.Network
if conf.Docker != nil && conf.Docker.Network != nil {
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

View File

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