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`.
|
||||
|
||||
#### `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`
|
||||
|
||||
```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).
|
||||
|
||||
!!! warning
|
||||
|
||||
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() {
|
||||
s.composeStop("simple", "withtcplabels", "withlabels1", "withlabels2", "withonelabelmissing", "powpow")
|
||||
s.composeStop("simple", "withtcplabels", "withlabels1", "withlabels2", "withonelabelmissing", "powpow", "nonRunning")
|
||||
}
|
||||
|
||||
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"))
|
||||
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:
|
||||
traefik.http.Routers.Super.Rule: Host(`my.super.host`)
|
||||
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{
|
||||
Name: "fake",
|
||||
HostConfig: &containertypes.HostConfig{},
|
||||
State: &containertypes.State{},
|
||||
},
|
||||
Config: &containertypes.Config{},
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@ -196,6 +211,19 @@ func (p *DynConfBuilder) keepContainer(ctx context.Context, container dockerData
|
||||
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 {
|
||||
logger.Debug().Msg("Filtering unhealthy or starting container")
|
||||
return false
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"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/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) {
|
||||
type expected struct {
|
||||
ip string
|
||||
|
||||
@ -10,6 +10,7 @@ type dockerData struct {
|
||||
ID string
|
||||
ServiceName string
|
||||
Name string
|
||||
Status string
|
||||
Labels map[string]string // List of labels set to container or service
|
||||
NetworkSettings networkSettings
|
||||
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) {
|
||||
containerList, err := dockerClient.ContainerList(ctx, container.ListOptions{})
|
||||
containerList, err := dockerClient.ContainerList(ctx, container.ListOptions{
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -43,9 +43,9 @@ func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClie
|
||||
return dockerData{}
|
||||
}
|
||||
|
||||
// This condition is here to avoid to have empty IP https://github.com/traefik/traefik/issues/2459
|
||||
// We register only container which are running
|
||||
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running {
|
||||
// Always parse all containers (running and stopped)
|
||||
// The allowNonRunning filtering will be applied later in service configuration
|
||||
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil {
|
||||
return parseContainer(containerInspected)
|
||||
}
|
||||
|
||||
@ -61,6 +61,7 @@ func parseContainer(container containertypes.InspectResponse) dockerData {
|
||||
dData.ID = container.ContainerJSONBase.ID
|
||||
dData.Name = container.ContainerJSONBase.Name
|
||||
dData.ServiceName = dData.Name // Default ServiceName to be the container's Name.
|
||||
dData.Status = container.ContainerJSONBase.State.Status
|
||||
|
||||
if container.ContainerJSONBase.HostConfig != nil {
|
||||
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)
|
||||
// or specific to the provider.
|
||||
type configuration struct {
|
||||
Enable bool
|
||||
Network string
|
||||
LBSwarm bool
|
||||
Enable bool
|
||||
Network string
|
||||
LBSwarm bool
|
||||
AllowNonRunning bool
|
||||
}
|
||||
|
||||
type labelConfiguration struct {
|
||||
Enable bool
|
||||
Docker *specificConfiguration
|
||||
Docker *dockerSpecificConfiguration
|
||||
Swarm *specificConfiguration
|
||||
}
|
||||
|
||||
type dockerSpecificConfiguration struct {
|
||||
Network *string
|
||||
LBSwarm bool
|
||||
AllowNonRunning bool
|
||||
}
|
||||
|
||||
type specificConfiguration struct {
|
||||
Network *string
|
||||
LBSwarm bool
|
||||
@ -43,9 +50,15 @@ func (p *Shared) extractDockerLabels(container dockerData) (configuration, error
|
||||
network = *conf.Docker.Network
|
||||
}
|
||||
|
||||
var allowNonRunning bool
|
||||
if conf.Docker != nil {
|
||||
allowNonRunning = conf.Docker.AllowNonRunning
|
||||
}
|
||||
|
||||
return configuration{
|
||||
Enable: conf.Enable,
|
||||
Network: network,
|
||||
Enable: conf.Enable,
|
||||
Network: network,
|
||||
AllowNonRunning: allowNonRunning,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user