11

Configuración dinámica en Traefik

Vistas: 0
Configuración dinámica en Traefik

Hasta ahora hemos trabajado exclusivamente con Docker como proveedor. Las labels de los contenedores son una forma cómoda de configurar Traefik, pero tienen una limitación importante: la configuración está atada a cada contenedor. ¿Y si quieres tener middlewares o routers que se compartan entre varios servicios? ¿O si quieres configurar servicios que no están en Docker? ¿O simplemente prefieres tener toda la configuración en un archivo en lugar de esparcida en labels por ocho archivos docker-compose distintos?

Para todo eso está el File provider, el proveedor de archivos de Traefik. Te permite definir toda la configuración dinámica (routers, servicios, middlewares) en archivos YAML o TOML. Y lo mejor es que puedes recargar la configuración en caliente, sin reiniciar Traefik. Esto te puede ahorrar más de un disgusto cuando necesites cambiar algo en producción.

En el capítulo anterior viste cómo balancear tráfico entre múltiples instancias de un servicio, cómo configurar health checks y sticky sessions. Pues bien, todo eso que configuraste con labels de Docker, también puedes hacerlo con archivos. Y más.

¿File provider vs Docker provider?

Cada uno tiene su sitio.

  • Docker provider: ideal para la configuración específica de cada servicio (dominio, entrypoint, TLS). Va con el contenedor, viaja con él, se despliega con él. Si mueves el contenedor a otra máquina, la configuración se mueve con él.
  • File provider: ideal para configuración compartida (middlewares comunes, routers globales, servicios externos). Es el lugar donde pones lo que no pertenece a un contenedor concreto.

Lo bueno es que puedes usar ambos a la vez. No es excluyente. De hecho, te recomiendo que uses los dos combinados. Es el enfoque híbrido que uso en producción y funciona de maravilla: los middlewares compartidos en archivos, la configuración específica de cada servicio en las labels de Docker.

Piénsalo así: las labels son para lo que es propio del contenedor (su dominio, su entrypoint, si usa TLS o no). Los archivos son para lo que es global (los rate limits que aplican a todos los servicios, las cabeceras de seguridad que toda la infraestructura debe tener, los servicios externos que no están en Docker).

Configurar el File provider

Para usar el File provider, tienes que indicarlo en tu traefik.yml,

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    directory: /etc/traefik/dynamic/
    watch: true
  • directory: el directorio donde Traefik buscará archivos de configuración. Puede ser también un archivo único con filename si prefieres tener todo en un solo sitio.
  • watch: si es true, Traefik monitoriza el directorio y recarga la configuración automáticamente cuando los archivos cambian. Sin necesidad de reiniciar.

Y en el docker-compose.yml montas el directorio,

volumes:
  - ./traefik.yml:/etc/traefik/traefik.yml:ro
  - ./dynamic:/etc/traefik/dynamic:ro

Fíjate que los montamos como :ro (read-only). En producción no tiene sentido que Traefik pueda escribir en sus propios archivos de configuración. Eso es cosa tuya, del administrador.

Usar un archivo único en lugar de un directorio

Si prefieres tener todo en un único archivo, puedes usar filename en lugar de directory,

providers:
  file:
    filename: /etc/traefik/dynamic.yml
    watch: true

¿Cuál elegir? Te recomiendo el directorio desde el principio, aunque empieces con un solo archivo. ¿Por qué? Porque conforme tu infraestructura crece, separar por temas te evita tener que buscar entre mil líneas de YAML. Empieza con directorio, aunque solo tengas un archivo dentro. Cuando necesites separar, solo tienes que crear un archivo nuevo.

Puedes incluso combinar filename y directory si quieres cargar un archivo base y luego extensiones. Pero no te líes demasiado. Con el directorio tienes suficiente.

Estructura de un archivo de configuración dinámica

Los archivos de configuración dinámica siguen la misma estructura que las labels, pero en formato YAML.

http:
  routers:
    mi-router:
      rule: Host(`app.dominio.com`)
      service: mi-servicio
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

  services:
    mi-servicio:
      loadBalancer:
        servers:
          - url: "http://192.168.1.10:3000"
        healthCheck:
          path: /health
          interval: 10s
          timeout: 3s

  middlewares:
    rate-limit:
      rateLimit:
        average: 100
        burst: 200
        period: 1m

Fíjate en la estructura: http como sección principal, y dentro routers, services, middlewares. Es exactamente lo mismo que ves en las labels de Docker, pero en YAML estructurado.

Para TCP y UDP la estructura es similar pero con tcp y udp como secciones principales. Da igual que mezcles todo en un mismo archivo o lo separes en varios. Traefik lo fusiona todo.

Secciones completas del File provider

Las secciones que puedes definir en el File provider son,

  • http.routers: reglas de enrutamiento para tráfico HTTP/HTTPS.
  • http.services: definiciones de servicios HTTP con balanceo, health checks, sticky sessions.
  • http.middlewares: middlewares que se aplican a las peticiones HTTP.
  • http.serversTransports: configuración de transporte HTTP, como timeout, proxy inverso, o ignorar certificados.
  • tcp.routers: reglas de enrutamiento para tráfico TCP.
  • tcp.services: servicios TCP con balanceo de carga.
  • tcp.middlewares: middlewares TCP (más limitados que los HTTP).
  • udp.routers: routers para tráfico UDP.
  • udp.services: servicios UDP.

Ya te habrás dado cuenta de que el modelo es el mismo para HTTP, TCP y UDP. Una vez que entiendes cómo funciona uno, los demás son exactamente igual pero cambiando la sección.

Middlewares compartidos

Este es el caso de uso más habitual del File provider. Definir middlewares una vez y usarlos desde cualquier contenedor Docker. Si has leído los capítulos anteriores sobre middlewares básicos y de autenticación, ya sabes lo potentes que son. Ahora vas a ver cómo centralizarlos.

Crea un archivo dynamic/middlewares.yml,

http:
  middlewares:
    rate-limit-100:
      rateLimit:
        average: 100
        burst: 200
        period: 1m

    sec-headers:
      headers:
        customResponseHeaders:
          X-Content-Type-Options: "nosniff"
          X-Frame-Options: "DENY"
          X-XSS-Protection: "1; mode=block"
          Referrer-Policy: "strict-origin-when-cross-origin"
          Permissions-Policy: "camera=(), microphone=(), geolocation=()"

    auth-basico-admin:
      basicAuth:
        usersFile: /etc/traefik/users.txt

    solo-red-local:
      ipAllowList:
        sourceRange:
          - "192.168.1.0/24"
          - "10.0.0.0/8"

    compresion:
      compress:
        excludedContentTypes:
          - text/event-stream

    redirect-https:
      redirectRegex:
        permanent: false
        regex: "^http://"
        replacement: "https://"

    rate-limit-estricto:
      rateLimit:
        average: 20
        burst: 40
        period: 1m

Y luego en los labels de Docker los referencias con @file,

labels:
  - "traefik.http.routers.mi-app.middlewares=rate-limit-100@file,sec-headers@file"

Fíjate en el @file al final. Eso le dice a Traefik que busque ese middleware en el File provider, no en las labels de Docker. Es el mismo mecanismo que usas para referenciar cualquier recurso de cualquier proveedor: nombre@nombre-del-proveedor.

Cadenas de middlewares de distintos proveedores

Puedes mezclar middlewares de Docker y del File provider en el mismo router. Esto es muy útil cuando tienes middlewares específicos del contenedor (definidos en sus labels) y quieres combinarles con los globales del File provider,

labels:
  - "traefik.http.routers.mi-app.middlewares=rate-limit-100@file,sec-headers@file,mi-middleware-local@docker"

Traefik los ejecuta en el orden en que los listas. Primero el rate limit, luego las cabeceras de seguridad, y por último el middleware local del contenedor. Dale caña a este patrón, porque te permite construir pipelines de procesamiento muy modulares.

Middleware de redirect: caso real

Uno de los middlewares que más uso desde el File provider es el de redirección. Por ejemplo, para redirigir tráfico de un dominio antiguo a uno nuevo sin tocar cada contenedor,

http:
  middlewares:
    redirect-old-domain:
      redirectRegex:
        permanent: true
        regex: "^https://antiguo.dominio.com/(.*)"
        replacement: "https://nuevo.dominio.com/${1}"

Lo aplicas a un router y listo. Todos los que lleguen al dominio antiguo se redirigen automáticamente al nuevo, manteniendo la ruta. Esto te puede ahorrar más de un disgusto durante una migración.

Middleware de retry y circuit breaker

Para servicios críticos, puedes combinar reintentos con circuit breaker desde el File provider,

http:
  middlewares:
    retry-peticiones:
      retry:
        attempts: 3
        initialInterval: 100ms

    circuit-breaker-api:
      circuitBreaker:
        expression: "NetworkErrorRatio() > 0.5 || LatencyAtQuantileMS(50.0) > 100"
        checkPeriod: 10s
        fallbackDuration: 30s
        recoveryDuration: 30s

El circuit breaker evita que un servicio que está fallando reciba más tráfico del que puede manejar, dándole tiempo para recuperarse. Y el retry reintenta peticiones fallidas automáticamente.

Middlewares que solo tienen sentido desde el File provider

Hay middlewares que raramente tienen sentido definirlos en las labels de Docker porque son globales por naturaleza. Por ejemplo,

IP AllowList para el dashboard

El dashboard de Traefik debería estar siempre protegido por IP. No tiene sentido ponerlo en las labels de un contenedor, porque el dashboard no es un contenedor como tal, es parte de Traefik.

http:
  middlewares:
    dashboard-auth:
      basicAuth:
        usersFile: /etc/traefik/users.txt
      ipAllowList:
        sourceRange:
          - "192.168.1.0/24"

  routers:
    dashboard:
      rule: Host(`traefik.dominio.com`)
      service: api@internal
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt
      middlewares:
        - dashboard-auth

Fíjate en el service: api@internal. Ese es el servicio interno del dashboard de Traefik. Solo puedes referenciarlo desde el File provider, no desde las labels de Docker.

Cabeceras de seguridad globales

Las cabeceras de seguridad como HSTS, Content-Security-Policy, o X-Frame-Options son típicamente globales. Quieres que todas tus aplicaciones las tengan. Definirlas en el File provider y referenciarlas desde cada contenedor es mucho más limpio que copiar las mismas veinte líneas de YAML en las labels de cada servicio.

Routers desde archivo

También puedes definir routers completos en archivos. Esto es útil para servicios que no están en Docker, o para configurar rutas que no pertenecen a ningún contenedor,

http:
  routers:
    api-externa:
      rule: Host(`api.dominio.com`) && PathPrefix(`/v2`)
      entryPoints:
        - websecure
      service: api-v2
      tls:
        certResolver: letsencrypt
      middlewares:
        - rate-limit-100@file
        - sec-headers@file

    landing-page:
      rule: Host(`dominio.com`) || Host(`www.dominio.com`)
      entryPoints:
        - websecure
      service: landing-estatico
      tls:
        certResolver: letsencrypt
      priority: 10

    api-publica:
      rule: Host(`api.dominio.com`) && PathPrefix(`/public`)
      entryPoints:
        - websecure
      service: api-publica
      middlewares:
        - rate-limit-estricto@file

Prioridades entre routers

Cuando tienes varios routers que pueden coincidir con la misma petición, Traefik usa la prioridad. Por defecto, la prioridad se calcula automáticamente: cuantos más matchers tenga la regla, mayor prioridad.

Pero a veces necesitas forzarla. Por ejemplo, si tienes un router genérico para Host(dominio.com) y otro específico para Host(dominio.com) && PathPrefix(/api), el específico tendrá prioridad automáticamente. Pero si quieres que un router con una regla simple tenga prioridad sobre otro con reglas más complejas, usa priority explícitamente.

http:
  routers:
    catchall:
      rule: HostRegexp(`{subdomain:[a-z]+}.dominio.com`)
      service: default-service
      priority: 1

    especifico:
      rule: Host(`admin.dominio.com`)
      service: admin-service
      priority: 100

El router especifico tiene prioridad 100 frente al 1 del catchall. Así te aseguras de que admin.dominio.com va al servicio correcto y no se lo come el catchall.

Servicios externos (no Docker)

El File provider brilla cuando necesitas enrutar a servicios que no están en Docker. Por ejemplo, un servidor físico corriendo una base de datos, o un servicio en otra máquina,

http:
  services:
    postgres-externo:
      loadBalancer:
        servers:
          - url: "http://192.168.1.50:5432"

    api-terceros:
      loadBalancer:
        servers:
          - url: "https://api.proveedor.com"
        passHostHeader: false

    landing-estatico:
      loadBalancer:
        servers:
          - url: "http://10.0.0.10:8080"
        healthCheck:
          path: /healthz
          interval: 30s
          timeout: 5s
          unhealthyStatuses:
            - 500
            - 503

Servicios con múltiples servidores y balanceo

Igual que configuraste en el capítulo anterior el balanceo de carga con las labels de Docker, puedes hacerlo desde el File provider,

http:
  services:
    api-cluster:
      loadBalancer:
        servers:
          - url: "http://192.168.1.10:8080"
          - url: "http://192.168.1.11:8080"
          - url: "http://192.168.1.12:8080"
        healthCheck:
          path: /health
          interval: 10s
          timeout: 3s
        sticky:
          cookie:
            name: _session
            httpOnly: true
        serversTransport: backend-transport

  serversTransports:
    backend-transport:
      insecureSkipVerify: true
      maxIdleConnsPerHost: 100
      forwardingTimeouts:
        dialTimeout: 10s
        responseHeaderTimeout: 10s

Esto configura un cluster de tres servidores con health check cada 10 segundos, sticky sessions mediante cookie, y un transporte personalizado que ignora certificados SSL (útil cuando los backends usan certificados autofirmados).

Sticky sessions con configuración avanzada

Las sticky sessions que viste en el capítulo anterior también se configuran desde archivo,

http:
  services:
    app-sesion:
      loadBalancer:
        servers:
          - url: "http://10.0.0.1:3000"
          - url: "http://10.0.0.2:3000"
          - url: "http://10.0.0.3:3000"
        sticky:
          cookie:
            name: "traefik-session"
            secure: true
            httpOnly: true
            sameSite: "lax"
            maxAge: 3600

Esto te puede ahorrar más de un disgusto si tienes aplicaciones que guardan estado en memoria. Configuras la cookie, la marcas como segura y httpOnly, y los usuarios siempre caen en la misma instancia mientras la cookie sea válida.

Configuración dinámica para TCP y UDP

El File provider también funciona para TCP y UDP. En el capítulo 12 veremos esto en profundidad, pero te adelanto cómo se estructura por si necesitas enrutar tráfico TCP desde ya,

tcp:
  routers:
    mumble-server:
      rule: HostSNI(`*`)
      entryPoints:
        - mumble
      service: mumble-servicio
      tls:
        passthrough: true

    postgres-remoto:
      rule: HostSNI(`db.dominio.com`)
      entryPoints:
        - postgres
      service: postgres-cluster
      tls:
        passthrough: true

  services:
    mumble-servicio:
      loadBalancer:
        servers:
          - address: "192.168.1.100:64738"

    postgres-cluster:
      loadBalancer:
        servers:
          - address: "192.168.1.20:5432"
          - address: "192.168.1.21:5432"
        healthCheck:
          path: "/health"
          interval: 5s
          timeout: 3s
udp:
  routers:
    dns-server:
      entryPoints:
        - dns
      service: dns-cluster

  services:
    dns-cluster:
      loadBalancer:
        servers:
          - address: "10.0.0.53:53"
          - address: "10.0.0.54:53"

Con TCP y UDP la estructura es calcada a HTTP: routers que definen reglas, servicios que apuntan a servidores, y middlewares donde aplican.

Varios archivos de configuración

Puedes tener varios archivos en el directorio dynamic/ y Traefik los cargará todos. Esto te permite organizar la configuración por temas,

dynamic/
├── middlewares.yml        # Middlewares compartidos
├── routers.yml            # Routers globales
├── servicios-externos.yml # Servicios fuera de Docker
├── tcp.yml                # Configuración TCP/UDP
├── security.yml           # Cabeceras de seguridad y whitelists
└── dashboard.yml          # Router y middlewares del dashboard

Traefik los carga todos y los fusiona automáticamente. Si hay conflictos (dos archivos definen el mismo router), Traefik te lanzará un error. Así que asegúrate de que los nombres de routers, servicios y middlewares sean únicos en todo el conjunto de archivos.

Orden de carga y fusión

Traefik no garantiza un orden de carga específico. Lo que sí garantiza es que la fusión es completa: si un archivo define routers y otro define middlewares, el resultado final contiene todo. El problema llega cuando dos archivos definen el mismo recurso con distinta configuración. Ahí Traefik lanza un error y no aplica ninguno de los dos.

Para evitar esto, te recomiendo una convención de nombres: prefija cada recurso según el archivo donde está definido. Por ejemplo,

# middlewares.yml
http:
  middlewares:
    mw-rate-limit-global:
    mw-sec-headers-global:

# routers.yml
http:
  routers:
    rt-api-externa:
    rt-dashboard:

# servicios-externos.yml
http:
  services:
    svc-api-cluster:
    svc-landing:

No es obligatorio, pero cuando tienes veinte middlewares y quince routers, agradeces tener nombres que te digan de un vistazo qué es cada cosa.

Configuración por directorios anidados

Una técnica avanzada es organizar los archivos en subdirectorios. Traefik no los soporta de forma nativa, pero puedes usar un truco con montajes de Docker: monta varios directorios en el mismo punto de montaje,

services:
  traefik:
    volumes:
      - ./dynamic/middlewares:/etc/traefik/dynamic/middlewares:ro
      - ./dynamic/routers:/etc/traefik/dynamic/routers:ro
      - ./dynamic/services:/etc/traefik/dynamic/services:ro

Pero esto es más complicado de mantener. Te recomiendo que te quedes con un solo directorio plano. Es más sencillo y cumple su función.

Recarga en caliente

Una de las grandes ventajas del File provider con watch: true es que puedes modificar los archivos y Traefik los recarga sin necesidad de reiniciar.

Esto es muy útil cuando,

  • Añades un nuevo middleware compartido.
  • Cambias la IP de un servidor externo porque lo has migrado a otra máquina.
  • Añades un router nuevo para un servicio que acabas de poner en marcha.
  • Modificas las cabeceras de seguridad para cumplir con un nuevo requisito.
  • Desactivas temporalmente un servicio cambiando su router.

Solo tienes que editar el archivo y guardar. Traefik lo detecta y aplica los cambios en segundos. Sin reinicios, sin cortes de servicio, sin tener que esperar a que el contenedor se levante otra vez.

Cómo verificar que la recarga ha funcionado

Cuando modificas un archivo, puedes verificar que Traefik lo ha cargado correctamente de varias formas,

  1. En los logs de Traefik: busca líneas como Configuration loaded from file provider.
  2. En el dashboard: los recursos aparecen automáticamente si la recarga ha ido bien.
  3. Con la API de Traefik: haz una petición al endpoint /api/http/routers para ver los routers activos.
curl -s https://traefik.dominio.com/api/http/routers | jq '.'

Si algo falla, Traefik no se cae. Simplemente ignora el archivo con errores y se queda con la última configuración válida. Esto te puede ahorrar más de un disgusto si cometes un error de sintaxis en medio de la noche.

Errores típicos en la recarga

El error más común es tener un YAML mal formado. Un espacio de más o un tabulador en lugar de espacios y el archivo no se carga. Traefik es muy sensible a la indentación. Usa siempre espacios, nunca tabuladores.

Otro error típico es referenciar un middleware o servicio que no existe. Si desde un router haces referencia a middlewares: - rate-limit-100@file pero ese middleware no está definido en ningún archivo, Traefik rechazará la configuración y se quedará con la anterior.

Para evitar estos problemas, te recomiendo validar tus archivos YAML antes de copiarlos al servidor. Puedes usar herramientas como yamllint o incluso el propio Traefik en modo dry-run,

docker run --rm -v $(pwd)/dynamic:/dynamic traefik:v3.7 traefik healthcheck

No es un validador exhaustivo, pero te da una primera línea de defensa.

Ejemplo práctico completo

Voy a dejarte un ejemplo completo con File provider, con varios servicios reales,

traefik.yml:

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
  postgres:
    address: ":5432"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    directory: /etc/traefik/dynamic
    watch: true

api:
  dashboard: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

dynamic/middlewares.yml:

http:
  middlewares:
    rate-limit-50:
      rateLimit:
        average: 50
        burst: 100
        period: 1m

    sec-headers:
      headers:
        customResponseHeaders:
          X-Content-Type-Options: "nosniff"
          X-Frame-Options: "DENY"
          X-XSS-Protection: "1; mode=block"
          Referrer-Policy: "strict-origin-when-cross-origin"
        customRequestHeaders:
          X-Forwarded-Proto: "https"

    auth-admin:
      basicAuth:
        usersFile: /etc/traefik/users.txt

    geobloqueo-api:
      plugin:
        geoblock:
          allowedCountries:
            - "ES"
            - "MX"
            - "AR"
          api: "https://geo.api.proveedor.com"

    retry-3:
      retry:
        attempts: 3
        initialInterval: 500ms

dynamic/routers.yml:

http:
  routers:
    dashboard:
      rule: Host(`traefik.dominio.com`)
      service: api@internal
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt
      middlewares:
        - auth-admin

    api-publica:
      rule: Host(`api.dominio.com`) && PathPrefix(`/v1`)
      entryPoints:
        - websecure
      service: api-v1-externa
      tls:
        certResolver: letsencrypt
      middlewares:
        - rate-limit-50
        - sec-headers

dynamic/servicios-externos.yml:

http:
  services:
    api-v1-externa:
      loadBalancer:
        servers:
          - url: "http://10.0.0.10:4000"
          - url: "http://10.0.0.11:4000"
        healthCheck:
          path: /api/health
          interval: 10s
          timeout: 3s
        sticky:
          cookie:
            name: "api-session"
            httpOnly: true

docker-compose.yml:

services:
  traefik:
    image: traefik:v3.7
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./dynamic:/etc/traefik/dynamic:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
      - ./users.txt:/etc/traefik/users.txt:ro
    networks:
      - traefik-net

  nextcloud:
    image: nextcloud:stable
    volumes:
      - ./nextcloud:/var/www/html
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nextcloud.rule=Host(`cloud.dominio.com`)"
      - "traefik.http.routers.nextcloud.entrypoints=websecure"
      - "traefik.http.routers.nextcloud.tls=true"
      - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
      - "traefik.http.routers.nextcloud.middlewares=rate-limit-50@file,sec-headers@file"
      - "traefik.http.services.nextcloud.loadbalancer.server.port=80"
    networks:
      - traefik-net

  wordpress:
    image: wordpress:latest
    volumes:
      - ./wordpress:/var/www/html
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.wordpress.rule=Host(`blog.dominio.com`)"
      - "traefik.http.routers.wordpress.entrypoints=websecure"
      - "traefik.http.routers.wordpress.tls=true"
      - "traefik.http.routers.wordpress.tls.certresolver=letsencrypt"
      - "traefik.http.routers.wordpress.middlewares=sec-headers@file"
    networks:
      - traefik-net

networks:
  traefik-net:
    name: traefik-net

Fíjate en cómo NextCloud usa el rate limit y las cabeceras de seguridad desde el File provider, mientras que WordPress solo usa las cabeceras. Cada servicio aplica los middlewares que necesita, sin tener que definirlos. Así mantienes la configuración limpia y evitas duplicar YAML.

Casos prácticos reales

Voy a contarte tres situaciones reales donde el File provider me ha sacado de un apuro.

Caso 1: Migración de servidores sin cortes

Tenía un servicio crítico que apuntaba a un servidor físico en 192.168.1.100:8080. Había que migrarlo a un cluster nuevo en 10.0.0.10:8080 y 10.0.0.11:8080. Con el File provider, el cambio fue editar el archivo servicios-externos.yml, cambiar el servidor único por los dos nuevos, y guardar. Traefik recargó en caliente. Ni un solo corte de servicio.

# Antes
http:
  services:
    app-legacy:
      loadBalancer:
        servers:
          - url: "http://192.168.1.100:8080"

# Después
http:
  services:
    app-legacy:
      loadBalancer:
        servers:
          - url: "http://10.0.0.10:8080"
          - url: "http://10.0.0.11:8080"
        healthCheck:
          path: /health

Caso 2: Bloquear un ataque por IP

Me llegó un aviso de que una IP estaba haciendo scraping agresivo a una API pública. En lugar de tocar el firewall o modificar el docker-compose, añadí un middleware en caliente,

http:
  middlewares:
    bloquear-scraper:
      ipAllowList:
        sourceRange:
          - "203.0.113.0/32"
        rejectStatusCode: 429

Y lo referencié desde el router de la API. El cambio fue inmediato. El scraper empezó a recibir 429 Too Many Requests. Sin reiniciar nada.

Caso 3: Añadir autenticación a un servicio legacy

Un servicio antiguo no tenía autenticación. Había que protegerlo hasta que el equipo de desarrollo lo actualizara. En cinco minutos,

  1. Generé un fichero users.txt con htpasswd.
  2. Añadí el middleware auth-basico-admin al archivo middlewares.yml.
  3. Lo referencié en el router del servicio legacy desde el File provider.

El servicio legacy ni se enteró. Para él, todas las peticiones llegaban igual. Traefik se encargaba de autenticar antes de pasar la petición.

Buenas prácticas

  1. Separa la configuración en varios archivos: middlewares, routers, servicios externos. Así es más fácil de mantener y de encontrar lo que buscas.
  2. Usa watch: true para recarga automática. Editas el archivo y Traefik lo aplica sin reiniciar. Esto es clave en producción.
  3. Para middlewares compartidos, el File provider es la opción correcta. No los dupliques en labels de cada contenedor. Centraliza y reutiliza.
  4. Para servicios en Docker, usa las labels. Es más natural, la configuración viaja con el contenedor, y al hacer deploy de una nueva versión, la configuración se actualiza sola.
  5. Usa @file para referenciar recursos del File provider desde las labels de Docker. Y recuerda que también puedes referenciar recursos de Docker con @docker.
  6. Valida el YAML antes de subirlo. Un error de sintaxis y Traefik ignora el archivo. Usa yamllint o tu editor favorito.
  7. Pon nombres descriptivos a tus middlewares y routers. rate-limit-50 es mejor que rl1. Cuando tengas veinte middlewares, lo agradecerás.
  8. Los archivos en :ro: monta los archivos de configuración como read-only. Traefik no necesita escribir en ellos.
  9. Monitorea los logs de Traefik cuando modifiques archivos en producción. Un vistazo rápido a los logs te confirma que la recarga ha ido bien.
  10. Usa prioridades con cabeza: no pongas priority a todo. Solo cuando necesites forzar el orden entre routers que compiten por la misma ruta.

Conclusión

El File provider te da flexibilidad para centralizar la configuración que se comparte entre servicios, y para enrutar tráfico a servidores que no están en Docker. Combinado con el Docker provider, tienes lo mejor de ambos mundos: la configuración específica viaja con cada contenedor, y la configuración global está centralizada en archivos que puedes modificar en caliente.

La recarga en caliente es una de esas características que cuando la pruebas, no entiendes cómo has podido vivir sin ella. Editas un archivo, guardas, y el cambio se aplica en segundos. Sin reinicios, sin cortes de servicio, sin tener que levantar contenedores.

En el próximo capítulo veremos los Plugins, cómo extender Traefik más allá de los middlewares que vienen por defecto. Verás cómo instalar plugins, cómo configurarlos desde el File provider, y cómo combinarlos con todo lo que has aprendido hasta ahora. Dale caña.

Más información

Deja una respuesta