mirror of
https://github.com/traefik/traefik.git
synced 2025-10-27 22:41:36 +01:00
Allow discovering non-running Docker containers
This commit is contained in:
parent
5c489c05fc
commit
10be359327
@ -688,6 +688,27 @@ 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.allownonrunning`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- "traefik.docker.allownonrunning=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, Traefik only considers containers in "running" state.
|
||||||
|
This option controls whether containers that are not in "running" state (e.g., stopped, paused, exited) should still be visible to Traefik for service discovery.
|
||||||
|
|
||||||
|
When this label is set to true, Traefik will:
|
||||||
|
|
||||||
|
- Keep the router and service configuration even when the container is not running
|
||||||
|
- Create services with empty backend server lists
|
||||||
|
- Return 503 Service Unavailable for requests to stopped containers (instead of 404 Not Found)
|
||||||
|
- Execute the full middleware chain, allowing middlewares to intercept requests
|
||||||
|
|
||||||
|
!!! warning "Configuration Collision"
|
||||||
|
|
||||||
|
As the `traefik.docker.allownonrunning` enables the discovery of all containers exposing this option disregarding their state,
|
||||||
|
if multiple stopped containers expose the same router but their configurations diverge, then the routers will be dropped.
|
||||||
|
|
||||||
#### `traefik.docker.network`
|
#### `traefik.docker.network`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@ -700,4 +721,5 @@ If a container is linked to several networks, be sure to set the proper network
|
|||||||
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
|
!!! 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`.
|
||||||
|
|||||||
@ -32,7 +32,7 @@ func (s *DockerSuite) TearDownSuite() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TearDownTest() {
|
func (s *DockerSuite) TearDownTest() {
|
||||||
s.composeStop("simple", "withtcplabels", "withlabels1", "withlabels2", "withonelabelmissing", "powpow")
|
s.composeStop("simple", "withtcplabels", "withlabels1", "withlabels2", "withonelabelmissing", "powpow", "nonRunning")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestSimpleConfiguration() {
|
func (s *DockerSuite) TestSimpleConfiguration() {
|
||||||
@ -222,3 +222,59 @@ func (s *DockerSuite) TestRestartDockerContainers() {
|
|||||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow"))
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestDockerAllowNonRunning() {
|
||||||
|
tempObjects := struct {
|
||||||
|
DockerHost string
|
||||||
|
DefaultRule string
|
||||||
|
}{
|
||||||
|
DockerHost: s.getDockerHost(),
|
||||||
|
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
|
||||||
|
}
|
||||||
|
|
||||||
|
file := s.adaptFile("fixtures/docker/simple.toml", tempObjects)
|
||||||
|
|
||||||
|
s.composeUp("nonRunning")
|
||||||
|
|
||||||
|
// Start traefik
|
||||||
|
s.traefikCmd(withConfigFile(file))
|
||||||
|
|
||||||
|
// Verify the container is working when running
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
req.Host = "non.running.host"
|
||||||
|
|
||||||
|
resp, err := try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
assert.Contains(s.T(), string(body), "Hostname:")
|
||||||
|
|
||||||
|
// Verify the router exists in Traefik configuration
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1*time.Second, try.BodyContains("NonRunning"))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
// Stop the container
|
||||||
|
s.composeStop("nonRunning")
|
||||||
|
|
||||||
|
// Wait a bit for container stop to be detected
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
// Verify the router still exists in configuration even though container is stopped
|
||||||
|
// This is the key test - the router should persist due to allowNonRunning=true
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers", 10*time.Second, try.BodyContains("NonRunning"))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
// Verify the service still exists in configuration
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1*time.Second, try.BodyContains("nonRunning"))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
// HTTP requests should fail (502 Bad Gateway) since container is stopped but router exists
|
||||||
|
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
req.Host = "non.running.host"
|
||||||
|
|
||||||
|
err = try.Request(req, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
}
|
||||||
|
|||||||
@ -35,3 +35,9 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
traefik.http.Routers.Super.Rule: Host(`my.super.host`)
|
traefik.http.Routers.Super.Rule: Host(`my.super.host`)
|
||||||
traefik.http.Services.powpow.LoadBalancer.server.Port: 2375
|
traefik.http.Services.powpow.LoadBalancer.server.Port: 2375
|
||||||
|
|
||||||
|
nonRunning:
|
||||||
|
image: traefik/whoami
|
||||||
|
labels:
|
||||||
|
traefik.http.Routers.NonRunning.Rule: Host(`non.running.host`)
|
||||||
|
traefik.docker.allownonrunning: "true"
|
||||||
|
|||||||
@ -12,6 +12,7 @@ func containerJSON(ops ...func(*containertypes.InspectResponse)) containertypes.
|
|||||||
ContainerJSONBase: &containertypes.ContainerJSONBase{
|
ContainerJSONBase: &containertypes.ContainerJSONBase{
|
||||||
Name: "fake",
|
Name: "fake",
|
||||||
HostConfig: &containertypes.HostConfig{},
|
HostConfig: &containertypes.HostConfig{},
|
||||||
|
State: &containertypes.State{},
|
||||||
},
|
},
|
||||||
Config: &containertypes.Config{},
|
Config: &containertypes.Config{},
|
||||||
NetworkSettings: &containertypes.NetworkSettings{
|
NetworkSettings: &containertypes.NetworkSettings{
|
||||||
|
|||||||
@ -114,6 +114,11 @@ func (p *DynConfBuilder) buildTCPServiceConfiguration(ctx context.Context, conta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep an empty server load-balancer for non-running containers.
|
||||||
|
if container.Status != "" && container.Status != containertypes.StateRunning {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Keep an empty server load-balancer for unhealthy containers.
|
||||||
if container.Health != "" && container.Health != containertypes.Healthy {
|
if container.Health != "" && container.Health != containertypes.Healthy {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -138,6 +143,11 @@ func (p *DynConfBuilder) buildUDPServiceConfiguration(ctx context.Context, conta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep an empty server load-balancer for non-running containers.
|
||||||
|
if container.Status != "" && container.Status != containertypes.StateRunning {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Keep an empty server load-balancer for unhealthy containers.
|
||||||
if container.Health != "" && container.Health != containertypes.Healthy {
|
if container.Health != "" && container.Health != containertypes.Healthy {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -164,6 +174,11 @@ func (p *DynConfBuilder) buildServiceConfiguration(ctx context.Context, containe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep an empty server load-balancer for non-running containers.
|
||||||
|
if container.Status != "" && container.Status != containertypes.StateRunning {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Keep an empty server load-balancer for unhealthy containers.
|
||||||
if container.Health != "" && container.Health != containertypes.Healthy {
|
if container.Health != "" && container.Health != containertypes.Healthy {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -196,6 +211,19 @@ func (p *DynConfBuilder) keepContainer(ctx context.Context, container dockerData
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllowNonRunning has precedence over AllowEmptyServices.
|
||||||
|
// If AllowNonRunning is true, we don't care about the container health/status,
|
||||||
|
// and we need to quit before checking it.
|
||||||
|
// Only configurable with the Docker provider.
|
||||||
|
if container.ExtraConf.AllowNonRunning {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if container.Status != "" && container.Status != containertypes.StateRunning {
|
||||||
|
logger.Debug().Msg("Filtering non running container")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if !p.AllowEmptyServices && container.Health != "" && container.Health != containertypes.Healthy {
|
if !p.AllowEmptyServices && container.Health != "" && container.Health != containertypes.Healthy {
|
||||||
logger.Debug().Msg("Filtering unhealthy or starting container")
|
logger.Debug().Msg("Filtering unhealthy or starting container")
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
ptypes "github.com/traefik/paerser/types"
|
ptypes "github.com/traefik/paerser/types"
|
||||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/provider"
|
||||||
"github.com/traefik/traefik/v3/pkg/tls"
|
"github.com/traefik/traefik/v3/pkg/tls"
|
||||||
"github.com/traefik/traefik/v3/pkg/types"
|
"github.com/traefik/traefik/v3/pkg/types"
|
||||||
)
|
)
|
||||||
@ -3935,6 +3936,464 @@ func TestDynConfBuilder_build(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDynConfBuilder_build_allowNonRunning(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
containers []dockerData
|
||||||
|
expected *dynamic.Configuration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "exited container with allowNonRunning=true should create router and service without servers",
|
||||||
|
containers: []dockerData{
|
||||||
|
{
|
||||||
|
ServiceName: "Test",
|
||||||
|
Name: "Test",
|
||||||
|
Status: "exited",
|
||||||
|
Health: "",
|
||||||
|
ExtraConf: configuration{
|
||||||
|
Enable: true,
|
||||||
|
AllowNonRunning: true,
|
||||||
|
},
|
||||||
|
NetworkSettings: networkSettings{
|
||||||
|
NetworkMode: "bridge",
|
||||||
|
Ports: nat.PortMap{
|
||||||
|
"80/tcp": []nat.PortBinding{},
|
||||||
|
},
|
||||||
|
Networks: map[string]*networkData{
|
||||||
|
"bridge": {
|
||||||
|
Name: "bridge",
|
||||||
|
Addr: "127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"Test": {
|
||||||
|
Service: "Test",
|
||||||
|
Rule: "Host(`Test`)",
|
||||||
|
DefaultRule: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"Test": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
PassHostHeader: pointer(true),
|
||||||
|
Strategy: "wrr",
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Stores: map[string]tls.Store{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "exited container with allowNonRunning=false should not create anything",
|
||||||
|
containers: []dockerData{
|
||||||
|
{
|
||||||
|
ServiceName: "Test",
|
||||||
|
Name: "Test",
|
||||||
|
Status: "exited",
|
||||||
|
Health: "",
|
||||||
|
ExtraConf: configuration{
|
||||||
|
Enable: true,
|
||||||
|
AllowNonRunning: false,
|
||||||
|
},
|
||||||
|
NetworkSettings: networkSettings{
|
||||||
|
NetworkMode: "bridge",
|
||||||
|
Ports: nat.PortMap{
|
||||||
|
"80/tcp": []nat.PortBinding{},
|
||||||
|
},
|
||||||
|
Networks: map[string]*networkData{
|
||||||
|
"bridge": {
|
||||||
|
Name: "bridge",
|
||||||
|
Addr: "127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Stores: map[string]tls.Store{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "running container with allowNonRunning=true should work normally with servers",
|
||||||
|
containers: []dockerData{
|
||||||
|
{
|
||||||
|
ServiceName: "Test",
|
||||||
|
Name: "Test",
|
||||||
|
Status: "running",
|
||||||
|
Health: "",
|
||||||
|
ExtraConf: configuration{
|
||||||
|
Enable: true,
|
||||||
|
AllowNonRunning: true,
|
||||||
|
},
|
||||||
|
NetworkSettings: networkSettings{
|
||||||
|
NetworkMode: "bridge",
|
||||||
|
Ports: nat.PortMap{
|
||||||
|
"80/tcp": []nat.PortBinding{},
|
||||||
|
},
|
||||||
|
Networks: map[string]*networkData{
|
||||||
|
"bridge": {
|
||||||
|
Name: "bridge",
|
||||||
|
Addr: "127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"Test": {
|
||||||
|
Service: "Test",
|
||||||
|
Rule: "Host(`Test`)",
|
||||||
|
DefaultRule: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"Test": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://127.0.0.1:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PassHostHeader: pointer(true),
|
||||||
|
Strategy: "wrr",
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Stores: map[string]tls.Store{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "created container with allowNonRunning=true should create router and service without servers)",
|
||||||
|
containers: []dockerData{
|
||||||
|
{
|
||||||
|
ServiceName: "Test",
|
||||||
|
Name: "Test",
|
||||||
|
Status: "created",
|
||||||
|
Health: "",
|
||||||
|
ExtraConf: configuration{
|
||||||
|
Enable: true,
|
||||||
|
AllowNonRunning: true,
|
||||||
|
},
|
||||||
|
NetworkSettings: networkSettings{
|
||||||
|
NetworkMode: "bridge",
|
||||||
|
Ports: nat.PortMap{
|
||||||
|
"80/tcp": []nat.PortBinding{},
|
||||||
|
},
|
||||||
|
Networks: map[string]*networkData{
|
||||||
|
"bridge": {
|
||||||
|
Name: "bridge",
|
||||||
|
Addr: "127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"Test": {
|
||||||
|
Service: "Test",
|
||||||
|
Rule: "Host(`Test`)",
|
||||||
|
DefaultRule: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"Test": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
PassHostHeader: pointer(true),
|
||||||
|
Strategy: "wrr",
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Stores: map[string]tls.Store{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "dead container with allowNonRunning=true should create router and service without servers)",
|
||||||
|
containers: []dockerData{
|
||||||
|
{
|
||||||
|
ServiceName: "Test",
|
||||||
|
Name: "Test",
|
||||||
|
Status: "dead",
|
||||||
|
Health: "",
|
||||||
|
ExtraConf: configuration{
|
||||||
|
Enable: true,
|
||||||
|
AllowNonRunning: true,
|
||||||
|
},
|
||||||
|
NetworkSettings: networkSettings{
|
||||||
|
NetworkMode: "bridge",
|
||||||
|
Ports: nat.PortMap{
|
||||||
|
"80/tcp": []nat.PortBinding{},
|
||||||
|
},
|
||||||
|
Networks: map[string]*networkData{
|
||||||
|
"bridge": {
|
||||||
|
Name: "bridge",
|
||||||
|
Addr: "127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"Test": {
|
||||||
|
Service: "Test",
|
||||||
|
Rule: "Host(`Test`)",
|
||||||
|
DefaultRule: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"Test": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
PassHostHeader: pointer(true),
|
||||||
|
Strategy: "wrr",
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Stores: map[string]tls.Store{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "exited container with TCP configuration and allowNonRunning=true should create TCP service without servers",
|
||||||
|
containers: []dockerData{
|
||||||
|
{
|
||||||
|
ServiceName: "Test",
|
||||||
|
Name: "Test",
|
||||||
|
Status: "exited",
|
||||||
|
Health: "",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.tcp.routers.Test.rule": "HostSNI(`test.localhost`)",
|
||||||
|
},
|
||||||
|
ExtraConf: configuration{
|
||||||
|
Enable: true,
|
||||||
|
AllowNonRunning: true,
|
||||||
|
},
|
||||||
|
NetworkSettings: networkSettings{
|
||||||
|
NetworkMode: "bridge",
|
||||||
|
Ports: nat.PortMap{
|
||||||
|
"80/tcp": []nat.PortBinding{},
|
||||||
|
},
|
||||||
|
Networks: map[string]*networkData{
|
||||||
|
"bridge": {
|
||||||
|
Name: "bridge",
|
||||||
|
Addr: "127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{
|
||||||
|
"Test": {
|
||||||
|
Service: "Test",
|
||||||
|
Rule: "HostSNI(`test.localhost`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{
|
||||||
|
"Test": {
|
||||||
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Stores: map[string]tls.Store{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "exited container with UDP configuration and allowNonRunning=true should create UDP service without servers",
|
||||||
|
containers: []dockerData{
|
||||||
|
{
|
||||||
|
ServiceName: "Test",
|
||||||
|
Name: "Test",
|
||||||
|
Status: "exited",
|
||||||
|
Health: "",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.udp.routers.Test.entrypoints": "udp",
|
||||||
|
},
|
||||||
|
ExtraConf: configuration{
|
||||||
|
Enable: true,
|
||||||
|
AllowNonRunning: true,
|
||||||
|
},
|
||||||
|
NetworkSettings: networkSettings{
|
||||||
|
NetworkMode: "bridge",
|
||||||
|
Ports: nat.PortMap{
|
||||||
|
"80/udp": []nat.PortBinding{},
|
||||||
|
},
|
||||||
|
Networks: map[string]*networkData{
|
||||||
|
"bridge": {
|
||||||
|
Name: "bridge",
|
||||||
|
Addr: "127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{
|
||||||
|
"Test": {
|
||||||
|
Service: "Test",
|
||||||
|
EntryPoints: []string{"udp"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.UDPService{
|
||||||
|
"Test": {
|
||||||
|
LoadBalancer: &dynamic.UDPServersLoadBalancer{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Stores: map[string]tls.Store{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(DefaultTemplateRule, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
p := Shared{
|
||||||
|
ExposedByDefault: true,
|
||||||
|
DefaultRule: DefaultTemplateRule,
|
||||||
|
defaultRuleTpl: defaultRuleTpl,
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := NewDynConfBuilder(p, nil, false)
|
||||||
|
configuration := builder.build(t.Context(), test.containers)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, configuration)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDynConfBuilder_getIPPort_docker(t *testing.T) {
|
func TestDynConfBuilder_getIPPort_docker(t *testing.T) {
|
||||||
type expected struct {
|
type expected struct {
|
||||||
ip string
|
ip string
|
||||||
|
|||||||
@ -10,6 +10,7 @@ type dockerData struct {
|
|||||||
ID string
|
ID string
|
||||||
ServiceName string
|
ServiceName string
|
||||||
Name string
|
Name string
|
||||||
|
Status string
|
||||||
Labels map[string]string // List of labels set to container or service
|
Labels map[string]string // List of labels set to container or service
|
||||||
NetworkSettings networkSettings
|
NetworkSettings networkSettings
|
||||||
Health string
|
Health string
|
||||||
|
|||||||
@ -165,7 +165,9 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
|
func (p *Provider) listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
|
||||||
containerList, err := dockerClient.ContainerList(ctx, container.ListOptions{})
|
containerList, err := dockerClient.ContainerList(ctx, container.ListOptions{
|
||||||
|
All: true,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,9 +43,9 @@ func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClie
|
|||||||
return dockerData{}
|
return dockerData{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This condition is here to avoid to have empty IP https://github.com/traefik/traefik/issues/2459
|
// Always parse all containers (running and stopped)
|
||||||
// We register only container which are running
|
// The allowNonRunning filtering will be applied later in service configuration
|
||||||
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running {
|
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil {
|
||||||
return parseContainer(containerInspected)
|
return parseContainer(containerInspected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +61,7 @@ func parseContainer(container containertypes.InspectResponse) dockerData {
|
|||||||
dData.ID = container.ContainerJSONBase.ID
|
dData.ID = container.ContainerJSONBase.ID
|
||||||
dData.Name = container.ContainerJSONBase.Name
|
dData.Name = container.ContainerJSONBase.Name
|
||||||
dData.ServiceName = dData.Name // Default ServiceName to be the container's Name.
|
dData.ServiceName = dData.Name // Default ServiceName to be the container's Name.
|
||||||
|
dData.Status = container.ContainerJSONBase.State.Status
|
||||||
|
|
||||||
if container.ContainerJSONBase.HostConfig != nil {
|
if container.ContainerJSONBase.HostConfig != nil {
|
||||||
dData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
|
dData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
|
||||||
|
|||||||
@ -16,17 +16,24 @@ const (
|
|||||||
// 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
|
||||||
Network string
|
Network string
|
||||||
LBSwarm bool
|
LBSwarm bool
|
||||||
|
AllowNonRunning bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type labelConfiguration struct {
|
type labelConfiguration struct {
|
||||||
Enable bool
|
Enable bool
|
||||||
Docker *specificConfiguration
|
Docker *dockerSpecificConfiguration
|
||||||
Swarm *specificConfiguration
|
Swarm *specificConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dockerSpecificConfiguration struct {
|
||||||
|
Network *string
|
||||||
|
LBSwarm bool
|
||||||
|
AllowNonRunning bool
|
||||||
|
}
|
||||||
|
|
||||||
type specificConfiguration struct {
|
type specificConfiguration struct {
|
||||||
Network *string
|
Network *string
|
||||||
LBSwarm bool
|
LBSwarm bool
|
||||||
@ -43,9 +50,15 @@ func (p *Shared) extractDockerLabels(container dockerData) (configuration, error
|
|||||||
network = *conf.Docker.Network
|
network = *conf.Docker.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var allowNonRunning bool
|
||||||
|
if conf.Docker != nil {
|
||||||
|
allowNonRunning = conf.Docker.AllowNonRunning
|
||||||
|
}
|
||||||
|
|
||||||
return configuration{
|
return configuration{
|
||||||
Enable: conf.Enable,
|
Enable: conf.Enable,
|
||||||
Network: network,
|
Network: network,
|
||||||
|
AllowNonRunning: allowNonRunning,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user