Linux things 🐧

un blog sur les technologies des logiciels libres et autres digressions


Tricot : un reverse proxy

migration de mon infra vers Tricot

Fri, 06 Dec 2024 19:20:30 +0200
# nomad   # consul   # tricot   # docker   # auto-hebergement  

Introduction

Je me baladais sur le site de l’association Deuxfleurs dont j’ai déjà parlé au sujet de leur serveur S3 Garage et je suis tombé cette page de leur guide qui présente les logiciels qu’ils ont développé.
A l’époque de ma découverte de Garage j’avais du faire l’impasse sur leurs autres outils mais ca été une “révélation” lorsque je suis tombé sur Tricot 😱

Je tiens à dire que ces hackers sont étonnants et leur association qui propose à leurs usagers d’être hébergé sur une infra auto-hébergés sur plusieurs sites mérite d’être largement plus connue => Comment rejoindre l’association.

Caddy

Si on fait un petit flashback j’utilisais Caddy et notamment caddy-docker-proxy (voir aussi Nomad et Caddy). Ce module Caddy fonctionne comme Traefik c’est à dire qu’il se connecte sur la socket de Docker pour auto découvrir les services et appliquer un reverse proxy. Je l’ai plus ou moins détourné sur mon infra Nomad en utilisant des conteneurs vide (qui font un sleep) afin de publier un label permettant à Caddy de faire un reverse vers des services déployés sur d’autres serveurs Docker.
Cette technique est inutile si on utilise un Docker swarm dans lequel les Dockers sont connectés, mais Docker swarm rocks plus vraiment :/

Bref cela marche plutôt bien mais Tricot permet de s’en passer ! En effet quel intérêt d’utiliser la socket de Docker alors qu’il suffirait d’utiliser Consul ?!

Tricot

Deuxfleurs a le bon goût d’utiliser Nomad et Consul et dans une telle infra les conteneurs peuvent enregistrer de l’information dans Consul via des tags comme je le faisais avec tags = ["allocport=${NOMAD_HOST_PORT_http}"].
Or si un reverse proxy pouvait lire directement la présence de ces tags dans Consul il pourrait automatiquement faire un reverse. Et c’est exactement ce que fait Tricot.
Il est d’ailleurs inspiré par le fonctionnement de Traefik comme on peut le voir sur le dépôt git : Tricot, a replacement for Traefik.

J’en ai eu marre de Traefik lors du passage à la version 2 qui a horriblement complexifié sa configuration. Caddy a été une libération par sa simplicité mais on va voir que Tricot est encore plus simple (plus besoin de conteneur qui sleep pour ajouter des labels) tout en étant aussi efficace, Rust n’ayant rien à envier à Go en terme de performance.

tricot.hcl

job "tricot" {
  datacenters = ["dc1"]
  type        = "service"

  group "proxy" {
    count = 1

    network {
      mode = "bridge"

      port "internal" {
        static       = 9334
        to           = 9334
        host_network = "tailscale"
      }

      port "http-public" {
        static       = 80
        to           = 80
        host_network = "public"
      }

      port "https-public" {
        static       = 443
        to           = 443
        host_network = "public"
      }
    }

    restart {
      attempts = 2
      interval = "2m"
      delay    = "30s"
      mode     = "fail"
    }

    task "tricot" {
      driver = "docker"

      constraint {
        attribute = "${attr.unique.hostname}"
        value     = "node1"
      }

      config {
        image = "fredix/tricot"

        volumes = [
          "secrets:/etc/tricot",
        ]
        ports = ["internal", "http-public", "https-public"]
      }

      resources {
        cpu    = 400
        memory = 1000
      }


      template {
        data        = <<EOH
        TRICOT_NODE_NAME={{ env "attr.unique.hostname" }}
        TRICOT_LETSENCRYPT_EMAIL=user@domain.tld
        TRICOT_ENABLE_COMPRESSION=true
        TRICOT_CONSUL_HOST=http://IP_TAILSCALE_SERVER_CONSUL:8500
        TRICOT_CONSUL_TLS_SKIP_VERIFY=true
        TRICOT_HTTP_BIND_ADDR=[::]:80
        TRICOT_HTTPS_BIND_ADDR=[::]:443
        TRICOT_METRICS_BIND_ADDR=[::]:9334
        RUST_LOG=tricot=info
        EOH
        destination = "secrets/env"
        env         = true
      }

      service {
        name     = "tricot-http"
        provider = "consul"
        port     = "http-public"
      }

      service {
        name     = "tricot-https"
        provider = "consul"
        port     = "https-public"
      }

      service {
        name     = "tricot-metrics"
        provider = "consul"
        port     = "internal"
      }



    }
  }
}

Sur mon infra j’utilise Tailscale, j’ai donc besoin que Tricot ait une patte sur l’interface public et celle de Tailscale pour se connecter aux backends (voir la configuration de Nomad).

L’autre partie importante est la variable TRICOT_CONSUL_HOST qui contient l’IP Tailscale du serveur Consul (sur Node3). Tricot va alors stocker dedans les certificats générés mais aussi lire les tags qui le concerne (qui commencent par tricot).

Comme exemple voici le hcl de Navidrome (Navidrome et l’autohébergement hybride V2)

navidrome.hcl

job "navidrome" {
  datacenters = ["dc1"]
  type        = "service"
  group "home" {
    count = 1

    network {
      port "http" {
        to = 4533 # container port the app runs on
        host_network = "tailscale"
      }
    }

    task "navidrome" {
      driver = "docker"

      constraint {
        attribute = "${attr.unique.hostname}"
        value     = "nuc"
      }

      env {
        ND_SCANSCHEDULE   = "1h"
        ND_LOGLEVEL       = "info"
        ND_SESSIONTIMEOUT = "24h"
        ND_BASEURL        = ""
      }

      config {
        image = "deluan/navidrome:latest"
        volumes = [
          "/data/volumes/navidrome/:/data",
          "/data/musiques:/music"
        ]
        ports = [
          "http"
        ]
      }

      resources {
        cpu    = 200
        memory = 500
      }

      service {
        name     = "navidrome"
        provider = "consul"
        port     = "http"

        tags = ["tricot navidrome.domain.tld"]
      }
    }

  }
}

La seule différence avec la version pour Caddy est le tag tags = ["tricot navidrome.domain.tld"]. Cette présence dans Consul suffit à Tricot d’automatiquement faire un reverse en utilisant les autres données stockées par le service dans Consul comme l’IP et le port.
Tricot génère alors un certificat Let’s Encrypt et le stock dans Consul.

Terminé, finito 😎 🎉

Load balancing

Si on souhaite équilibrer la charge entre 4 conteneurs Docker, voici un exemple avec un simple whoami :

whoami.hcl

job "whoami" {
  datacenters = ["dc1"]
  type        = "service"
  group "home" {
    count = 4

    # Add an update stanza to enable rolling updates of the service
    update {
      max_parallel     = 2
      min_healthy_time = "30s"
      healthy_deadline = "5m"

      # Enable automatically reverting to the last stable job on a failed
      # deployment.
      auto_revert = true
    }

    network {
      port "http" {
        to           = 80 # container port the app runs on
        host_network = "tailscale"
      }
    }

    task "whoami" {
      driver = "docker"

      constraint {
        attribute = "${attr.unique.hostname}"
        value     = "nuc"
      }

      config {
        image = "containous/whoami"

        ports = [
          "http"
        ]
      }

      resources {
        cpu    = 30
        memory = 16
      }

      service {
        name     = "whoami"
        provider = "consul"
        port     = "http"

        tags = [
          "tricot whoami.fredix.xyz",
          "tricot-site-lb",
        ]

        check {
          type     = "http"
          name     = "app_health"
          path     = "/"
          interval = "20s"
          timeout  = "10s"
        }
      }
    }
  }
}

On note le tags qui possède une ligne de plus :

"tricot-site-lb"

Cette directive suffit à Tricot pour savoir qu’il doit répartir les requètes. On constate sur la page de whoami que le Hostname change régulièrement au bout d’un certain nombre de reload. En bonus grâce à la section update vous pouvez modifier la configuration du hcl (par exemple monter ou descendre la memory) et relancer un nomad job run whoami.hcl, les conteneurs se mettront à jour sans interruption de service 🙌

Fin

La beauté est comme toujours dans la simplicité et l’efficacité 😏

Hésitez pas à tester mon image fredix/tricot ou bien construire la votre, j’ai fait un pull request pour rafraichir le Dockerfile à partir d’une image Alpine.

(Ce texte a été écrit avec VNote)