Effectivement, ça sent un peu le cambouis

Michel, utilisateur de garage

C’est quoi exactement S3 ?

Commençons par le commencement, S3 (pour Simple Storage Service) est un protocole de stockage objet, appuyé sur HTTP. Pensez NAS mais avec autre chose que du NFS/CIFS et plus orienté usage sur WAN plutôt que LAN (et oui, je sais qu’on peut faire du NFS/CIFS sur du WAN mais juste parce que tu peux ne veut pas dire que tu dois…).

Pourquoi est-ce que tu devrais t’y intéresser ? Parce qu’il y a tout un tas de logiciels qui l’utilisent pour faire du stockage de fichiers et que ça peut du coup être bien pratique d’en avoir à disposition. On pensera, entre autres, à NextCloud, Peertube, Mastodon, Hugo, Restic, etc…

En plus, comme c’est orienté Web, il est aussi possible de l’utiliser directement pour servir un site Web statique, ou des éléments statiques sur un site Web (et donc techniquement économiser de la ressource sur ledit site Web).

Ah et du coup, pourquoi garage ?

Il y a plein de logiciels qui font du S3 : OpenIO, Ceph, MinIO et probablement plein d’autres. Mais ils ont tous (ou presque) en commun d’être relativement compliqués à mettre en œuvre, soit en terme technique, soit en terme de ressource.

C’est là qu’intervient garage. Son objectif est de faire du S3 avec le moins de ressources possibles (en gros, ça peut tourner sur un Raspberry Pi, littéralement) tout en conservant un minimum de fonctionnalités propre à ce type de service : redondance des données, transfert optimisé, etc… Il est également conçu pour supporter des latences réseaux relativement élevées (de l’ordre de 200 ms au max), ce qui n’est généralement pas le cas de ces concurrents.

Et en plus, c’est écrit en Rust ❤️

Bon OK, comment qu’on fait du coup ?

On va partir sur une architecture volontairement un peu compliquée pour que tu puisses bien te rendre compte des possibilités offertes par garage. En fait, on va créer un cluster avec 6 nœuds au total (que j’ai appelé garage{1..5} et garagegw1), dans 3 zones différentes dont un nœud passerelle (j’expliquerai le pourquoi de la passerelle plus tard). Il faut savoir que garage est conçu pour assurer lui même l’intégrité et la redondance des données, il n’est donc généralement pas nécessaire d’avoir des systèmes super résilients sur les nœuds eux-mêmes (oubliez les RAIDs et autres systèmes de redondance, garage fait tout pour vous).

J’ai donc monté 6 containers LXC pour ce faire, histoire de me simplifier la vie. Première chose, il faut aller télécharger l’exécutable garage sur l’ensemble des 6 nœuds (dans /usr/local/bin/garage par exemple avec les bonnes permissions). Tu peux le compiler toi-même si tu as la patience, sinon tu peux le télécharger directement ici (il s’agit d’un exécutable statique compilé avec musl, il est donc énorme, mais a l’avantage de tourner n’importe où sans autre prérequis).

Petite précision : garage étant un exécutable sans aucune dépendance, j’ai choisi de le faire tourner directement sur les containers, mais on pouvait tout aussi bien le faire tourner dans un container Docker (si besoin, l’image officielle est dxflrs/garage).

Bref, maintenant qu’on a nos exécutables sur chaque machine, on va pouvoir créer un service systemd pour les démarrer. garage supporte par défaut la fonction DynamicUser de systemd qui permet de démarrer un service avec un numéro d’UID aléatoire à chaque fois (c’est systemd qui recolle les morceaux), ce qui est bien pratique pour ne pas se prendre la tête avec les détails genre créer un utilisateur, une home directory, etc…

On peut donc créer, sur chaque nœud, le fichier /etc/systemd/system/garage.service :

[Unit]
Description=Garage Data Store
After=network-online.target
Wants=network-online.target

[Service]
Environment='RUST_LOG=garage=info' 'RUST_BACKTRACE=1'
ExecStart=/usr/local/bin/garage server
StateDirectory=garage
DynamicUser=true
ProtectHome=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

Les données seront alors stockées /var/lib/private/garage/{meta,data} de manière permanente avec un lien symbolique dans /var/lib/garage quand le service est démarré. Mais avant de faire cela, il va falloir créer un fichier de configuration spécifique à garage, /etc/garage.toml :

metadata_dir = "/var/lib/garage/meta" # obligatoire sinon rien ne va fonctionner
data_dir = "/var/lib/garage/data" # pareil
db_engine = "lmdb" # c’est la DB recommandé par défaut pour stocker les métadonnées

replication_mode = "3" # le mode de réplication. Nous voulons ici une redondance max, ce sera donc 3.
                       # on peut mettre 2 ou même 1 en fonction de la redondance que l’on souhaite.

compression_level = 2 # le degré de compression zstd, de 1 à 19, plus c’est élevé, plus c’est compressé
                      # plus ça consomme de ressources

rpc_bind_addr = "[::]:3901" # l’adresse par défaut pour le protocole RPC de garage
rpc_public_addr = "garage1.lxc.arpa:3901" # son nom public
# ça, c’est le secret RPC qui doit être partagé entre tous les nœuds,
# on peut le générer avec la commande `openssl rand -hex 32`
rpc_secret = "814297d51a3878c66ac4db89e20539b980dd70f43be35dd884d0c2f68aab6c90"

# ça, c’est pour accéder à l’API S3
[s3_api]
s3_region = "garage"
# concernant les nœuds de stockage, on peut éventuellement écouter sur [::1] si on a une passerelle
api_bind_addr = "[::]:3900"
root_domain = ".lxc.arpa"

Petite explication complémentaire : garage utilise un protocole chiffré qui lui est propre pour communiquer entre les nœuds (ça correspond aux indications rpc_*), parfaitement utilisable sur un réseau non-sûr comme Internet. Par contre, par défaut, le S3 est en HTTP et non en HTTPS. Donc si tu dois exposer l’API S3, il faudra probablement rajouter un reverse proxy devant (ou utiliser une autre astuce que nous verrons un peu plus loin).

Bref, maintenant tu peux démarrer le service garage sur chaque nœud et faire un :

# garage status
==== HEALTHY NODES ====
ID                Hostname  Address         Tags              Zone  Capacity
f1a78b6b674c054d  garage1   127.0.1.1:3901  NO ROLE ASSIGNED

Comme tu peux le constater, pour le moment, il ne fait pas grand-chose…

Fuuuuuuuuuuuuu-sion !

On va donc se dépêcher t’interconnecter tout ce beau monde. On va donc se connecter sur chaque nœud et récupérer son identifiant avec la commande suivante :

# garage node id -q
93506740b859618e6b5953092485b7717d186b3b15b61d9c59e20f685ee3122f@garage2:3901

Cet identifiant peut ensuite être utilisé sur le tout premier nœud pour faire l’interconnection :

# garage node connect 93506740b859618e6b5953092485b7717d186b3b15b61d9c59e20f685ee3122f@garage2:3901
Success.

Et ainsi de suite pour l’ensemble des nœuds du cluster. À la fin, le statut devrait être le suivant :

# garage status
==== HEALTHY NODES ====
ID                Hostname   Address            Tags              Zone  Capacity
32e482de67868f30  garage5    100.64.4.34:3901   NO ROLE ASSIGNED
93506740b859618e  garage2    100.64.4.105:3901  NO ROLE ASSIGNED
fe8c6c5dd5730813  garagegw1  100.64.4.238:3901  NO ROLE ASSIGNED
38e9e413613a6afe  garage4    100.64.4.217:3901  NO ROLE ASSIGNED
f1a78b6b674c054d  garage1    127.0.1.1:3901     NO ROLE ASSIGNED
9bc75561e211a385  garage3    100.64.4.153:3901  NO ROLE ASSIGNED

Voilà, tous les nœuds se voient entre eux. Il va maintenant falloir les grouper dans des zones, leur donner des capacités et, optionnellement, désigner une paserelle (ce que nous allons en l’occurence faire).

Les zones

La zone ne donne, en soi, aucune indication particulière : c’est simplement un moyen de grouper les serveurs entre eux. Derrière le rideau, garage va surtout se servir des zones pour multiplier les redondances de données.

Il faut savoir que si elles sont présentes, les zones serviront de base pour la redondance des données et non les serveurs présents dans chaque zone.

La passerelle

Les passerelles sont simplement des nœuds garage qui ne stockent pas de données, mais permettent d’accéder aux données via S3. Imaginons que tu ne souhaites pas exposer directemet l’API S3 sur Internet mais tu ais besoin d’un de tes services y accède. Tu peux donc simplement monter un garage sur la même machine que celle hébergeant le service en question, le faire écouter sur localhost sur ce même serveur et le grouper avec tous les autres. Comme ça, tu peux effectivement accéder à tes buckets S3 et leurs données, mais sans jamais exposer publiquement garage et sans copier les données sur le serveur susnommé.

Les capacités

Alors là, ça se complique un tout petit peu. Les capacités s’entendent au sens disque, mais en relatif d’un nœud à l’autre. Donc si tu mets 10/10/10 dans une configuration à 3 nœuds, tu indiques simplement que tous les nœuds peuvent héberger la même quantité de données (tu pourrais aussi bien mettre 1/1/1). Si tu mets 10/20/30, tu indiques que le nœud 30 a 3 fois plus de capacité que le nœud 10 et 50% de plus que le nœud 20. Ça n’indique donc pas vraiment la capacité, mais plus la façon dont il faut répartir les données.

Au passage, une autre fonctionnalité intéressante de garage, la plupart des autres logiciels faisant du S3 que je connais obligent à avoir des nœuds de capacité identique, aussi bien d’un point de vue stockage, que mémoire ou puissance de calcul.

Allez, chauffe Marcel !

Allons mettre tout cela en musique. Nous avons 5 nœuds de stockage, nous allons donc les répartir en 3 zones : maison (qui simulera les babasses qu’il y a chez toi), mere-grand (qui sera le serveur que t’as mis en loucedé chez ta grand-mère) et boulot (pour celui que tu as laissé dans le placard à balai du taf un jour que t’étais bourré). Nous admettrons aussi que les serveurs maison sont identiques en terme de capa, les autres serveurs des autres zones seront 3 fois plus gros chacun (histoire d’avoir une copie complète de toutes les données dans chaque zone).

# garage layout assign -z maison -g fe8c6c5dd5730813
# garage layout assign -z maison -c 10 f1a78b6b674c054d
# garage layout assign -z maison -c 10 93506740b859618e
# garage layout assign -z maison -c 10 9bc75561e211a385
# garage layout assign -z mere-grand -c 30 38e9e413613a6afe
# garage layout assign -z boulot -c 30 32e482de67868f30

Voilà, avec ça on va avoir un layout relativement cohérent par rapport à l’énoncé plus haut. Tu peux le voir avec garage layout show et l’appliquer avec garage layout --version 1. Tout ceci est effectivement versionné en cas de besoin mais fais quand même attention : chaque fois que tu fais un changement significatif sur le design général, cela entraîne des mouvements de données pour répondre à ce besoin. Donc autant sur un cluster vide, comme c’est le cas actuellement, les conséquences seront moindres, autant sur un cluster rempli ras la gueule, ça risque d’être bien plus compliqué.

J’fais quoi moi maintenant ? Tu te casses !

Bon ben maintenant, on va pouvoir créer un bucket, une clé et téléverser quelques fichiers pour vérifier que tout va bien (et observer comment que ça marche derrière). Pour le client S3 à utiliser, je recommande personnellement mc, le client MinIO.

Commençons par créer un bucket :

# garage bucket create potdechambre
Bucket potdechambre was created.
# garage key new --name caca
Key name: caca
Key ID: GK3fdf189892c2c7665049631d
Secret key: 0fd528f075d06ce05bf6e020e50d42bb37e288e1bb36d9a38076ef06f541958a
Can create buckets: false

Key-specific bucket aliases:

Authorized buckets:

Il suffit maintenant d’associer la clé en question au bucket précédent (on va se mettre tous les droits pour le moment, mais évidemment, on peut faire plus subtil) :

# garage bucket allow potdechambre --owner --read --write --key GK3fdf189892c2c7665049631d
New permissions for GK3fdf189892c2c7665049631d on potdechambre: read true, write true, owner true.

Et voilà, il ne reste plus qu’à tester avec mc :

> mc alias set garage http://garagegw1.lxc.arpa:3900 <access_key> <secret_key>
> mc ls garage/
[2022-12-29 14:07:20 CET]     0B potdechambre/

Et donc maintenant, on peut déposer quelques fichiers et voir comment ça se comporte :

> mc mirror ./Téléchargements garage/potdechambre/
...ur-card.zip: 74.42 MiB / 74.42 MiB ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 56.43 MiB/s 1s

En comptant la compression, on voit bien qu’on a une répartition à peu près intelligente des données :

> for i in {1..5}; do echo garage${i}; ssh root@garage${i}.lxc.arpa du -hs /var/lib/private/garage/data; done
garage1
14M	/var/lib/private/garage/data
garage2
29M	/var/lib/private/garage/data
garage3
23M	/var/lib/private/garage/data
garage4
65M	/var/lib/private/garage/data
garage5
65M	/var/lib/private/garage/data

Et bien sûr, on peut récupérer n’importe quel fichier depuis le S3 vers la machine locale :

> mc cp garage/potdechambre/rnamd /tmp/rnamd
...ambre/rnamd: 794.31 KiB / 794.31 KiB ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13.51 MiB/s 0s

Est-ce que c’est vraiment résistant aux pannes ?

Et ben, on va essayer !?! Éteignons le nœud 1 et voyons ce qu’il se passe :

> mc ls garage/potdechambre
[affiche toujours des trucs]
> mc mirror garage/potdechambre /tmp/Téléchargements
...ur-card.zip: 74.42 MiB / 74.42 MiB ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 151.55 MiB/s 0s

Aucun souci. En fait, on peut même éteindre tous les nœuds d’une même zone et continuer d’accéder aux données. Par contre, s’il y a deux pannes dans deux zones différentes (même d’un seul serveur pour le cas de maison), garage ne peut atteindre un quorum et les données ne sont plus accessibles (elles ne sont pas perdues pour autant, en tout cas pour le moment).

Donc en gros, si tu arrives à avoir 3 copies de tes données dans 3 zones différentes, tant que deux de ces zones sont accessibles, tu n’auras pas de souci pour accéder aux données (c’est ce que signifie le replication_mode="3", 3 répliques sur l’ensemble du cluster en essayant de les répartir entre toutes les zones).

Et ben, c’était pas mal tout ça…

Voilà, c’était un exemple un peu compliqué mais qui permet de bien voir toutes les possibilités offertes par garage. Dans le cadre d’un auto-hébergement, on pourra bien sûr faire bien plus simple avec un seul nœud par exemple ou deux nœuds dans deux zones différentes, chez toi et chez un pote genre.

Et maintenant tu vois à quoi ça ressemble, et tu peux éventuellement commencer à imaginer ce que tu pourrais en faire. Dans un prochain article, on regardera comment on peut l’utiliser comme moteur de stockage pour Nextcloud.