Routing between two traefiks (splited by constraints)

I have two Traefiks (T1 & T2), each one with their constraints. So, basically two environments, but on the same Docker Swarm infrastructure.

I want to create a entry on the first one (T1) to route to a service on the second one. I think it could be done by using file provider, such the following example:

  services:
    route-to-service-on-t1:
      loadbalancer:
        servers:
          - url: http://my-service-name-here/
        passhostheader: true

I didn't test it, by when connect on the T1 I was able to execute curl http://my-service-name-here/. But I'd like to do it by using the labels.

I know that services.[service-name].loadbalancer.servers[x].url is not available on Docker Swarm (even when not using the IP address, but the service name). Is there a way to configure on T1 a router to a service linked to T2? I cant configure directly on the service, because it does not have the T1 constraint, so the service cannot "see" T1.

Sure, loadbalancer.servers.url is available in Docker Swarm. It is just not available in Docker dynamic config via labels, you need to use providers.file with a dynamic config file.

Shouldn’t it work with Configuration Discovery and a constraint for both services? (Doc)

Here is a simple example, that doesn't fully utilize TLS, you would need to setup a wildcard cert (with LetsEncrypt). And the rule in traefik-dynamic.yml could be improved, using something like *.lab.example.com.

traefik-ext listens externally on 80 and 443, creates TLS certs. It recognizes services with traefik.ext=true label. Every request not matching the discovered "ext" services will be forwarded to traefik-int via the traefik-dynamic.yml configuration file.

traefik-int only listens in Docker network internally on port 80, no TLS, discovers services using traefik.int=true label.

docker-compose.yml:

version: '3.9'

networks:
  proxy-ext:
    name: proxy-ext
    driver: overlay
  proxy-int:
    name: proxy-int
    driver: overlay

volumes:
  traefik-certificates:

configs:
  traefik-dynamic.yml:
    file: traefik-dynamic.yml


services:
  traefik-ext:
    image: traefik:v2.10
    hostname: '{{.Node.Hostname}}'
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
    networks:
      - proxy-ext
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik-certificates:/certificates
    configs:
      - traefik-dynamic.yml
    command:
      - --providers.docker=true
      - --providers.docker.swarmMode=true
      - --providers.docker.exposedByDefault=false
      - --providers.docker.network=proxy-ext
      - --providers.docker.constraints=Label(`traefik.ext`,`true`)
      - --providers.file.filename=/traefik-dynamic.yml
      - --providers.file.watch=true
      - --entryPoints.web.address=:80
      - --entryPoints.web.http.redirections.entryPoint.to=websecure
      - --entryPoints.web.http.redirections.entryPoint.scheme=https
      - --entryPoints.websecure.address=:443
      - --entryPoints.websecure.http.tls=true
      - --entryPoints.websecure.http.tls.certResolver=myresolver
      - --api.debug=true
      - --api.dashboard=true
      - --log.level=INFO
      - --accesslog=true
      - --certificatesResolvers.myresolver.acme.email=mail@example.com
      - --certificatesResolvers.myresolver.acme.storage=/certificates/acme.json
      - --certificatesresolvers.myresolver.acme.tlschallenge=true
    deploy:
      mode: global
      placement:
        constraints:
          - node.role==manager
      labels:
        - traefik.ext=true
        - traefik.enable=true
        - traefik.http.routers.traefik-ext.entrypoints=websecure
        - traefik.http.routers.traefik-ext.rule=Host(`traefik-ext.example.com`)
        - traefik.http.routers.traefik-ext.service=api@internal
        - traefik.http.routers.traefik-ext.middlewares=auth-ext
        - 'traefik.http.middlewares.auth-ext.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/'
        - traefik.http.services.dummy-svc.loadbalancer.server.port=9999


  traefik-int:
    image: traefik:v2.10
    hostname: '{{.Node.Hostname}}'
    networks:
      - proxy-ext
      - proxy-int
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command:
      - --providers.docker=true
      - --providers.docker.swarmMode=true
      - --providers.docker.exposedByDefault=false
      - --providers.docker.network=proxy-int
      - --providers.docker.constraints=Label(`traefik.int`,`true`)
      - --entryPoints.web.address=:80
      - --entryPoints.web.forwardedHeaders.insecure=true
      - --api.debug=true
      - --api.dashboard=true
      - --log.level=INFO
      - --accesslog=true
    deploy:
      mode: global
      placement:
        constraints:
          - node.role==manager
      labels:
        - traefik.int=true
        - traefik.enable=true
        - traefik.http.routers.traefik-int.entrypoints=web
        - traefik.http.routers.traefik-int.rule=Host(`traefik-int.example.com`)
        - traefik.http.routers.traefik-int.service=api@internal
        - traefik.http.routers.traefik-int.middlewares=auth-int
        - 'traefik.http.middlewares.auth-int.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/'
        - traefik.http.services.dummy-svc.loadbalancer.server.port=9999


  whoami-ext:
    hostname: '{{.Node.Hostname}}'
    image: traefik/whoami:v1.10
    networks:
      - proxy-ext
    deploy:
      mode: global
      labels:
        - traefik.ext=true
        - traefik.enable=true
        - traefik.http.routers.whoami-ext.entrypoints=websecure
        - traefik.http.routers.whoami-ext.rule=Host(`whoami-ext.example.com`)
        - traefik.http.services.whoami-ext.loadbalancer.server.port=80


  whoami-int:
    hostname: '{{.Node.Hostname}}'
    image: traefik/whoami:v1.10
    networks:
      - proxy-int
    deploy:
      mode: global
      labels:
        - traefik.int=true
        - traefik.enable=true
        - traefik.http.routers.whoami-int.entrypoints=web
        - traefik.http.routers.whoami-int.rule=Host(`whoami-int.example.com`)
        - traefik.http.services.whoami-int.loadbalancer.server.port=80

traefik-dynamic.yml:

http:
  routers:
    forward-all:
      rule: PathPrefix(`/`)
      service: forward-all
  services:
    forward-all:
      loadbalancer:
        servers:
          - url: http://traefik-int/
1 Like

Here is another template using only a docker-compose file.

traefik-int is labeled with traefik.ext=true, so it is automatically picked up by traefik-ext, and using entrypoint websecure, it is (incl. /dashboard/) practically available externally.

Again, it's missing a TLS wildcard certresolver for traefik-ext and all requests unknown to traefik-ext are passed to traefik-int using rule=PathPrefix(`/`).

Other "internal" services must use label traefik.int=true and entrypoint web.

version: '3.9'

networks:
  proxy-ext:
    name: proxy-ext
    driver: overlay
  proxy-int:
    name: proxy-int
    driver: overlay

volumes:
  traefik-certificates:

services:
  traefik-ext:
    image: traefik:v2.10
    hostname: '{{.Node.Hostname}}'
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
    networks:
      - proxy-ext
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik-certificates:/certificates
    command:
      - --providers.docker=true
      - --providers.docker.swarmMode=true
      - --providers.docker.exposedByDefault=false
      - --providers.docker.network=proxy-ext
      - --providers.docker.constraints=Label(`traefik.ext`,`true`)
      - --entryPoints.web.address=:80
      - --entryPoints.web.http.redirections.entryPoint.to=websecure
      - --entryPoints.web.http.redirections.entryPoint.scheme=https
      - --entryPoints.websecure.address=:443
      - --entryPoints.websecure.http.tls=true
      - --entryPoints.websecure.http.tls.certResolver=myresolver
      - --api.debug=true
      - --api.dashboard=true
      - --log.level=INFO
      - --accesslog=true
      - --certificatesResolvers.myresolver.acme.email=mail.example.com
      - --certificatesResolvers.myresolver.acme.storage=/certificates/acme.json
      - --certificatesresolvers.myresolver.acme.tlschallenge=true
    deploy:
      mode: global
      placement:
        constraints:
          - node.role==manager
      labels:
        - traefik.ext=true
        - traefik.enable=true
        - traefik.http.routers.traefik-ext.entrypoints=websecure
        - traefik.http.routers.traefik-ext.rule=Host(`traefik-ext.example.com`)
        - traefik.http.routers.traefik-ext.service=api@internal
        - traefik.http.routers.traefik-ext.middlewares=auth-ext
        - 'traefik.http.middlewares.auth-ext.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/'
        - traefik.http.services.dummy-svc.loadbalancer.server.port=9999


  traefik-int:
    image: traefik:v2.10
    hostname: '{{.Node.Hostname}}'
    networks:
      - proxy-ext
      - proxy-int
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command:
      - --providers.docker=true
      - --providers.docker.swarmMode=true
      - --providers.docker.exposedByDefault=false
      - --providers.docker.network=proxy-int
      - --providers.docker.constraints=Label(`traefik.int`,`true`)
      - --entryPoints.web.address=:80
      - --entryPoints.web.forwardedHeaders.insecure=true
      - --api.debug=true
      - --api.dashboard=true
      - --log.level=INFO
      - --accesslog=true
    deploy:
      mode: global
      placement:
        constraints:
          - node.role==manager
      labels:
        - traefik.ext=true
        - traefik.enable=true
        - traefik.http.routers.traefik-int.entrypoints=websecure
        - traefik.http.routers.traefik-int.rule=Host(`traefik-int.example.com`)
        - traefik.http.routers.traefik-int.service=api@internal
        - traefik.http.routers.traefik-int.middlewares=auth-int
        - 'traefik.http.middlewares.auth-int.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/'
        - traefik.http.services.dummy-svc.loadbalancer.server.port=9999

        - traefik.http.routers.traefik-fwd.entrypoints=websecure
        - traefik.http.routers.traefik-fwd.rule=PathPrefix(`/`)
        - traefik.http.routers.traefik-fwd.service=fwd
        - traefik.http.services.fwd.loadbalancer.server.port=80


  whoami-ext:
    hostname: '{{.Node.Hostname}}'
    image: traefik/whoami:v1.10
    networks:
      - proxy-ext
    deploy:
      mode: global
      labels:
        - traefik.ext=true
        - traefik.enable=true
        - traefik.http.routers.whoami-ext.entrypoints=websecure
        - traefik.http.routers.whoami-ext.rule=Host(`whoami-ext.example.com`)
        - traefik.http.services.whoami-ext.loadbalancer.server.port=80


  whoami-int:
    hostname: '{{.Node.Hostname}}'
    image: traefik/whoami:v1.10
    networks:
      - proxy-int
    deploy:
      mode: global
      labels:
        - traefik.int=true
        - traefik.enable=true
        - traefik.http.routers.whoami-int.entrypoints=web
        - traefik.http.routers.whoami-int.rule=Host(`whoami-int.example.com`)
        - traefik.http.services.whoami-int.loadbalancer.server.port=80
1 Like

Hi @bluepuma77.

Thank you for your help. It worked perfectly to my propose. :slight_smile:

I just had to make a small change on your approach, because since both traefik are referencing api@internal, they were showing the same result (from the external one).

My workaround was to enable the --api.insecure=true and use the port=8080 on the internal Traefik. Of course I will have to create the middlewares (auth and ipwhitelist) to do not expose it to the other services inside the swarm.

My question is: was my approach the best solution? Is there another way to reference the api@internal on the internal traefik without conflicting with the external one?

I've just figure out that my approach to protect the API makes no sense. It will be applied to the proxy layer (accessing from outside). Another service could still access the traefik API (traefik-int:8080), even with whitelist and auth. :frowning:

Back to the doc/google/whatever. :slight_smile:

The first one with traefik-dynamic.yml has a completely separate traefik-int, there dashboard should work with auth.

Note: add attachable: true to the network definition if you want other services in different compose files to work with Traefik.

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.