+++
title = "Garage : le petit serveur S3 qu’il est bien pour s’en servir"
date = 2023-01-02
[taxonomies]
tags = [ "système", "s3", "garage", "stockage" ]
+++

> Effectivement, ça sent un peu le cambouis

*Michel, utilisateur de garage*

<!-- more -->

# 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](https://www.openio.io/), [Ceph](https://ceph.com/), [MinIO](https://min.io/) 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`](https://garagehq.deuxfleurs.fr/). 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](https://garagehq.deuxfleurs.fr/download/) (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` :
```ini
[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` :

```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 :
```bash
# 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 :

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

Cet identifiant peut ensuite être utilisé sur le tout premier nœud pour faire l’interconnection :
```bash
# 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 :
```bash
# 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).

```bash
# 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](https://min.io/docs/minio/linux/reference/minio-mc.html).

Commençons par créer un *bucket* :

```bash
# 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) :

```bash
# 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` :

```bash
> 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 :

```bash
> 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 :

```bash
> 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 :
```bash
> 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 :

```bash
> 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.
