DEV Community

Cover image for Partie 6 — Deux instances
Dinh Doan Van Bien
Dinh Doan Van Bien

Posted on • Edited on • Originally published at supabase.voieduco.de

Partie 6 — Deux instances

Partie 6 sur 7 — Supabase en auto-hébergement : retour d'expérience

Version française de Part 6 — Two instances.

L'offre gratuite de Supabase inclut deux projets actifs. Je les utilisais déjà tous les deux. Ajouter une deuxième instance au cluster auto-hébergé ne visait pas la capacité, mais l'isolation. Quand Supabase fait tourner deux projets sur la même infrastructure, comment les maintient-il séparés ? Ce billet y répond par la pratique.


Ce que signifie l'isolation ici

Quand je dis que les deux projets sont isolés, voici ce que j'entends par là.

Isolation réseau. Chaque projet possède son propre réseau overlay Docker. Le fichier compose déclare internal: comme nom de réseau, mais au déploiement via docker stack deploy ... project1, Swarm préfixe automatiquement ce nom avec celui du stack. Le réseau devient project1_internal à l'exécution. Les services de project1 ne peuvent pas atteindre les services de project2 via leurs réseaux internes, même si les deux fichiers compose définissent un réseau appelé internal. Le seul réseau partagé est traefik_default, sur lequel les conteneurs Kong et Studio des deux projets coexistent pour le routage. En théorie, les conteneurs sur le même réseau overlay peuvent communiquer entre eux : l'isolation n'est donc pas absolue au niveau réseau. En pratique, chaque service n'écoute que sur son propre port et exige la clé API de son propre projet.

Isolation des données. Chaque projet dispose de son propre conteneur Postgres avec son propre volume. Les bases de données ne partagent ni stockage, ni connexion, ni aucun moyen de communiquer.

Isolation de l'authentification. Secrets JWT, tables d'utilisateurs, clés API (clé anon, clé service role) — tout est distinct d'un projet à l'autre. Un jeton émis par project1 n'est pas valide sur project2, et inversement.

Isolation du routage. Des sous-domaines différents, des certificats différents.

Les seules ressources partagées sont le proxy inverse Traefik (extérieur aux deux stacks) et le CPU et la RAM du serveur physique.


Le fichier compose est presque identique

Le fichier compose de project2 est une copie de celui de project1, avec ces modifications :

  1. Les valeurs proviennent d'un chemin Vault distinct (secret/project2)
  2. Des noms de routeurs Traefik différents (c'est le point critique)
  3. Des règles de sous-domaine différentes dans les labels Traefik
  4. Un FLY_ALLOC_ID différent pour Realtime (project2-realtime)

Inutile de modifier les noms de réseau à la main : Swarm préfixe automatiquement chaque nom avec celui du stack.


Les noms de routeurs doivent être uniques

C'est le seul détail de configuration qui cassera votre deuxième instance si vous passez à côté.

Traefik identifie les routes par leur nom de routeur. Si deux services enregistrent un routeur avec le même nom, Traefik journalise une erreur (« Router defined multiple times with different configurations ») et les routes concernées renvoient un 404. Dans un flux de logs chargé, c'est facile à rater.

Dans project1, nous avons nommé nos routeurs p1-kong et p1-studio :

traefik.http.routers.p1-kong.rule: Host(`kong.project1.yourdomain.com`)
traefik.http.routers.p1-studio.rule: Host(`studio.project1.yourdomain.com`)
Enter fullscreen mode Exit fullscreen mode

Dans project2, ils doivent être différents :

traefik.http.routers.p2-kong.rule: Host(`kong.project2.yourdomain.com`)
traefik.http.routers.p2-studio.rule: Host(`studio.project2.yourdomain.com`)
Enter fullscreen mode Exit fullscreen mode

Il en va de même pour les noms de services et les noms de middlewares :

traefik.http.services.p2-kong.loadbalancer.server.port: '8000'
traefik.http.middlewares.p2-studio-auth.basicauth.users: ...
traefik.http.routers.p2-studio.middlewares: security-headers@swarm,p2-studio-auth@swarm
Enter fullscreen mode Exit fullscreen mode

Préfixez tout avec l'identifiant du projet. C'est l'affaire de trente secondes.


Secrets Vault séparés

Stockez les secrets de project2 sous un chemin distinct :

vault kv put secret/project2 \
  POSTGRES_PASSWORD="$(openssl rand -hex 16)" \
  JWT_SECRET="$(openssl rand -hex 32)" \
  SUPABASE_ANON_KEY="<different anon jwt>" \
  SUPABASE_SERVICE_ROLE_KEY="<different service_role jwt>" \
  API_EXTERNAL_URL="https://kong.project2.yourdomain.com" \
  GOTRUE_EXTERNAL_URL="https://kong.project2.yourdomain.com" \
  SITE_URL="https://kong.project2.yourdomain.com" \
  DB_ENC_KEY="supabaserealtime" \
  GOTRUE_MAILER_AUTOCONFIRM="false" \
  SECRET_KEY_BASE="$(openssl rand -hex 64)" \
  PG_META_CRYPTO_KEY="$(openssl rand -hex 16)"
Enter fullscreen mode Exit fullscreen mode

Créez un jeton en lecture seule séparé pour project2 :

vault policy write project2-readonly vault-policy-project2.hcl
vault token create -policy=project2-readonly -ttl=8760h -format=json \
  | jq -r '.auth.client_token' > /root/project2-token.txt
Enter fullscreen mode Exit fullscreen mode

Stockez le jeton dans /root/project2-token.txt et ajoutez-le comme secret GitHub Actions séparé si vous utilisez des déploiements automatisés.


Mémoire avec deux instances

En faisant tourner deux stacks complets, voici approximativement comment les 4 Go sont utilisés :

Service Projet 1 Projet 2 Total
PostgreSQL ~77 Mo ~77 Mo ~154 Mo
Kong ~229 Mo ~185 Mo ~414 Mo
GoTrue ~12 Mo ~12 Mo ~24 Mo
PostgREST ~17 Mo ~17 Mo ~34 Mo
Realtime ~168 Mo ~168 Mo ~336 Mo
Storage ~18 Mo ~18 Mo ~36 Mo
postgres-meta ~68 Mo ~68 Mo ~136 Mo
Studio ~170 Mo ~170 Mo ~340 Mo

Sous-total pour les deux projets : environ 1,5 Go. Ajoutez Traefik (30 Mo), Vault (140 Mo) et le système d'exploitation (~300 Mo) et l'on arrive à 2,0–2,5 Go sur les 4 Go disponibles.

Une troisième instance tiendrait probablement. Je n'ai pas encore essayé.


Déployer project2

bash scripts/fetch-env-from-vault.sh project2
set -a && source instances/project2/.env && set +a
docker stack deploy -c instances/project2/docker-compose.yml project2
bash scripts/init-realtime.sh project2
Enter fullscreen mode Exit fullscreen mode

Vérifiez les deux stacks :

docker service ls
Enter fullscreen mode Exit fullscreen mode

Vous devriez voir 17 services ou plus, avec tous les réplicas à 1/1.


Vérifier l'isolation

Créez un utilisateur sur project1 et vérifiez qu'il n'existe pas sur project2 :

curl -X POST https://kong.project1.yourdomain.com/auth/v1/signup \
  -H "apikey: PROJECT1_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"TestPass123!"}'

# Essayez les mêmes identifiants sur project2
curl -X POST https://kong.project2.yourdomain.com/auth/v1/token?grant_type=password \
  -H "apikey: PROJECT2_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"TestPass123!"}'
# {"error":"invalid_grant","error_description":"Invalid login credentials"}
Enter fullscreen mode Exit fullscreen mode

Les systèmes d'authentification sont complètement séparés. C'est d'ailleurs ainsi que Supabase isole les données de ses clients sur son infrastructure mutualisée. Plus simple que je ne le pensais.

Partie 7 — Sécurité et test de charge →


La série complète

  1. Pourquoi auto-héberger
  2. Le serveur
  3. Traefik et SSL
  4. La première instance Supabase
  5. Vault
  6. Deux instances ← cet article
  7. Sécurité et test de charge

Top comments (0)