Traefik

From Leo's Notes
Last edited on 15 November 2022, at 18:39.

Traefik is an open source HTTP reverse proxy and load balancer written in golang. It can be integrated into various infrastructures such as Docker and Kubernetes or used as a standalone service. Traefik comes with lots of automation features such as dynamic configuration through container labels and automatic SSL certificate renewals using the ACME protocol.

Docker integration

Traefik can be used as the reverse proxy for all web-related services on a Docker host. The added benefit with this approach is that new web services that are added can be automatically configured through docker labels and SSL certificates are automatically obtained provided that the DNS names already resolve.

Extra care is required when using Traefik and Docker since Traefik needs to have access to the Docker socket in order to see container events for its automatic configuration. Since having access to the Docker socket is pretty much equivalent to owning the Docker host, from a security stand-point, it's best to have an intermediate Docker container proxy the Docker socket so that it can only be read from but not written to. More on this set up below.

Setting up Traefik with Docker Compose

Create a new 'traefik' Docker network. This network will be used by any other containers on the host that needs to be reverse proxied by Treafik.

# docker network create traefik

Next, we will set up the Traefik docker-compose stack. Traefik should be configured to work with Docker. We'll also restrict Traefik from exposing containers unless they are explicitly labeled. Review the Traefik Docker documentation for all the available Docker related options.

Included here is the socket-proxy which we'll use to restrict what Traefik can do to Docker in case it gets compromised.

version: '3.3'

services:
  traefik:
    image: traefik:latest
    restart: always
    command: --web --docker --docker.watch --docker.exposedbydefault=false --providers.docker.endpoint=tcp://socket-proxy:2375
    volumes:
      - ./config/traefik.yml:/traefik.yml
      - ./config:/config
      - /var/log/traefik:/var/log/traefik
    networks:
      - traefik
      - internal
    environment:
      - "TZ=MST7MDT,M3.2.0,M11.1.0"
    ports:
      - "80:80"
      - "443:443"

  # https://github.com/Tecnativa/docker-socket-proxy
  socket-proxy:
    build:
      context: ./socket-proxy
      dockerfile: Dockerfile
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      CONTAINERS: 1
    networks:
      - internal
    expose:
      - "2375"

networks:
  internal:
  traefik:
    name: traefik
    external: true

Bring the entire stack up and when everything comes up, you're done.

# docker-compose up -d

Using Traefik with your Docker containers

With the the Traefik container set up, we can now create containers to take advantage of Traefik as the ingress controller for the Docker host. This can be done through just container labels. Containers should be labeled with the following labels.

Description Label (example)
Enable Traefik for this container traefik.enable=true
Define the Docker network that Traefik needs to use to connect to this container traefik.docker.network=traefik
Define a new service to this container. For example, if your container is listening on port 8888, define a new Traefik service that listens on port 8888. traefik.http.services.my-new-service.loadbalancer.server.port=8888
Expose your container using the service created previously via the HTTPS entrypoint.

This will make Traefik forward HTTPS data matching your virtual host to this container.

SSL certificates are handled with Let's Encrypt.

traefik.http.routers.new-service-router-https.entrypoints=https

traefik.http.routers.new-service-router-https.rule=Host(`my-new-service.example.com`)

traefik.http.routers.new-service-router-https.service=gitlab-service

traefik.http.routers.new-service-router-https.tls=true

traefik.http.routers.new-service-router-https.tls.certresolver=letsencrypt

You can also expose your container via HTTP.

Be sure that your router name is distinct.

traefik.http.routers.new-service-router.entrypoints=http

traefik.http.routers.new-service-router.rule=Host(`my-new-service.example.com`)

traefik.http.routers.new-service-router.service=gitlab-service

If you want your HTTP traffic redirected to the HTTPS version, create a new middleware that redirects to the HTTPS scheme. traefik.http.routers.new-service-router.entrypoints=http

traefik.http.routers.new-service-router.rule=Host(`my-new-service.example.com`)

traefik.http.routers.new-service-router.middlewares=new-service-redirect

traefik.http.middlewares.new-service-redirect.redirectscheme.scheme=https

If your container exposes multiple services, you can define multiple services and routers in your labels. Just make sure things are named uniquely.

Here's the configuration for one of my test Wiki containers using Traefik.

wiki:
    image: mediawiki:1.37
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik
      - traefik.http.middlewares.wiki-https-redirect.redirectscheme.scheme=https
      # http
      - traefik.http.routers.wiki.entrypoints=http
      - traefik.http.routers.wiki.rule=Host(`wiki-test.example.com`)
      - traefik.http.routers.wiki.middlewares=wiki-https-redirect
      # https
      - traefik.http.routers.wiki-https.entrypoints=https
      - traefik.http.routers.wiki-https.rule=Host(`wiki-test.example.com`)
      - traefik.http.routers.wiki-https.tls=true
      - traefik.http.routers.wiki-https.tls.certresolver=letsencrypt
      - traefik.http.routers.wiki-https.service=wiki-https
      - traefik.http.services.wiki-https.loadbalancer.server.port=8080
    networks:
      - traefik
    expose:
      - "8080"
    restart: always

Standalone setup

Traefik can be used as a standalone service to reverse proxy and do SSL termination. The example below will set up a simple reverse proxy for a local web service listening on port 8000. The SSL certificate will automatically be obtained using Let's Encrypt.

Create the following traefik.yml configuration file at /etc/traefik/traefik.yml:

log:
  level: INFO
  filepath: "/var/log/traefik.log"

accessLog:
  filepath: "/var/log/access.log"
  fields:
    defaultMode: keep
    headers:
      defaultMode: keep

api:
  dashboard: false
  insecure: false
  debug: true

entryPoints:
  http:
    address: ":80"
  https:
    address: ":443"

providers:
  file:
    filename: "/etc/traefik/dynamic_config.yml"
    watch: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: my-email-address@example.com
      storage: "/etc/traefik/acme.json"
      httpChallenge:
        entryPoint: http

Create a dynamic_config.yml file at /etc/traefik/dynamic_config.yml. This file will define our routes and backend services. You must have a separate router for each of the insecure http and secure https routes to your backend service.

http:
  serversTransports:
    ignorecert:
      insecureSkipVerify: true

  routers:
    testing-http:
      rule: Host(`testing.example.com`)
      service: my-testing-service
      entrypoints:
        - http
		
    testing-https:
      rule: Host(`testing.example.com`)
      service: my-testing-service
      entrypoints:
        - https
      tls:
        certresolver: letsencrypt

  services:
    my-testing-service:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:8000"

Start traefik:

# traefik --web --config /etc/traefik/traefik.yml

Tasks

Add host header on requests to the backend

You can specify custom request headers by injecting a middleware. For example, when reverse proxying HTTP traffic for a website, you may wish to have the reverse proxy provide the 'Host' header. To do this, we can add the following dynamic config.

http:
  routers:
    public:
      rule: Host(`public.example.com`)
      service: public
      entrypoints:
        - http
      middlewares:
        - public

  services:
    public:
      loadBalancer:
        servers:
          - url: "http://10.1.2.200"

  middlewares:
    public:
      headers:
        customRequestHeaders:
          Host: "public.example.com"

Add a path prefix

To serve content under a specific path (eg. /downloads) using another container, create the service as you would normally but specify the router rule to include a PathPrefix. Traefik can also strip out this path prefix so that the underlying server sees only the root path (eg. have /downloads/file become /file) using the stripprefix middleware.

Here is an example docker-compose.yml definition:

services:
  downloads:
    build:
      context: ./downloads
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik
      # http
      - traefik.http.routers.downloads.entrypoints=http
      - 'traefik.http.routers.downloads.rule=(Host(`example.com`) && PathPrefix(`/downloads/`))'
      - traefik.http.routers.downloads.middlewares=downloads-https-redirect
      - traefik.http.middlewares.downloads-https-redirect.redirectscheme.scheme=https
      # https
      - traefik.http.routers.downloads-https.entrypoints=https
      - 'traefik.http.routers.downloads-https.rule=(Host(`example.com`) && PathPrefix(`/downloads/`))'
      - traefik.http.routers.downloads-https.tls=true
      - traefik.http.routers.downloads-https.tls.certresolver=letsencrypt
      - traefik.http.services.downloads-https.loadbalancer.server.port=8080
      - traefik.http.routers.downloads-https.service=downloads-https
      - traefik.http.routers.downloads-https.middlewares=downloads-strip-prefix
      - traefik.http.middlewares.downloads-strip-prefix.stripprefix.prefixes=/downloads
    networks:
      - traefik
    expose:
      - "8080"

Install a custom certificate

Installing a custom certificate and private key in Traefik is simple:

  1. Edit the traefik.conf and ensure that it has a dynamic configuration set.
    providers:
      file:
        filename: "/config/dynamic_config.yml"
        watch: true
    
  2. In the dynamic configuration file, specify the certificate and key files. For example, here is what I would place for a *.example.com wildcard certificate:
    tls:
      certificates:
        - certFile: /config/ssl/example.com.crt
          keyFile: /config/ssl/example.com.key
    
  3. Place the certificates in the /config/ssl directory.

Restart Traefik if you didn't previously have the dynamic config file defined in the traefik.conf previously. Traefik will automatically determine what domains it has certificates for and use it as required.