15 min de lectureIngénierie
Pilier

Auto-héberger Elido sur k3s - un playbook complet

Un guide pas à pas pour déployer la stack Elido complète sur un cluster k3s : bootstrap Helm, 14 services, plan de données en StatefulSets, TLS à la demande Caddy, sauvegardes et stratégie de mise à jour.

Marius Voß
DevRel · edge infra
Architecture diagram of Elido on k3s: edge-redirect and api-core Deployments in front, StatefulSets for Postgres, Redis, ClickHouse, Redpanda, MinIO, and Meilisearch behind, Caddy Ingress handling on-demand TLS at the top

La version managée d'Elido tourne sur une infrastructure de région UE avec une configuration privacy-by-default. Pour la plupart des utilisateurs, c'est suffisant. Pour certains, ça ne l'est pas.

Si votre équipe sécurité exige que les données de destination des liens courts et les événements de clic ne quittent jamais un centre de données spécifique, si votre politique d'audit demande un contrôle total sur le serveur de base de données, ou si vous intégrez Elido à une plateforme interne qui doit tourner en air-gap, le chemin auto-hébergé existe précisément à cet effet.

Ce guide parcourt le déploiement d'Elido sur k3s, une distribution Kubernetes minimale de qualité production qui tourne confortablement sur une seule VM ou un petit cluster HA. À la fin, vous aurez chaque service en fonctionnement, TLS provisionné, sauvegardes câblées et un chemin de mise à jour reproductible. Le guide suppose que vous voulez un système qui fonctionne, pas un tutoriel sur les concepts Kubernetes - les explications sont brèves et les étapes opérationnelles sont explicites.

Pourquoi auto-héberger#

Avant d'aborder le playbook, il vaut la peine d'être précis sur les compromis. L'auto-hébergement n'est pas automatiquement meilleur - il déplace le risque opérationnel d'un fournisseur vers votre propre équipe.

Résidence des données et conformité. Si votre cadre de conformité (périmètre ISO 27001, politique de classification des données interne ou clause contractuelle de résidence des données) exige que les métadonnées des liens et les événements analytics restent sur une infrastructure que vous contrôlez directement, le SaaS managé ne peut pas satisfaire cette exigence, quel que soit l'endroit où il s'exécute physiquement. Un déploiement auto-hébergé sur une VM de région UE que vous possédez le peut. Voir l'aperçu de la conformité pour les spécificités sur la façon dont l'architecture d'Elido se mappe aux cadres courants.

Prévisibilité des coûts à l'échelle. Les plans managés facturent sur les liens actifs et le volume de clics. Au-dessus d'un certain seuil - généralement quelque part au-dessus de quelques millions de clics par mois - le coût par événement sur les plans managés dépasse le coût d'infrastructure pour faire tourner soi-même la charge StatefulSet équivalente. Le point de croisement dépend de la forme de votre trafic, mais il existe.

Contrôle d'audit. Certaines organisations exigent l'accès aux tables Postgres brutes et aux données d'événements de clic ClickHouse pour l'emballage de preuves, la conservation légale ou l'intégration SIEM. L'API Elido expose des endpoints de journal d'audit et des exports de preuves, mais l'accès direct à la base de données n'est disponible que dans les déploiements auto-hébergés.

Ce que vous échangez. k3s exige que quelqu'un possède les mises à jour, la santé des nœuds, la vérification des sauvegardes et la réponse aux incidents. Si votre équipe n'a pas d'expérience opérationnelle Kubernetes, la version managée à un emplacement d'hébergement conforme est presque certainement la bonne réponse. Voir la dernière section de ce billet pour un traitement plus direct du moment où l'auto-hébergement est le mauvais choix.

Prérequis#

  • Un cluster k3s. Un seul nœud avec 4 vCPU et 8 Go de RAM gère les charges légères. Pour la HA, trois nœuds control-plane plus deux nœuds workers ou plus est la topologie minimale recommandée. L'etcd intégré de k3s couvre la HA du control-plane ; la HA du plan de données est gérée par Patroni (Postgres) dans la chart Helm.
  • kubectl configuré avec un contexte pointant vers votre cluster.
  • Helm 3.14 ou plus récent.
  • Un domaine que vous contrôlez avec la possibilité de créer des enregistrements DNS. L'IP d'ingress k3s doit être joignable sur les ports 80 et 443 depuis internet pour que les défis ACME Let's Encrypt réussissent.
  • Si conformité oblige, une VM de région UE. Hetzner (Falkenstein, Helsinki, Nuremberg) et OVH (Gravelines, Roubaix) sont les deux fournisseurs qu'utilise la propre infrastructure edge d'Elido.

Sur un Hetzner CX32 neuf (4 vCPU / 8 Go) sous Ubuntu 24.04, k3s s'installe en environ 30 secondes :

curl -sfL https://get.k3s.io | sh -
# Copy the kubeconfig to your local machine:
scp root@<your-vm>:/etc/rancher/k3s/k3s.yaml ~/.kube/elido-self-host.yaml
export KUBECONFIG=~/.kube/elido-self-host.yaml
kubectl get nodes  # should show Ready

Clonez le dépôt Elido pour obtenir la chart Helm (la chart se trouve à deploy/helm/elido/ dans le dépôt - il n'y a pas de dépôt Helm public séparé) :

git clone https://github.com/elidoapp/elido.git
cd elido

Démarrage rapide#

Le preset self-host (deploy/helm/elido/values-selfhost.yaml) est le point de départ recommandé pour un déploiement k3s mono-nœud. Bootstrappez d'abord les secrets, puis installez :

# 1. Mint all required Kubernetes Secrets in one shot (idempotent)
./scripts/bootstrap-secrets.sh elido

# 2. Install using the self-host preset
helm -n elido upgrade --install elido ./deploy/helm/elido \
  -f ./deploy/helm/elido/values-selfhost.yaml \
  --set ingress.hosts.redirect[0]=r.example.com \
  --set ingress.hosts.api=api.example.com \
  --set ingress.hosts.dashboard=app.example.com \
  --set image.tag=$(git rev-parse --short HEAD) \
  --create-namespace \
  --wait --timeout 10m

Pour des surcharges personnalisées (compteurs de réplicas en production, services externes managés, etc.), copiez values-selfhost.yaml vers un my-values.yaml local, éditez-le et passez-le avec -f. Les sections clés de valeurs de haut niveau sont ingress.hosts.* (vos hostnames), image.registry/image.tag et les blocs resources par service. N'utilisez pas global.domain - la chart utilise ingress.hosts.* pour la configuration du domaine.

Le flag --wait bloque jusqu'à ce que tous les Deployments et StatefulSets atteignent leur état ready. Sur un nœud neuf sans images mises en cache, attendez-vous à 5 à 8 minutes sur un lien 1 Gbps.

Vérifiez que tout est bien démarré :

kubectl -n elido get pods
kubectl -n elido get svc

Voir la référence des valeurs de la chart avec helm show values ./deploy/helm/elido pour la surface complète des paramètres. Le deploy/helm/elido/README.md dans le dépôt est la source faisant autorité pour les noms de champs spécifiques et l'utilisation du script de bootstrap.

Flux de deploiement Helm pour Elido : bootstrap-secrets cree les Secrets Kubernetes, helm upgrade install rend la chart avec les valeurs self-host, les manifestes s'appliquent au serveur API k3s, le cluster reconcilie 14 Deployments et 6 StatefulSets pendant que chaque pod execute migrate up au demarrage, et helm wait bloque jusqu'a ce que chaque pod soit Ready

L'architecture que vous déployez#

L'architecture d'Elido est divisée par budget de latence. Comprendre cette division vous aide à prendre des décisions éclairées d'allocation de ressources.

Topologie du cluster k3s pour Elido : un ingress Caddy en haut, le Deployment hot-path edge-redirect et les Deployments API warm-path en dessous, les Deployments workers cold-path sur le cote, et le plan de donnees StatefulSet (Postgres, Redis, ClickHouse, Redpanda, MinIO, Meilisearch) en bas

Hot path : edge-redirect#

edge-redirect est le seul service sur le chemin synchrone d'une requête de redirection. Il est écrit en Go avec fasthttp et a un budget de latence dur : p50 5 ms, p95 15 ms sur un cache hit. Le service maintient un cache à deux niveaux : un LRU in-process (L1) backé par Redis Cluster (L2). Sur un cache miss, il tombe sur un appel gRPC vers api-core. Les événements de clic sont émis fire-and-forget vers Redpanda - la réponse de redirection n'est jamais retenue en attendant que l'écriture de l'événement se termine.

La chart Helm déploie edge-redirect comme un Deployment avec un HorizontalPodAutoscaler. Dans une configuration auto-hébergée mono-région, deux réplicas est un point de départ raisonnable. Le cache Redis L2 est partagé entre les réplicas, donc le réchauffement du cache est rapide après une mise à jour rolling.

Warm path : surface API#

Cinq services gèrent le travail synchrone d'API et de logique métier :

  • api-core - Go + chi, REST et gRPC. Source de vérité pour les liens, workspaces, memberships, domaines personnalisés, événements d'audit.
  • api-bff - couche BFF Node/Hono pour le dashboard web et les clients mobiles. Agrège entre api-core et analytics-api.
  • analytics-api - Go, requêtes ClickHouse. Sert les dashboards analytics.
  • billing - Go + chi + sqlc. Moteur de TVA UE, intégration de paiement LiqPay, génération de factures.
  • search - Go, proxie les requêtes de recherche de liens vers Meilisearch.

L'authentification est gérée par Ory Kratos (identité, sessions, vérification d'email) et Ory Hydra (tokens OAuth2/OIDC pour les intégrations tierces et l'extension de navigateur). Les deux sont déployés comme Deployments dans la chart.

Cold path : workers async#

Six services consomment les événements des topics Redpanda et n'ont pas de budget de temps de réponse :

  • click-ingester - consomme les événements de clic, écrit dans ClickHouse.
  • webhook-dispatcher - fan-out les payloads webhook signés vers les endpoints clients.
  • notification - notifications email et in-app (événements de compte, alertes de lien).
  • url-scanner - exécute le scan d'URL de destination contre Google Safe Browsing, PhishTank, SURBL.
  • metadata-fetcher - récupère les métadonnées Open Graph pour les previews de liens.
  • domain-manager - vérification DNS et provisionnement TLS à la demande Caddy pour les domaines personnalisés.

Plan de données : StatefulSets#

Six systèmes stateful supportent les services ci-dessus :

SystèmeRôleRessource chart
Postgres (Patroni)Source de vérité pour liens, utilisateurs, facturationStatefulSet, 3 réplicas en mode HA
Redis ClusterCache de liens hot-pathStatefulSet
ClickHouseStockage des événements de clic et requêtes analyticsStatefulSet
RedpandaBus d'événements entre servicesStatefulSet
MinIOAssets uploadés par les utilisateurs (images QR, exports)StatefulSet
MeilisearchRecherche de liens in-appStatefulSet

Dans un déploiement mono-nœud, chaque StatefulSet tourne avec un seul réplica. En mode HA (activé en mettant ha.enabled: true dans values), Postgres passe à trois réplicas sous Patroni, Redis passe à six (trois primaires, trois réplicas) et Redpanda passe à trois brokers.

Cablage du plan de donnees pour Elido auto-heberge : edge-redirect lit le cache Redis L2, api-core utilise Postgres comme source de verite et emet des evenements vers Redpanda, click-ingester consomme Redpanda et ecrit dans ClickHouse, analytics-api interroge ClickHouse, search interroge Meilisearch, et api-core stocke les assets dans MinIO

Ingress Caddy#

Caddy gère la terminaison TLS et le TLS à la demande pour les domaines personnalisés des tenants. Quand un opérateur de workspace ajoute un domaine personnalisé via le dashboard (ou l'API), domain-manager vérifie la propriété DNS, enregistre le hostname comme autorisé, et Caddy provisionne le certificat Let's Encrypt sur la première requête HTTP vers ce hostname. La chart déploie Caddy comme un DaemonSet sur les nœuds étiquetés elido.app/ingress=true, ou comme un Deployment dans la configuration mono-nœud par défaut.

Le flux pour un domaine personnalisé de tenant go.company.com :

  1. L'opérateur crée un CNAME : go.company.com → <your-k3s-ingress-ip> (ou un enregistrement A pointant directement).
  2. L'opérateur appelle POST /v1/workspaces/{id}/domains ou clique sur « Add domain » dans le dashboard.
  3. domain-manager interroge le DNS pour confirmer que le CNAME résout vers l'IP d'ingress.
  4. Caddy reçoit la première requête HTTPS, vérifie la liste blanche émise par Elido et demande un certificat à Let's Encrypt via ACME HTTP-01.
  5. Le certificat est stocké dans le volume d'état Caddy et renouvelé automatiquement.

Bootstrap au premier lancement#

Après que helm install se termine, l'API admin est disponible mais aucun compte utilisateur n'existe.

Bootstrapper l'utilisateur admin#

# Port-forward to the api-core admin interface (not exposed by default)
kubectl -n elido port-forward svc/api-core 8081:8081 &

# Create the first admin account
curl -X POST http://localhost:8081/internal/admin/bootstrap \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "[email protected]",
    "password": "your-secure-password"
  }'

Le endpoint de bootstrap n'est appelable que depuis le réseau interne du cluster et est désactivé après le premier appel réussi. Une fois le compte admin créé, connectez-vous via le dashboard à votre domaine configuré.

Créer le premier workspace#

Après connexion, le dashboard demande la création de workspace lors de la première utilisation. Alternativement, via l'API :

curl -X POST https://links.example.com/v1/workspaces \
  -H "Authorization: Bearer <your-session-token>" \
  -H 'Content-Type: application/json' \
  -d '{"name": "My Workspace", "slug": "my-workspace"}'

Ajouter un domaine personnalisé#

Depuis les paramètres du workspace, allez dans Domains et ajoutez votre domaine de lien court. Définissez l'enregistrement DNS (CNAME ou A) avant de cliquer sur « Verify » - domain-manager vérifie le DNS immédiatement et retourne une erreur si l'enregistrement n'est pas encore propagé. Une fois la vérification passée, Caddy provisionnera le certificat sur la première requête de redirection vers ce domaine. L'émission du certificat prend généralement 10 à 30 secondes sur la première requête.

Vérifiez que TLS fonctionne :

curl -I https://go.company.com/healthz
# Expect: HTTP/2 200

Préoccupations opérationnelles#

Sauvegardes#

Postgres. La chart fournit un CronJob qui exécute pg_dump selon un planning configurable (par défaut : quotidien à 02:00 UTC) et upload le dump compressé vers le bucket MinIO configuré dans values. Pour les déploiements HA, pg_dump s'exécute contre un réplica pour éviter d'impacter le primaire. Activez-le dans values :

backups:
  postgres:
    enabled: true
    schedule: "0 2 * * *"
    retentionDays: 30
    s3Bucket: "elido-backups"

Pour la restauration point-in-time, activez l'archivage WAL (postgres.walArchive.enabled: true dans values), qui expédie les segments WAL vers MinIO en continu. Combinez le pg_dump quotidien avec l'archivage WAL pour un RPO sous les 5 minutes.

ClickHouse. ClickHouse stocke les événements de clic, qui sont append-only et peuvent être rejoués depuis Redpanda si la rétention le permet. La chart inclut un CronJob qui exécute SQL BACKUP TABLE vers MinIO en utilisant l'interface de sauvegarde native de ClickHouse. Activez avec backups.clickhouse.enabled: true.

Redpanda. Redpanda est un bus de streaming, pas une base de données. La rétention est basée sur le temps (par défaut : 7 jours) et configurée dans values sous redpanda.retention. Si votre click-ingester prend du retard de plus que la fenêtre de rétention, les événements sont perdus. Surveillez le retard du consumer group - la chart fournit une alerte Prometheus (ElidoRedpandaConsumerLag) qui se déclenche quand un consumer group est à plus de 100K messages de retard.

Vérification des sauvegardes. Une sauvegarde qui n'a jamais été testée est une sauvegarde sur laquelle vous ne pouvez pas compter. Exécutez un exercice de restauration dans un namespace séparé au moins trimestriellement :

kubectl -n elido-restore create ns elido-restore || true
helm install elido-restore ./deploy/helm/elido \
  --namespace elido-restore \
  --values ./my-values.yaml \
  --set restore.fromBackup=true \
  --set restore.backupDate="2026-05-01"

Monitoring#

La chart inclut un ServiceMonitor pour chaque service si le CRD Prometheus Operator est présent dans le cluster. Métriques clés à alerter :

monitoring:
  prometheus:
    enabled: true # requires prometheus-operator in cluster
  grafana:
    enabled: true # deploys bundled dashboards
    adminPassword: "change-me"

Les dashboards Grafana fournis couvrent :

  • Latence p50/p95 d'edge-redirect, ratio de cache hit, volume de clics
  • Taux de requêtes d'api-core, taux d'erreur, latence gRPC
  • Retard du consumer click-ingester par partition Redpanda
  • Retard de réplication primaire/réplica Postgres (Patroni)
  • Taux d'éviction Redis et pression mémoire

Si vous avez déjà une stack Prometheus dans le cluster, mettez monitoring.prometheus.install: false et pointez votre stack existante sur les ServiceMonitors.

HA Patroni pour Postgres#

En mode HA, Patroni gère l'élection du leader et le failover. La chart configure Patroni avec le distributed configuration store kubernetes (utilise les ConfigMaps Kubernetes, pas d'etcd séparé nécessaire). Le failover se termine généralement en 15 à 30 secondes. Pendant le failover, api-core et billing connaissent de brèves erreurs d'écriture ; les deux services réessaient sur 5xx avec backoff exponentiel.

Pour inspecter l'état du cluster Patroni :

kubectl -n elido exec -it postgres-0 -- patronictl -c /etc/patroni/config.yml list

Flux de mise à jour#

Elido suit le semantic versioning. Les releases patch contiennent des corrections de bugs et n'exigent pas d'étapes de migration manuelles. Les releases mineures et majeures peuvent inclure des migrations de schéma de base de données, qui sont intégrées dans les binaires de service et s'exécutent automatiquement au démarrage du pod via le migration runner.

Le chemin de mise à jour recommandé :

# 1. Pull the latest chart (git pull in your cloned repo)
git pull origin main

# 2. Review the upgrade notes
helm show chart ./deploy/helm/elido | grep -A10 'version\|appVersion'

# 3. Diff the values changes (requires helm-diff plugin)
helm diff upgrade elido ./deploy/helm/elido \
  --namespace elido \
  --values ./my-values.yaml

# 4. Upgrade
helm upgrade elido ./deploy/helm/elido \
  --namespace elido \
  --values ./my-values.yaml \
  --wait --timeout 10m

Gestion des migrations. Chaque binaire de service Go contient ses migrations de schéma (utilisant golang-migrate contre un répertoire de migrations embarqué). Au démarrage du pod, le binaire exécute migrate up avant de servir les requêtes. Dans une mise à jour rolling, le nouveau pod applique les migrations en attente avant que l'ancien pod ne se termine. Les migrations doivent être rétrocompatibles avec la version mineure précédente - une nouvelle colonne ajoutée en v1.5 doit être nullable ou porter une valeur par défaut afin que le binaire v1.4 tournant à côté pendant la fenêtre de rollout ne renvoie pas d'erreur.

Blue/green pour les mises à jour majeures. Pour les mises à jour de version majeure où les changements de schéma ne sont pas rétrocompatibles, utilisez la stratégie blue/green : installez la nouvelle version sous un nom de release Helm séparé dans un namespace de staging, migrez un snapshot de la base de données de production vers elle, smoke-testez, puis exécutez le cutover DNS au niveau de l'ingress. Après avoir validé la stack green, supprimez la release blue.

# Install green stack
helm install elido-green ./deploy/helm/elido \
  --namespace elido-green \
  --create-namespace \
  --values ./green-values.yaml

# After validation, cut over DNS at your registrar
# Then decommission blue
helm uninstall elido --namespace elido

Quand ne pas auto-héberger#

L'auto-hébergement est le bon choix dans un ensemble restreint de circonstances. Dans plus de cas qu'on pourrait le croire, c'est le mauvais.

Votre charge est petite. Si vous créez moins de 100K liens par mois et n'avez pas d'exigences strictes de résidence des données, Elido managé coûte moins en argent et en temps opérationnel que faire tourner k3s. Le palier hébergé inclut sauvegardes, mises à jour et astreinte pour l'infrastructure que vous auriez autrement à votre charge.

Vous n'avez pas d'expérience opérationnelle Kubernetes. k3s est minimal, mais Kubernetes n'est pas simple. Si personne dans votre équipe n'a opéré de StatefulSets, géré la sauvegarde/restauration etcd, ou débogué un CrashLoopBackOff dans Patroni à 2 h du matin, l'auto-hébergement ajoute une catégorie de risque opérationnel que les économies de coût d'infrastructure ne compensent pas.

Votre exigence de conformité est la résidence des données UE, pas la tenance spécifique. La résidence des données UE signifie des données stockées et traitées dans l'UE. L'infrastructure managée d'Elido tourne dans Hetzner FRA et OVH GRA, qui satisfont tous deux les exigences de l'article 44 du RGPD sans transfert transfrontalier. Si l'exigence réelle de votre équipe de conformité est « données dans l'UE », le produit managé la satisfait déjà sans déploiement auto-hébergé - voir la page tarification pour les fonctionnalités de conformité disponibles sur chaque plan.

Vous voulez la performance edge multi-région. Le service managé fait tourner des POPs edge à Francfort, Ashburn et Singapour, avec le edge-redirect hot-path déployé à chacun. Un cluster k3s auto-hébergé mono-région a une latence de redirection bornée par la distance géographique de la VM par rapport à l'utilisateur final. L'auto-hébergement multi-région est possible mais multiplie la surface opérationnelle de manière significative - c'est une entreprise différente de ce que couvre ce guide.


Les questions sur les valeurs de la chart Helm, les exigences de ressources StatefulSet pour des volumes de trafic spécifiques, ou les chemins de mise à jour pour une version spécifique doivent aller au board GitHub Discussions de l'édition auto-hébergée. Pour les questions liées à la conformité - spécifiquement ce que la configuration auto-hébergée fournit au-delà de la résidence UE - la page conformité couvre le journal d'audit, l'export de preuves et les contrôles RBAC en détail.

Pour aller plus loin sur le blog#

Essayer Elido

Collez une URL, obtenez un lien court

Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.

Gratuit, sans inscription · 2 par jour

Essayer Elido

Raccourcisseur d'URL hébergé en UE : domaines personnalisés, analyses approfondies et API ouverte. Forfait gratuit - sans carte bancaire.

Tags
self hosted url shortener
k3s
kubernetes url shortener
helm
self host elido
url shortener kubernetes
data residency
eu compliance

Lire la suite