How to add a Traefik service to an existing Docker Swarm cluster?

Viewed 803

I have a web application that I can deploy successfully using Docker Swarm with a Compose file:

docker stack deploy --compose-file docker-compose.yml common

and is described by the file:

version: "3.8"
services:
  www:
    image: localhost:5000/www.learnintouch
    ports:
      - "81:80"
      - "444:443"
    networks:
      common:
networks:
  common:
    name: common

Now I add a Traefik service to it:

version: "3.8"
services:
  traefik:
    image: traefik
    ports:
      - target: 81
        published: 80
        mode: host
      - target: 8080
        published: 8080
        mode: host
      - target: 444
        published: 443
        mode: host
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      labels:
        - traefik.enable=true
        - traefik.docker.network=common
        - traefik.constraint-label=common
        - traefik.docker.lbswarm=true
    command:
      --log.level=DEBUG
      --api.insecure=true
      --providers.docker
      --providers.docker.swarmmode
      --accesslog
      --api.dashboard=true
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "~/dev/docker/projects/common/volumes/traefik/logs:/traefiklog"
      - "~/dev/docker/projects/common/volumes/traefik/rules:/rules"
  www:
    image: localhost:5000/www.learnintouch
    ports:
      - "81:80"
      - "444:443"
    labels:
      - "traefik.http.routers.www_learnintouch.rule=Host('www_learnintouch.docker.localhost')"
    networks:
      common:
networks:
  common:
    name: common
  default:
    driver: overlay

The containers are deployed:

22:49 $ docker ps
CONTAINER ID   IMAGE                                       COMMAND                  CREATED              STATUS                        PORTS                                                                                                                         NAMES
ff38498c061f   localhost:5000/www.learnintouch:latest      "/bin/bash /usr/loca…"   About a minute ago   Up About a minute (healthy)                                                                                                                                 www_learnintouch_www.1.n9mvfo64hp8unzvcau6rws99q
df070019539f   traefik:latest                              "/entrypoint.sh --lo…"   2 minutes ago        Up 2 minutes                  80/tcp, 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:80->81/tcp, :::80->81/tcp, 0.0.0.0:443->444/tcp, :::443->444/tcp   common_traefik.1.t141a053tvcbi0ql5ztbjmfgp

But there are some issues with the port mappings:

common_traefik.1.t141a053tvcb@stephane-pc    | time="2021-06-13T20:52:24Z" level=error msg="service \"common-traefik\" error: port is missing" providerName=docker container=common-traefik-m6sxqj8giwuex2jlbrsgilsm9
common_traefik.1.t141a053tvcb@stephane-pc    | time="2021-06-13T20:52:39Z" level=error msg="service \"www-learnintouch-www\" error: port is missing" providerName=docker container=www-learnintouch-www-n9mvfo64hp8unzvcau6rws99q
common_traefik.1.t141a053tvcb@stephane-pc    | time="2021-06-13T20:53:24Z" level=warning msg="Could not find network named 'common' for container 'common_traefik'! Maybe you're missing the project's prefix in the label? Defaulting to first available network." providerName=docker container=common-traefik-m6sxqj8giwuex2jlbrsgilsm9 serviceName=common-traefik

I did not create a network manually with another command, since I never needed to do it before adding the Traefik service.

As a Traefik and network dummy I was hoping for an easy installation without having to manually specify hostnames redundantly, and without having to run some external commands as well.

After a search for hints I found this opened issue but I could not figure out what to make of it.

UPDATE: I added a port to all the services as in:

labels:
  - "traefik.http.routers.www_learnintouch.rule=Host('www_learnintouch.docker.localhost')"
  - "traefik.http.services.www.loadbalancer.server.port=5000"

but the log still shows the same error message.

UPDATE: The file after the provided solution:

version: "3.9"
services:
  traefik:
    image: traefik
    networks:
      common:
    ports:
      - target: 81
        published: 80
        mode: host
      - target: 444
        published: 443
        mode: host
    deploy:
      mode: global
      placement:
        constraints:
          - node.role == manager
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.traefik.loadbalancer.server.port=8080"
        - "traefik.constraint-label=common"
    command:
      --log.level=DEBUG
      --api.insecure=true
      --providers.docker
      --providers.docker.swarmmode
      --accesslog
      --api.dashboard=true
      --providers.docker.network=common
      --providers.docker.exposedbydefault=false
      --providers.docker.defaultRule=Host(`{{normalize .Name}}.docker.local`)
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "~/dev/docker/projects/common/volumes/traefik/logs:/traefiklog"
      - "~/dev/docker/projects/common/volumes/traefik/rules:/rules"
  logrotate:
    image: localhost:5000/logrotate
    networks:
      common:
    labels:
      - "traefik.enable=false"
    volumes:
      - "~/dev/docker/projects/common/volumes/logs:/usr/local/logrotate/logs"
    user: "${CURRENT_UID}:${CURRENT_GID}"
    environment:
      HOST_USER_ID: ${CURRENT_UID}
      HOST_GROUP_ID: ${CURRENT_GID}
  www:
    image: localhost:5000/www.learnintouch
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.www.loadbalancer.server.port=80"
    networks:
      common:
    volumes:
      - "~/dev/docker/projects/learnintouch/volumes/www.learnintouch/account/data:/usr/local/learnintouch/www/learnintouch.com/account/data"
      - "~/dev/docker/projects/learnintouch/volumes/www.learnintouch/account/backup:/usr/local/learnintouch/www/learnintouch.com/account/backup"
      - "~/dev/docker/projects/learnintouch/volumes/engine:/usr/local/learnintouch/engine"
      - "~/dev/docker/projects/common/volumes/letsencrypt/certbot/conf/live/thalasoft.com:/usr/local/learnintouch/letsencrypt"
      - "~/dev/docker/projects/common/volumes/logs:/usr/local/apache/logs"
      - "~/dev/docker/projects/common/volumes/logs:/usr/local/learnintouch/logs"
    user: "${CURRENT_UID}:${CURRENT_GID}"
    environment:
      HOST_USER_ID: ${CURRENT_UID}
      HOST_GROUP_ID: ${CURRENT_GID}
      DB_HOST: mysql
      DB_PORT: 3306
      WWW_LEARNINTOUCH_DB_NAME: db_learnintouch
      WWW_LEARNINTOUCH_DB_USER: learnintouch
      WWW_LEARNINTOUCH_NAME: learnintouch
      WWW_LEARNINTOUCH_DOMAIN: dev.learnintouch.com
      WWW_LEARNINTOUCH_SCHEME: http
      STAFF_EMAIL: mittiprovence@yahoo.se
      NODEJS_SOCKET_PORT: 9001
    deploy:
      replicas: 1
      restart_policy:
        condition: any
        delay: 5s
        max_attempts: 3
        window: 10s
    healthcheck:
      test: curl --fail http://127.0.0.1:80/engine/ping.php || exit 1
      interval: 10s
      timeout: 10s
      retries: 3
networks:
  common:
    name: common
  default:
    driver: overlay
1 Answers

In docker swarm mode traefik cant (for some reason!?) detect the ports that services are listening on.

As such, every docker service needs to have a label of the form:

"traefik.http.services.www_learnintouch.loadbalancer.server.port=8080"

--

I missed in the first pass that there is a www service.

You need to remove the ports section from www as traefik needs to handle ingress, and be the service that actually exposes those ports.

Then, you also need to add networks: [ common ] to traefik, as it can't do this routing without sharing a common network.

Finally, traefik exposes all services by default, and so will emit errors for any and all services on your swarm that are not configured with a full/minimal set of traefik labels including itself. For some reason the maintainers are deaf to the idea that this is stupid and should be fixed.

If we fix these issues, I would suggest a traefik service that looks like this:

version: "3.9"
services:
  traefik:
    image: traefik
    ports:
      - target: 80
        published: 80
        mode: host
      - target: 443
        published: 443
        mode: host
    networks:
      - common
    deploy:
      mode: global
      placement:
        constraints:
          - node.role == manager
      labels:
        - traefik.enable=true
        - traefik.http.services.traefik.loadbalancer.server.port=8080
    command:
      --log.level=DEBUG
      --api.insecure=true
      --providers.docker
      --providers.docker.swarmmode
      --accesslog
      --api.dashboard=true
      --providers.docker.network=common
      --providers.docker.exposedbydefault=false
      --providers.docker.defaultRule=Host(`{{normalize .Name}}.docker.local`)
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

And your www service would be:

 www_learnintouch:
    image: localhost:5000/www.learnintouch
    labels:
      - traefik.enable=true
      - traefik.http.services.www_learnintouch.loadbalancer.server.port=80
    networks:
      common:
Related