====== Apuntes de Traefik V2 ====== Estas notas sobre //Traefik// son un refrito de las siguientes fuentes: * [[https://doc.traefik.io/traefik/getting-started/quick-start/|Traefik - Quick Start]] * [[https://joshuaavalon.io/setup-traefik-v2-step-by-step|Joshua Avalon Blog: Setup Traefik v2]] * [[https://www.digitalocean.com/community/tutorials/how-to-use-traefik-v2-as-a-reverse-proxy-for-docker-containers-on-ubuntu-20-04|Digital Ocean: Howto use Traefik as reverse proxy for Docker]] * [[https://blog.roberthallam.org/2020/05/generating-a-traefik-nginx-password-hash-without-htpasswd/|Hash with openssl]] * [[https://medium.com/@containeroo/traefik-2-0-docker-a-simple-step-by-step-guide-e0be0c17cfa5|Containeroo: Traefik 2.0 + Docker — a Simple Step by Step Guide]] * [[https://medium.com/@containeroo/traefik-2-0-docker-an-advanced-guide-d098b9e9be96|Containeroo: Traefik 2.0 + Docker — an Advanced Guide]] * [[https://medium.com/@containeroo/traefik-2-2-docker-global-entrypoint-configuration-ff11d7f84913|Containeroo: Traefik 2.2 + Docker: Entry Point Configuration]] ===== Edge Router ===== //Traefik// es un //edge router// (router de borde, router frontera, proxy inverso) Es decir que es el punto de entrada a tu servidor (o plataforma) y se encarga de interceptar cada petición de entrada y de enrutarlas a los servicios correspondientes. Puede hacer el enrutamiento basándose en muchos criterios diferentes (nombre del host, cabeceras, etc.) Lo normal es que instales //Traefik// entre tu platafoma de servicios e Internet. De esta forma no tienes que abrir mas que uno o dos puertos hacia la red (tipicamente el 80 y el 443) y atender todas las peticiones a los distintos servicios a través de estos dos puertos. **Plataforma de servicios** equivale a un VPS, a una raspi en tu casa, a un servidor dedicado, etc. etc. ===== auto-discovery de servicios ===== Normalmente los //edge_router// o proxy inversos necesitan un fichero de configuración detallando las reglas para enrutar las peticiones a los servicios. //Traefik// obtiene las reglas de los propios servicios. Así que en el momento de desplegar un nuevo servicio, un contenedor //Docker// en lo que respecta a este documento, especificaremos la información que ese servicio debe dar a //Traefik// para establecer los enrutamientos. De esta forma cuando se despliega un servicio, //Traefik// lo detecta y actualiza las reglas en tiempo real. De la misma forma, si el servicio se cae o se para, //Traefik// elimina las rutas asociadas en tiempo real. Para descubrir los servicios //Traefik// usa el API de la plataforma que los alberga, en el lenguaje propio de //Traefik// usa un **//Provider//**. Como nosotros vamos a usar //Traefik// en //Docker// vamos a centrarnos en el //docker provider//. ===== Ejemplo01: Uno facilito ===== El siguiente fichero //docker-compose// contiene una configuración lo más sencilla posible con el **//docker provider//**. Preparamos los ficheros y directorios de trabajo: mkdir -p ~/work/docker/ejemplo_01/traefik touch ~/work/docker/ejemplo_01/traefik/docker-compose.yml El contenido del fichero ''%%~/work/docker/ejemplo01/traefik/docker-compose.yml%%'' sería: version: '3' services: reverse-proxy: # The official v2 Traefik docker image image: traefik # Enables the web UI and tells Traefik to listen to docker command: --api.insecure=true --providers.docker ports: # The HTTP port - "80:80" # The Web UI (enabled by --api.insecure=true) - "8080:8080" volumes: # So that Traefik can listen to the Docker events - /var/run/docker.sock:/var/run/docker.sock Este fichero //docker-compose//: * Define el servicio //reverse-proxy// * Basado en la imagen ''%%traefik:latest%%'' * pasamos las opciones ''%%--api.insecure=true%%'' y ''%%--providers.docker%%'' al comando de arranque de la imagen //Traefik// * Conectamos los puertos 80 y 8080 del contenedor al exterior (a la red host) * Definimos un //bind mount// para que el socket de comunicación con //Docker// esté disponible para el contenedor. Lo necesita para que //Traefik// pueda conectar el //provider docker// al API de //Docker// en la máquina //host// ¡Cuidado! la opción ''%%api.insecure%%'' **no debe** usarse nunca en producción. Con esta opción estamos dejando el acceso al API de nuestro //Traefik// abierto y sin protección. Más adelante veremos opciones para configurar de forma segura el acceso al API. En cuanto lanzemos nuestro //docker-compose//: ''%%docker-compose --verbose up -d%%'' Podremos acceder al //dashboard// de //Traefik// visitando http://my_server_ip:8080, donde ''%%my_server_ip%%'' será la dirección del server donde has lanzado el contenedor o ''%%localhost%%'' si estás haciendo las pruebas en tu ordenador personal. Si estás lanzando el contenedor en un servidor verás que el propio //Docker// se encarga de abrir el //firewall// añadiendo las rutas necesarias a las //iptables//. Puedes comprobar las reglas iptables para el puerto 8080 con el comando: ''%%iptables-save | grep 8080%%'' [[https://docs.docker.com/network/iptables/|Más info]] Con este //Traefik// básico que hemos lanzado, ya prodríamos lanzar servicios que conectaran con el mismo. Vamos a añadir unas lineas a nuestro fichero //docker-compose//, que quedaría así: version: '3' services: reverse-proxy: # The official v2 Traefik docker image image: traefik # Enables the web UI and tells Traefik to listen to docker command: --api.insecure=true --providers.docker ports: # The HTTP port - "80:80" # The Web UI (enabled by --api.insecure=true) - "8080:8080" volumes: # So that Traefik can listen to the Docker events - /var/run/docker.sock:/var/run/docker.sock # ... whoami: # A container that exposes an API to show its IP address image: traefik/whoami labels: - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" Hemos incluido un nuevo servicio //whoami// en nuestro fichero ''%%docker-compose.yml%%''. Es un servicio bastante chorras que nos facilita la gente de //Traefik// para hacer pruebas. Simplemente responde peticiones con algunos datos del servidor ''%%whoami%%'' como la dirección IP o el nombre de la máquina. En este caso responderá con datos de la “máquina virtual”, es decir el contenedor ''%%whoami%%'' dentro de //Docker//. Si levantamos el nuevo servicio con ''%%docker-compose up -d whoami%%'' podremos consultar el dashboard de //Traefik// http://my_server_ip:8080 o directamente el //raw api// http://my_server_ip:8080/api/rawdata y comprobaremos que //Traefik// ha detectado el nuevo servicio ''%%whoami%%'' y ha configurado la ruta. Si queremos comprobarlo en la práctica podemos ejecutar una //request// con //curl//, con el comando: ''%%curl -H Host:whoami.docker.localhost http://my_server_ip%%'' curl -H Host:whoami.docker.localhost http://my_server_ip Hostname: 0e1584c3151f IP: 127.0.0.1 IP: 172.19.0.3 RemoteAddr: 172.19.0.2:55008 GET / HTTP/1.1 Host: whoami.docker.localhost User-Agent: curl/7.68.0 Accept: */* Accept-Encoding: gzip X-Forwarded-For: 192.168.0.154 X-Forwarded-Host: whoami.docker.localhost X-Forwarded-Port: 80 X-Forwarded-Proto: http X-Forwarded-Server: 3b4d7b2f09be X-Real-Ip: 192.168.0.154 **¿Qué esta pasando aquí?** //Traefik// abre dos //entry points// uno atiende peticiones en el puerto 80 y otro en el 8080 (para acceder al //dashboard//). Cuando lanzamos nuestra petición con ''%%curl%%'' //Traefik// la recibe por el puerto 80, comprueba que hay un //router// que encaja con la cabecera (''%%whoami.docker.localhost%%'') y, de acuerdo con esa regla, le pasa la petición al contenedor del servicio ''%%whoami%%''. Este //router// se configuró automáticamente gracias a la etiqueta ''%%traefik.http.routers.whoami.rule=Host('whoami.docker.localhost')%%'' que asociamos al contenedor ''%%whoami%%'' en el fichero ''%%docker-compose.yml%%''. Estas etiquetas son procesadas a través del //provider// Docker para reconfigurar //Traefik// en tiempo real. El servicio ''%%whoami%%'' atiende la petición y devuelve la respuesta al usuario. Yo estoy probando //Traefik// en un servidor en mi intranet, en mi caso un mini-pc, en tu caso podría ser una raspi, o podrías estar haciendo las pruebas en un vps. O también podrías estar haciendo las pruebas en tu propio ordenador. Si estás haciendo las pruebas en tu propio ordenador, tienes que usar ''%%localhost%%'' o ''%%127.0.0.1%%'' en todos los sitios donde he dicho ''%%my_server_ip%%'', si estás usando un servidor de cualquier tipo, pues la ip correspondiente. El caso es que hemos declarado la ruta para //Traefik// como si estuvieramos en nuestro ordenador (//localhost//), así que para hacer la prueba rápida con //curl//, especificamos la cabecera de nuestra request como ''%%whoami.docker.localhost%%'' aunque después **encaminamos** la //request// a nuestro servidor con la URL ''%%http://my_server_ip%%'' Con esta configuración tan sencilla podemos ver otra funcionalidad muy importante de //Traefik//, si lanzamos un segundo servidor whoami (un segundo contenedor) con el comando: ''%%docker-compose up -d --scale whoami=2%%'' //Traefik// se encargará de hacer **reparto de carga** entre los dos servidores. Si lanzamos el comando //curl// anterior, veremos como nos van respondiendo los dos contenedores. Si quieres parar los contenedores ejecuta ''%%docker-compose down%%'' ===== Conceptos centrales en Traefik ===== ==== Providers ==== Son los agentes capaces de cambiar la configuración dinámica de //Traefik// hay varios tipos: //file//, //Kubernetes//, //Docker// etc. etc. En nuestro caso nos vamos a centrar en el //docker provider// **Los //providers// son parte de la configuración estática de //Traefik//.** ==== Configuraciones de Traefik ==== En //Traefik// distinguimos entre dos configuraciónes: * //static configuration// La configuración de arranque de //Traefik//, aquí se definen los _**Providers** (//Docker// en nuestro ejemplo anterior) y los **//entry points//** (los puertos 80 y 8080 en nuestro ejemplo anterior). Es decir, elementos que no cambian muy a menudo a lo largo de la vida del //edge router// \\ //Traefik// lee su configuración estática de tres posibles orígenes (mutuamente exclusivos): - [[https://doc.traefik.io/traefik/reference/static-configuration/file/|Fichero de configuración]] - [[https://doc.traefik.io/traefik/reference/static-configuration/cli/|Parámetros por linea de comandos]] - [[https://doc.traefik.io/traefik/reference/static-configuration/env/|En variables de entorno]] * //dynamic configuration// La configuración dinámica del enrutado. Comprende toda la definición del procesado de las //request// recibidas por el router. Esta configuración puede cambiar y se recarga en caliente sin parar el router y sin provocar pérdidas de conexión o interrupción del servicio. //Traefik// obtiene su configuración dinámica a través de los **Providers**. En el ejemplo anterior //Docker// informa a //Traefik// de las rutas que necesita cada servicio gracias a las etiquetas ([[https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file|Labels]]) que añadimos en la configuración del servicio (ver [[https://doc.traefik.io/traefik/providers/docker/|referencia]]). Es un caso de configuración dinámica de //Traefik// a través del //Docker Provider//. ==== Entry Point ==== Son los componentes principales del //“frontend”// de nuestro //edge router//. Básicamente son los puertos de entrada donde se reciben las peticiones de servicio del “exterior”. **Los //entry point// son parte de la configuración estática.** //Traefik// puede atender peticiones //HTTP// o peticiones //TCP// por qué puede actuar como un //router// de nivel 7 (//HTTP//) o de nivel 4 (//TCP//). ==== Services ==== Serían los componentes del //“backend”//, en nuestro caso serán servicios implementados en contenedores Docker. **Los //services// son parte de la configuración dinámica.** ==== Routers ==== Son las reglas para pasar peticiones desde el //“frontend”// al //“backend”//. Se implementan y reconfiguran por la información recibida por los //providers//. **Los //routers// son parte de la configuración dinámica.** ==== Middlewares ==== Pueden hacer cambios en las peticiones o en las respuestas a peticiones. Hay multitud de //middlewares// y además se pueden encadenar para acumular cambios. **Los //middlewares// son parte de la configuración dinámica.** ===== Ejemplo02: Certificados SSL con Let’s Encrypt ===== Vamos con un ejemplo un pelín más complejo. Vamos a definir un contenedor de //Traefik// que simplemente expone su //Dashboard//, de momento no vamos a definir más contenedores, pero podremos añadirlos a este escenario en el futuro. La gracia del ejemplo es que //Traefik// obtendrá los certificados SSL del dominio (p.ej. miDominio.com) automáticamente desde //[[https://letsencrypt.org/|Let’s Encrypt]]//. Y expondrá el //dashboard// en un subdominio, digamos en https://dashtraefik.miDominio.com. Preparamos los directorios y los ficheros de trabajo. mkdir -p ~/work/docker/ejemplo_02/traefik touch ~/work/docker/ejemplo_02/traefik/{traefik.yml,traefik_dynamic.yml,acme.json} chmod 600 ~/work/docker/ejemplo_02/traefik/acme.json * ''%%traefik.yml%%'' será el fichero de configuración estática de //Traefik//. * ''%%traefik_dynamic.yml%%'' será el fichero de configuración dinámica de //Traefik// * ''%%acme.json%%'' es el fichero que almacenará los certificados HTTPS ==== Configuración estática de Traefik ==== Primero vamos a definir la configuración estática de nuestro //Traefik//, es imprescindible tener una. La almacenaremos en el fichero ''%%traefik.yml%%'' y más adelante tendremos que hacer que el contenedor //Traefik// pueda acceder a esa configuración (con un //bind mount// por ejemplo) Dentro de esta configuración definiremos: * **//entry points//** Definiremos dos //entry points//: ''%%http%%'' y ''%%https%%'' que aceptarán peticiones en los puertos 80 y 443. \\ Los nombres de los //entry points// son etiquetas arbitrarias, puedes llamarlos como quieras. entryPoints: http: address: :80 https: address: :443 La redirección de puertos se solía hacer con un //middleware// pero desde la version 2.2 de //Traefik// podemos definirla en la configuración estática añadiendo unas lineas en la propia definición de los //entry points//: entryPoints: http: address: :80 http: redirections: entrypoint: to: https https: address: :443 * **//providers//** \\ Vamos a definir dos _providers_ diferentes, uno de tipo ''docker'' y otro de tipo ''file''. \\ Nuestro objetivo es usar _Traefik_ en Docker así que siempre deberíamos definir el _provider_ docker. Recordemos que a través de este //provider// los contenedores (servicios) que definamos en nuestro //Docker// informarán a //Traefik// de que rutas necesitan para recibir peticiones. entryPoints: http: address: :80 http: redirections: entrypoint: to: https https: address: :443 providers: docker: endpoint: unix:///var/run/docker.sock exposedByDefault: false file: filename: traefik_dynamic.yml El //docker provider// de //Traefik// se comunica con //Docker// (en la máquina //host// ) a traves del //socket// de //Docker// (en el sistema de ficheros de la máquina _host). Así que necesitaremos que el contenedor //Docker// pueda acceder al //socket// con otro //bind mount// El parámetro ''%%exposedByDefault%%'' por defecto vale ''%%true%%'' y hace que todos los contenedores en //Docker// sean visibles para //Traefik//. El valor recomendado es justo el contrario: ''%%false%%'', para que //Traefik// “vea” únicamente los contenedores que tiene que tiene que enrutar. En este ejemplo concreto el ''%%docker provider%%'' no hará nada. Pero como estamos viendo como usar //Traefik// con //Docker// es mejor acostumbrarse a definirlo siempre. En cuanto definamos algún contenedor será imprescindible. Además del //provider docker// definimos un segundo //provider// de tipo //file//. Este segundo //provider// nos permitirá especificar la configuración dinámica mediante un fichero. En el futuro la configuración dinámica podría ampliarse a través del //docker provider// si definimos nuevos contenedores. Podemos definir un //provider file// especificando un ''%%dirname%%'' en lugar de un ''%%filename%%''. En ese caso podríamos tener varios ficheros en el directorio especificado para añadir modificaciones a la configuración dinámica de //Traefik// * **//certificates resolvers//** Se encargará de negociar los certificados SSL con //Let’s Encrypt// ¡automáticamente! \\ **Los //certificates resolvers// son parte de la configuración estática.** \\ Hay [[https://doc.traefik.io/traefik/v2.0/https/acme/|varias maneras]] de que //Traefik// obtenga los certificados, en este ejemplo usamos una muy simple, el //http challenge//. entryPoints: http: address: :80 http: redirections: entrypoint: to: https https: address: :443 providers: docker: endpoint: unix:///var/run/docker.sock exposedByDefault: false file: filename: traefik_dynamic.yml certificatesResolvers: myresolver: acme: email: storage: acme.json httpChallenge: entryPoint: http Las lineas añadidas al fichero ''%%traefik.yml%%'' definen un //certificates resolver// llamado ''%%myresolver%%'' (es una etiqueta, puedes llamarlo como quieras). Tenemos que especificar un correo electrónico (sólo a efectos de notificaciones) y el fichero (o la ruta) donde //Traefik// almacenará los certificados obtenidos. Además para el //httpChallenge// tenemos que usar obligatoriamente el puerto 80, así que especificamos ''%%entryPoint: http%%'' que previamente asociamos a ese puerto. //Traefik// no va a pedir certificados de dominio por iniciativa propia, tendremos que indicar, en la configuración dinámica, que servicios queremos asegurar con un certificado (en principio todos) También tenemos que acordarnos de que más adelante tendremos que crear un //bind mount// para tener fácil acceso al fichero ''%%acme.json%%''. El agente ACME de //Traefik// genera una pareja de claves pública-privada para hablar con la autoridad certificadora (CA) de //Let’s Encrypt//. Después “pregunta” a la CA que debe hacer para probar que controla un dominio determinado. La CA le planteará un conjunto de posibles “desafíos”. Puede pedirle por ejemplo, que cree un subdominio determinado en el dominio reclamado, o servir un contenido arbitrario en una dirección HTTP del dominio. Junto con los desafíos la CA envia un //nonce// (un número de un sólo uso) Si el agente ACME consigue completar alguno de los desafíos planteados. Informa a la CA y le devuelve el //nonce// firmado con su clave. La CA comprueba la solución del desafío y que la firma del //nonce// son válidas. A partir de este punto la pareja de claves asociada al dominio se considera autorizada, y el agente ACME ya puede solicitar certificados y/o revocarlos usando esta pareja de claves. * **//dashboard//** Activamos el //dashboard//. Necesitamos el //dashboard// activo. Más adelante cuando definamos la configuración dinámica indicaremos en que URL podemos acceder al //dashboard// y le añadiremos seguridad para que no sea público. entryPoints: http: address: :80 http: redirections: entrypoint: to: https https: address: :443 providers: docker: endpoint: unix:///var/run/docker.sock exposedByDefault: false file: filename: traefik_dynamic.yml certificatesResolvers: myresolver: acme: email: storage: acme.json httpChallenge: entryPoint: http api: dashboard: true Con esto tendríamos completa la configuración estática de nuestro //Traefik//. Podríamos incluso arrancar nuestro contenedor //Traefik// en //Docker//, teniendo cuidado de mapear los puertos 80 y 443 al host y estableciendo los //bind mounts// necesarios para que el contenedor lea el fichero de configuración estática y el almacen de claves ''%%acme.json%%''. Por ejemplo podríamos ejecutar: docker run -d \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $PWD/traefik.yml:/traefik.yml \ -v $PWD/acme.json:/acme.json \ -p 80:80 \ -p 443:443 \ --name traefik \ traefik:v2.4.0 Claro que la configuración dinámica estaría vacía, así que nuestro //Traefik// sería bastante tonto. Expondría los puertos 80 y 443, pero no sabría que hacer con las peticiones que le llegaran a esos puertos. Además, aunque levantaría el //dashboard// y su acceso en el puerto 8080 del contenedor, este no sería accesible. Por un lado no hemos añadido la opción ''%%insecure: true%%'' y por otro no hemos establecido usuario y password así que no hay manera de consultar el ''%%dashboard%%'' (todavía) ==== Configuración dinámica de Traefik ==== Vamos a completar la configuración de nuestro //edge router// definiendo la parte dinámica de la misma. La vamos a definir en un fichero, pero igual que la estática no hay por que hacerlo así. Ya hemos dicho que nuestro router básico solo expondrá el //dashboard//, así que tenemos que definir la capa de seguridad de acceso al //dashboard// y una ruta apuntando al servicio. Para usar el //middleware// [[https://doc.traefik.io/traefik/middlewares/basicauth/|Basic Auth]], necesitamos declarar parejas de usurio:password. Las contraseñas deben ser //hash// MD5, SHA1 o BCrypt. Para generar las credenciales, es decir la pareja de usuario y password “//hasheada//” podemos usar distintas herramientas. Con ''%%htpasswd%%'': sudo apt install apache-utils echo $(htpasswd -nb ) Con ''%%openssl%%'': openssl passwd -apr1 Si ejecutas varias veces el comando ''%%openssl%%'' con la misma contraseña los resultados serán **distintos**. Esto es normal, en cada ejecución se cambia el ''%%salt%%'' para que funcione así. Esto impide, por ejemplo, que un atacante detecte si estas reutilizando contraseñas. Si te fijas en la salida tiene tres campos delimitados por ''%%$$%%'' El primer campo es ''%%apr1%%'' el tipo de //hash// que estamos usando. El segundo campo es el ''%%salt%%'' (semilla) utilizado para generar el //hash// El tercer campo es el //hash// propiamente dicho Por otro lado veréis que en muchos sitios de internet indican que hay que duplicar los símbolos ‘$’ para escaparlos, tipicamente usando algún filtro //sed// como ''%%| sed -E "s:[\$]:\$\$:g"%%'' pero **solo aplica** cuando usamos //labels// para configurar los servicios, **no en nuestro caso** ya que usamos un fichero la configuración dinámica. Una vez que tenemos nuestra contraseña super segura en formato //hash// podemos añadir las siguientes lineas a nuestra configuración dinámica, para definir un //middleware// que llamaremos ''%%myauth%%'': # Declaring the user list http: middlewares: myauth: basicAuth: users: - "user1:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" - "user2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" Una vez definido nuestro //middleware// de tipo //basicAuth// solo nos falta definir el //[[https://doc.traefik.io/traefik/routing/routers/|router]]// para nuestro //dashboard//. Lo llamaremos ''%%mydash%%'': http: middlewares: myauth: basicAuth: users: - "user1:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" - "user2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" routers: mydash: rule: Host(`dashtraefik.miDominio.com`) middlewares: - myauth service: api@internal # This is the defined name for api. You cannot change it. tls: certresolver: myresolver Con esto completamos la configuración de nuestro //Traefik//, antes de poder probar tendríamos que asegurarnos de apuntar el nombre ''%%dashtraefik.miDominio.com%%'' a la dirección IP correcta en nuestro gestor del dominio. Si ya tenemos el DNS correctamente configurado podríamos lanzar la prueba con: docker run -d \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $PWD/traefik.yml:/traefik.yml \ -v $PWD/traefik_dynamic.yml:/traefik_dynamic.yml \ -v $PWD/acme.json:/acme.json \ -p 80:80 \ -p 443:443 \ --name traefik \ traefik:v2.4.0 Si no hay errores en nuestro DNS ni en la configuración del //Traefik// comprobaremos que podemos acceder tanto a http://dashtraefik.miDominio.com como https://dashtraefik.miDominio.com (el primero estará redirigido al segundo) Veremos también que //Traefik// ha negociado con //Let’s Encrypt// para obtener los certificados SSL para nuestro dominio. Prueba a hacer un ''%%cat acme.json%%'' en el servidor para ver el contenido del fichero. En caso de problemas, probablemente el primer paso sea comprobar los logs del contenedor //Traefik// con ''%%docker logs traefik%%'' ===== Ejemplo03: Una configuración sencilla para empezar con Traefik en Producción ===== Vamos a ver una configuración sencilla que nos sirva de base para empezar en producción. Posiblemente quieras crear un usuario específico para mantener el //docker// en tu servidor. sudo adduser dockadmin gpasswd -a dockadmin sudo gpasswd -a dockadmin docker Preparamos los directorios y ficheros de trabajo: mkdir -p ~/work/docker/ejemplo_03/{portainer,traefik/{configurations,letsencrypt}} touch ~/work/docker/ejemplo_03/{docker-compose.yml,traefik/{traefik.yml,letsencrypt/acme.json,configurations/middlewares.yml}} chmod 600 ~/work/docker/ejemplo03/traefik/letsencrypt/acme.json ==== Networks ==== Vamos a organizar nuestro //Docker// con dos //networks//: * **//frontend//** En la red //frontend// tendremos todos los contenedores que expongan un interfaz de usuario “al exterior”. Evidentemente nuestro contenedor //Traefik// tiene que estar en esta red. Otro ejemplo sería un servidor web. En realidad todos los contenedores que van implementar servicios a través de //Traefik// tienen que estar conectados a esta red. (Podríamos haberla llamado //traefik//) * **//backend//** Los contenedores que no exponen servicios al exterior solo tienen que estar en esta red. Por ejemplo un servidor de base de datos es muy posible que esté conectado exclusivamente a la red //backend//. Los contenedores de la red //frontend// pueden necesitar una conexión adicional con la red //backend//. Por ejemplo un contenedor //Wordpress// tiene que estar en la red //frontend// para publicar las páginas web a través de //Traefik// pero también en la red //backend// para comunicarse con el servidor de base de datos. {{< image src=“/images/traefik/traefik_networks.png” >}} Creamos las redes en //Docker// con: docker network create --subnet 172.20.0.0/16 backend docker network create --subnet 172.21.0.0/16 frontend Los rangos de direcciones IP son arbitrarios, igual que los nombres (//frontend// y //backend//) de las redes. Podríamos usar otros cualquiera. **Ojo** en //Traefik V1// los términos //frontend// y //backend// tienen un significado especial. Nada que ver con las redes definidas en el ejemplo03. Cuando consultes información en internet **asegúrate** de que referencian la versión actual de //Traefik// (2.4 cuando escribo esto). Si hablan mucho de definir el //frontend// y el //backend// es muy posible que estén hablando de //Traefik V1// ==== Traefik ==== Vamos a especificar la configuración de //Traefik//: * Tendremos la configuración estática en el fichero ''%%traefik.yml%%'' * Definiremos un directorio ''%%configurations%%'' mediante un //provider file//, todos los ficheros que pongamos en ese directorio se cargarán como parte de la configuración dinámica. Cualquier modificación en los ficheros o cualquier nuevo fichero se cargará “al vuelo”, sin necesidad de reiniciar //Traefik//. === middlewares comunes === En el fichero ''%%/configurations/middlewares.yml%%'' vamos a definir //middlewares// que usaremos con muchos (o todos) nuestros contenedores. Ya sabemos que en //Traefik// podemos definir la configuración dinámica de muchas formas, podríamos usar //labels// asociadas al contenedor //Traefik// para definir estos //middlewares//, pero creo que así queda más ordenado. Definimos los //middlewares//: * **//secureHeaders//**\\ Que define una serie de cabeceras que vamos a aplicar a todas las conexiones HTTP de nuestro //Traefik//. * **//frameDeny: true//**\\ Para evitar ataques //[[https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#click-jacking|click-jacking]]// * **//sslRedirect: true//**\\ Para permitir solo peticiones //https// * **//browserXssFilter//**\\ Para paliar ataques de //[[https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss|cross site scripting]]// * **//contentTypeNosniff: true//**\\ Ver [[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options|referencia]] * **//forceSTSHeader: true//**:\\ fuerza cabeceras STS para todas las conexiones, con el flag //preload// y la directiva //includeSubdomains// y un //max-age// de un año. * **//local-whitelist//**\\ Los servicios a los que apliquemos este //middleware// sólo se podrán acceder desde IP en la //whitelist//. Este //middleware// es interesante si accedes a los servicios desde la intranet. * **//user-auth//**\\ Para definir credenciales de acceso a nuestros servicios con //basicAuth//. //Traefik// nos pedirá usuario y contraseña para los servicios a los que apliquemos este //middleware//. * **//localsec//**\\ Este es un ejemplo de “cadena de //middlewares//”, aplicar este //middleware// equivale a aplicar //secureHeaders// y //local-whitelist//. **//chain//** es muy interesante para organizar nuestros //middlewares// y aplicar la misma configuración a varios contenedores. Contenido del fichero ''%%middlewares.yml%%'': http: middlewares: secureHeaders: headers: frameDeny: true sslRedirect: true forceSTSHeader: true stsIncludeSubdomains: true stsPreload: true stsSeconds: 31536000 local-whitelist: ipWhiteList: sourceRange: - "10.0.0.0/24" - "192.168.0.0/16" - "172.0.0.0/8" user-auth: basicAuth: users: - "user1:$apr1$MTqfVwiE$FKkzT5ERGFqwH9f3uipxA1" - "user2:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" - "user3:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" localsec: chain: middlewares: - local-whitelist - secureHeaders === Configuración estática === En el fichero ''%%traefik.yml%%'' definimos la configuración estática de //Traefik//: * Activamos el //dashboard// * Definimos los //entry points// * ''%%htpp%%'' redirigido a ''%%https%%'' * ''%%https%%'' al que aplicamos los //middleware// con las cabeceras de seguridad y el //certresolver// apuntando a //Let’s Encrypt// para generar certificados SSL * Definimos los //providers// * ''%%docker%%'' imprescindible claro * ''%%file%%'' apuntando al directorio ''%%/configurations%%'' para cargar ficheros que amplien la configuración dinámica * Definimos el //certresolver// para obtener los certificados SSL automáticamente desde //Let’s Encrypt// El contenido del fichero ''%%traefik.yml%%'' nos quedaría: # traefik.yml api: dashboard: true entryPoints: http: address: :80 http: redirections: entryPoint: to: https https: address: :443 http: middlewares: - secureHeaders@file tls: certResolver: letsencrypt providers: docker: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false file: directory: /configurations certificatesResolvers: letsencrypt: acme: email: youruser@mailprovider.com storage: acme.json httpChallenge: entryPoint: http ==== docker-compose.yml ==== Ahora que ya tenemos listos los ficheros de configuración de //Traefik// vamos a definir el fichero ''%%docker-compose.yml%%'': * Definimos siempre la versión de nuestras imágenes //Docker// explicitamente. Es muy mala idea usar ''%%:latest%%'' en producción, hay que saber que versión usamos y hacer las actualizaciones con conocimiento de causa. * Definimos un nombre (''%%traefik%%'') para nuestro contenedor, y definimos una política para ''%%restarts%%'' * Establecemos la opción ''%%security_opt: no-new-privileges%%'' para impedir escaladas de privilegios con ''%%setuid%%'' * Definimos los //volumes// del contenedor //Traefik// * Mapeamos ''%%/etc/localtime%%'' para que el contenedor sincronice la hora con el //host// * Mapeamos el //socket// de //Docker//, imprescindible para el //provider docker// * Mapeamos el fichero de configuración estática ''%%traefik,yml%%'', el directorio de configuraciones dinámicas ''%%/configurations%%'' y el fichero ''%%acme.json%%'' para el //certresolver// * Añadimos etiquetas (//labels//) para la configuración dinámica del contenedor //Traefik// (concretamente del //dashboard//): * El servicio (se refiere al //dashboard//) se proveerá a través de //Traefik// * El contenedor se conecta a la red //frontend// * Se configura el router ''%%traefik-secure%%'' * Para el servicio //Traefik dashboard// (''%%api@internal%%'') * Con //middleware// ''%%user-auth%%'' * Con la regla ''%%Host(`dashtraefik.yourdomain.com`)%%'' * Por último declaramos la red (//network//) //frontend// como ''%%external%%'' ya que la hemos creado previamente. version: '3' services: traefik: image: traefik:v2.4 container_name: traefik restart: unless-stopped security_opt: - no-new-privileges:true networks: - frontend ports: - 80:80 - 443:443 volumes: - /etc/localtime:/etc/localtime:ro - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik/traefik.yml:/traefik.yml:ro - ./traefik/configurations:/configurations:ro - ./traefik/letsencrypt/acme.json:/acme.json labels: - "traefik.enable=true" - "traefik.docker.network=frontend" - "traefik.http.routers.traefik-secure.entrypoints=https" - "traefik.http.routers.traefik-secure.rule=Host(`dashtraefik.yourdomain.com`)" - "traefik.http.routers.traefik-secure.service=api@internal" - "traefik.http.routers.traefik-secure.middlewares=user-auth@file" networks: frontend: external: true Si ya tenemos nuestro DNS configurado para que el nombre ''%%dashtraefik.yourdomain.com%%'' se resuelva correctamente ya podemos arrancar nuestro //Traefik//: cd ~/work/docker/ejemplo_03 docker-compose --verbose up -d Ya tenemos nuestro contenedor //Traefik// funcionando correctamente. Estamos listos para añadir nuevos servicios a nuestra plataforma. ==== Nuestro primer servicio: Portainer ==== Vamos a definir nuestro primer servicio conectado por //Traefik// en el mismo fichero ''%%docker-compose.yml%%''. Añadimos la definición del nuevo servicio en las lineas que van de la 29 a la 47: version: '3' services: traefik: image: traefik:v2.4 container_name: traefik restart: unless-stopped security_opt: - no-new-privileges:true networks: - frontend ports: - 80:80 - 443:443 volumes: - /etc/localtime:/etc/localtime:ro - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik/traefik.yml:/traefik.yml:ro - ./traefik/configurations:/configurations:ro - ./traefik/letsencrypt/acme.json:/acme.json labels: - "traefik.enable=true" - "traefik.docker.network=frontend" - "traefik.http.routers.traefik-secure.entrypoints=https" - "traefik.http.routers.traefik-secure.rule=Host(`dashtraefik.yourdomain.com`)" - "traefik.http.routers.traefik-secure.service=api@internal" - "traefik.http.routers.traefik-secure.middlewares=user-auth@file" portainer: image: portainer/portainer-ce:2.0.1-alpine container_name: portainer restart: unless-stopped security_opt: - no-new-privileges:true networks: - frontend volumes: - /etc/localtime:/etc/localtime:ro - /var/run/docker.sock:/var/run/docker.sock:ro - ./portainer:/data labels: - "traefik.enable=true" - "traefik.docker.network=frontend" - "traefik.http.routers.portainer-secure.entrypoints=https" - "traefik.http.routers.portainer-secure.rule=Host(`portainer.yourdomain.com`)" - "traefik.http.routers.portainer-secure.service=portainer" - "traefik.http.services.portainer.loadbalancer.server.port=9000" networks: frontend: external: true Añadimos un nuevo servicio ''%%portainer%%'', con las opciones de rearranque y de seguridad iguales a las de //Traefik//. Mapeamos también el fichero ''%%/etc/localtime%%'' para que el contenedor se ponga en hora con el //host//; y el //socket// ''%%docker.sock%%'' que //Portainer// necesita (//Portainer// es un //frontend// para //Docker//) Añadimos también las etiquetas para informar a //Traefik// del nuevo servicio: * El servicio se llama //portainer// * Acepta peticiones en el puerto 9000 * //Portainer// usará el //entry point// ''%%https%%'' de //Traefik// * Y la regla de enrutado será ''%%Host(`portainer.yourdomain.com`)%%'' Una vez completada la configuración del DNS, podemos levantar nuestro nuevo servicio con: docker-compose up -d portainer //Portainer// implementa su propio sistema de gestión y autenticación de usuarios. Además da problemas si activamos el //middleware// ''%%user-auth%%'' **Tendrás que crear un usuario ''%%admin%%'' y protegerlo con contraseña tan pronto como lo arranques.**