You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1153 lines
39 KiB
1153 lines
39 KiB
9 months ago
|
---
|
||
|
weight: 4
|
||
|
title: "Apuntes de Traefik v2"
|
||
|
date: 2021-01-31T22:28:31+0100
|
||
|
draft: false
|
||
|
summary: "Apuntes de Traefik v2"
|
||
|
categories:
|
||
|
- notes
|
||
|
tags:
|
||
|
- docker
|
||
|
- traefik
|
||
|
---
|
||
|
|
||
|
Apuntes sobre Traefik v2.
|
||
|
|
||
|
<!--more-->
|
||
|
|
||
|
# Traefik
|
||
|
|
||
|
{{< admonition type=abstract title="Referencias" state=open >}}
|
||
|
|
||
|
Estas notas sobre _Traefik_ son un refrito de las siguientes fuentes:
|
||
|
- [Traefik - Quick Start](https://doc.traefik.io/traefik/getting-started/quick-start/)
|
||
|
- [Joshua Avalon Blog: Setup Traefik v2](https://joshuaavalon.io/setup-traefik-v2-step-by-step)
|
||
|
- [Digital Ocean: Howto use Traefik as reverse proxy for Docker](https://www.digitalocean.com/community/tutorials/how-to-use-traefik-v2-as-a-reverse-proxy-for-docker-containers-on-ubuntu-20-04)
|
||
|
- [Hash with openssl](https://blog.roberthallam.org/2020/05/generating-a-traefik-nginx-password-hash-without-htpasswd/)
|
||
|
- [Containeroo: Traefik 2.0 + Docker — a Simple Step by Step Guide](https://medium.com/@containeroo/traefik-2-0-docker-a-simple-step-by-step-guide-e0be0c17cfa5)
|
||
|
- [Containeroo: Traefik 2.0 + Docker — an Advanced Guide](https://medium.com/@containeroo/traefik-2-0-docker-an-advanced-guide-d098b9e9be96)
|
||
|
- [Containeroo: Traefik 2.2 + Docker: Entry Point Configuration](https://medium.com/@containeroo/traefik-2-2-docker-global-entrypoint-configuration-ff11d7f84913)
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
<!--
|
||
|
-
|
||
|
- https://doc.traefik.io/traefik/v2.0/user-guides/docker-compose/basic-example/
|
||
|
- [[https://doc.traefik.io/traefik/v2.0/][Documentación Oficial]]
|
||
|
- [Simplecto: un ejemplo simple con nginx y certificados](https://www.simplecto.com/use-traefik-with-nginx-apache-caddyserver-serve-static-files/)
|
||
|
- [IMPORTANTE: DNS challenge en OVH](https://medium.com/nephely/configure-traefik-for-the-dns-01-challenge-with-ovh-as-dns-provider-c737670c0434)
|
||
|
|
||
|
https://medium.com/@containeroo/traefik-2-0-route-external-services-through-traefik-7bf2d56b1057
|
||
|
|
||
|
https://www.simplecto.com/use-traefik-with-nginx-apache-caddyserver-serve-static-files/
|
||
|
|
||
|
-->
|
||
|
|
||
|
|
||
|
## _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.)
|
||
|
|
||
|
{{< admonition type=tip title="¿Para qué sirve?" state=open >}}
|
||
|
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.
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
## _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:
|
||
|
|
||
|
```bash
|
||
|
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:
|
||
|
|
||
|
```yml
|
||
|
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_
|
||
|
|
||
|
{{< admonition type=danger title="API Insecure" open=true >}}
|
||
|
|
||
|
¡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.
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
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.
|
||
|
|
||
|
{{< admonition type=tip title="firewall" open=true >}}
|
||
|
|
||
|
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`
|
||
|
|
||
|
[Más info](https://docs.docker.com/network/iptables/)
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
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í:
|
||
|
|
||
|
```yml
|
||
|
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`
|
||
|
|
||
|
```bash
|
||
|
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.
|
||
|
|
||
|
{{< admonition type=warning title="localhost vs my_server_ip" state=open >}}
|
||
|
|
||
|
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`
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
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):
|
||
|
1. [Fichero de configuración](https://doc.traefik.io/traefik/reference/static-configuration/file/)
|
||
|
2. [Parámetros por linea de comandos](https://doc.traefik.io/traefik/reference/static-configuration/cli/)
|
||
|
3. [En variables de entorno](https://doc.traefik.io/traefik/reference/static-configuration/env/)
|
||
|
|
||
|
___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
|
||
|
([_Labels_](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file))
|
||
|
que añadimos en la configuración (del servicio) (ver
|
||
|
[referencia](https://doc.traefik.io/traefik/providers/docker/)). Es
|
||
|
un caso de configuración dinámica de _Traefik_ a traves 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.**
|
||
|
|
||
|
{{< admonition type=tip title="Traefik requests" state=open >}}
|
||
|
|
||
|
_Traefik_ puede atender peticiones _HTTP_ o peticiones _TCP_ por qué
|
||
|
puede actuar como un _router_ de nivel 7 (_HTTP_) o de nivel 4
|
||
|
(_TCP_).
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
### _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 [_Let's
|
||
|
Encrypt_](https://letsencrypt.org/). Y expondrá el _dashboard_ en un
|
||
|
subdominio, digamos en <https://dashtraefik.miDominio.com>.
|
||
|
|
||
|
Preparamos los directorios y los ficheros de trabajo.
|
||
|
|
||
|
```bash
|
||
|
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.
|
||
|
|
||
|
```traefik.yml
|
||
|
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_:
|
||
|
|
||
|
```traefik.yml
|
||
|
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.
|
||
|
|
||
|
```traefik.yml
|
||
|
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.
|
||
|
|
||
|
{{< admonition type=warning >}}
|
||
|
|
||
|
En este ejemplo concreto el `docker provider` no hara nada. Pero
|
||
|
como estamos viendo como usar _Traefik_ con _Docker_ es mejor
|
||
|
acostumbrarse a definirlo siempre. En cuanto definamos algún
|
||
|
contenedor nos hará falta.
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
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.
|
||
|
|
||
|
{{< admonition type=info title="provider file: dirname vs filename" state=open >}}
|
||
|
|
||
|
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_
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
__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 [varias maneras](https://doc.traefik.io/traefik/v2.0/https/acme/)
|
||
|
de que _Traefik_ obtenga los certificados, en este ejemplo usamos
|
||
|
una muy simple, el _http challenge_.
|
||
|
|
||
|
```traefik.yml
|
||
|
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: <my_email@mailprovider.com>
|
||
|
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)
|
||
|
|
||
|
Tambien tenemos que acordarnos de que más adelante tendremos que
|
||
|
crear un _bind mount_ para tener fácil acceso al fichero
|
||
|
`acme.json`.
|
||
|
|
||
|
{{< admonition type=tip title="Protocolo ACME" state=open >}}
|
||
|
|
||
|
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.
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
___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.
|
||
|
|
||
|
```traefik.yml
|
||
|
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: <my_email@mailprovider.com>
|
||
|
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:
|
||
|
|
||
|
```bash
|
||
|
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_ [_Basic
|
||
|
Auth_](https://doc.traefik.io/traefik/middlewares/basicauth/),
|
||
|
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`:
|
||
|
|
||
|
```bash
|
||
|
sudo apt install apache-utils
|
||
|
echo $(htpasswd -nb <USER> <PASSWORD>)
|
||
|
```
|
||
|
|
||
|
Con `openssl`:
|
||
|
|
||
|
```bash
|
||
|
openssl passwd -apr1
|
||
|
```
|
||
|
|
||
|
{{< admonition type=info title="Hashes" state=open >}}
|
||
|
|
||
|
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.
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
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`:
|
||
|
|
||
|
```yaml
|
||
|
# 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
|
||
|
[_router_](https://doc.traefik.io/traefik/routing/routers/) para
|
||
|
nuestro _dashboard_. Lo llamaremos `mydash`:
|
||
|
|
||
|
```yaml
|
||
|
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:
|
||
|
|
||
|
```bash
|
||
|
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.
|
||
|
|
||
|
```bash
|
||
|
sudo adduser dockadmin
|
||
|
gpasswd -a dockadmin sudo
|
||
|
gpasswd -a dockadmin docker
|
||
|
```
|
||
|
|
||
|
Preparamos los directorios y ficheros de trabajo:
|
||
|
|
||
|
```bash
|
||
|
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:
|
||
|
|
||
|
```bash
|
||
|
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.
|
||
|
|
||
|
{{< admonition type=warning title="Traefik V1: frontend y backend" state=open >}}
|
||
|
|
||
|
**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_
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
### _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
|
||
|
[_click-jacking_](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#click-jacking)
|
||
|
* ___sslRedirect: true___ Para permitir solo peticiones _https_
|
||
|
* ___browserXssFilter___ Para paliar ataques de [_cross site
|
||
|
scripting_](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss)
|
||
|
* ___contentTypeNosniff: true___ Ver
|
||
|
[referencia](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options)
|
||
|
* ___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`:
|
||
|
|
||
|
```yaml
|
||
|
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:
|
||
|
|
||
|
```yaml
|
||
|
# 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.
|
||
|
|
||
|
|
||
|
```yaml
|
||
|
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_:
|
||
|
|
||
|
```bash
|
||
|
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:
|
||
|
|
||
|
```yaml
|
||
|
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:
|
||
|
|
||
|
```bash
|
||
|
docker-compose up -d portainer
|
||
|
```
|
||
|
|
||
|
{{< admonition type=warning title="Login en Portainer" state=open >}}
|
||
|
|
||
|
_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.**
|
||
|
|
||
|
{{< /admonition >}}
|