497 lines
14 KiB
Markdown
497 lines
14 KiB
Markdown
|
---
|
||
|
layout: post
|
||
|
slug: a-quick-tour-of-garage
|
||
|
status: published
|
||
|
sitemap: true
|
||
|
title: A quick tour of Garage
|
||
|
description: Garage is a new lightweight and versatile object storage platform implementing the S3 API, let's quickly deploy it.
|
||
|
category: operation
|
||
|
tags:
|
||
|
---
|
||
|
|
||
|
|
||
|
[Garage](https://garagehq.deuxfleurs.fr) is an object storage platform that can be used as a drop-in replacement for AWS S3.
|
||
|
It is designed to run on bare metal hardware and has no dependency on any cloud provider.
|
||
|
In this article, I quickly deploy a geo-distributed Garage cluster and use it as a backend for Nextcloud.
|
||
|
|
||
|
I chose to use a cloud platform (Scaleway) to easily and quickly spawn geo-distributed machines.
|
||
|
To abstract our machines deployment, I wrote a small tool named [nuage](https://git.deuxfleurs.fr/quentin/nuage).
|
||
|
|
||
|
You will need a working account on [Scaleway](https://console.scaleway.com).
|
||
|
Then, we will need to install some tools on our machine (be sure to have [go](https://golang.org) installed).
|
||
|
|
||
|
Let's install Scaleway's CLI tool:
|
||
|
|
||
|
```bash
|
||
|
sudo curl -o /usr/local/bin/scw -L "https://github.com/scaleway/scaleway-cli/releases/download/v2.3.1/scw-2.3.1-linux-x86_64"
|
||
|
sudo chmod +x /usr/local/bin/scw
|
||
|
scw init # enter your scaleway credentials
|
||
|
```
|
||
|
|
||
|
And my helper to easily deploy instances on Scaleway:
|
||
|
|
||
|
```bash
|
||
|
go install git.deuxfleurs.fr/quentin/nuage@latest
|
||
|
export PATH="$PATH:$HOME/go/bin"
|
||
|
nuage # display how to use the tool
|
||
|
```
|
||
|
|
||
|
Now, we are ready to spawn some machines!
|
||
|
|
||
|
## Spawn machines
|
||
|
|
||
|
We start by creating our `nuage` inventory in a file named `garage_inventory.txt`:
|
||
|
|
||
|
```
|
||
|
fr-par-1 dev1-s debian_bullseye garage-fr-1
|
||
|
fr-par-1 dev1-s debian_bullseye garage-fr-2
|
||
|
pl-waw-1 dev1-s debian_bullseye garage-pl-1
|
||
|
pl-waw-1 dev1-s debian_bullseye garage-pl-2
|
||
|
nl-ams-1 dev1-s debian_bullseye garage-nl-1
|
||
|
nl-ams-1 dev1-s debian_bullseye garage-nl-2
|
||
|
```
|
||
|
|
||
|
Then let's pass it to `nuage`:
|
||
|
|
||
|
```bash
|
||
|
nuage spawn < ./garage_inventory.txt
|
||
|
```
|
||
|
|
||
|
`nuage` will spawn 6 machines:
|
||
|
- 2 machines in Paris, France
|
||
|
- 2 machines in Warsaw, Poland
|
||
|
- 2 machines in Amsterdam, Netherlands
|
||
|
|
||
|
All instances will run debian on Scaleway's cheap [dev1-s](https://www.scaleway.com/en/pricing/#development-instances) instances.
|
||
|
For the naming of our instances, we built it following this pattern: `garage-<zone>-<id>`.
|
||
|
|
||
|
Now, we suppose that the instances are started and your able to login on them, for example:
|
||
|
|
||
|
```
|
||
|
ssh root@51.15.227.63
|
||
|
```
|
||
|
|
||
|
## Some crypto
|
||
|
|
||
|
Our garage instances will communicate together securely through TLS.
|
||
|
We need to generate some certificates locally that we will deploy on the remote instances later.
|
||
|
To ease the operation, we provide a small handler named `genkeys.sh` to generate all the needed keys:
|
||
|
|
||
|
```
|
||
|
wget https://git.deuxfleurs.fr/Deuxfleurs/garage/raw/tag/v0.3.0/genkeys.sh
|
||
|
chmod +x ./genkeys.sh
|
||
|
./genkeys.sh
|
||
|
```
|
||
|
|
||
|
Now you should have a folder named `pki` containing both the key and the certificate for your CA and your end-entity.
|
||
|
Ideally, each node would have its own end-entity certificate but to simplify the configuration, we will use only once in this tour.
|
||
|
|
||
|
Let's create a script to deploy our pki:
|
||
|
|
||
|
```bash
|
||
|
cat > deploy_pki.sh <<EOF
|
||
|
#!/bin/bash
|
||
|
mkdir -p /etc/garage/pki
|
||
|
cat > /etc/garage/pki/garage-ca.crt <<EOG
|
||
|
$(cat pki/garage-ca.crt)
|
||
|
EOG
|
||
|
cat > /etc/garage/pki/garage.crt <<EOG
|
||
|
$(cat pki/garage.crt)
|
||
|
EOG
|
||
|
cat > /etc/garage/pki/garage.key <<EOG
|
||
|
$(cat pki/garage.key)
|
||
|
EOG
|
||
|
EOF
|
||
|
```
|
||
|
|
||
|
Then send and execute our generated script on each of our machine.
|
||
|
|
||
|
```
|
||
|
nuage run ./deploy_pki.sh < ./garage-inventory.txt
|
||
|
```
|
||
|
|
||
|
## Configuration
|
||
|
|
||
|
Garage needs a small configuration file to work.
|
||
|
|
||
|
Again, we will write a deployment script.
|
||
|
You must adapt the `bootstrap_peers` section to your instances, you can run again `nuage spawn < ./garage-inventory.txt` to get their addresses.
|
||
|
Not all IP addresses are needed, after a discovery phase, garage maintains its own list and exchange it regulargy with its peers
|
||
|
|
||
|
Save the following file as `deploy_conf.sh` once you edited it (we arbitrarily chose to put 3 IPs here):
|
||
|
|
||
|
```bash
|
||
|
#!/bin/bash
|
||
|
cat > /etc/garage/config.toml <<EOF
|
||
|
metadata_dir = "/var/lib/garage/meta"
|
||
|
data_dir = "/var/lib/garage/data"
|
||
|
replication_mode = "3"
|
||
|
rpc_bind_addr = "[::]:3901"
|
||
|
bootstrap_peers = [
|
||
|
"51.15.59.148:3901",
|
||
|
"51.15.227.63:3901",
|
||
|
"51.15.206.116:3901",
|
||
|
]
|
||
|
[rpc_tls]
|
||
|
ca_cert = "/etc/garage/pki/garage-ca.crt"
|
||
|
node_cert = "/etc/garage/pki/garage.crt"
|
||
|
node_key = "/etc/garage/pki/garage.key"
|
||
|
[s3_api]
|
||
|
s3_region = "garage"
|
||
|
api_bind_addr = "[::]:3900"
|
||
|
[s3_web]
|
||
|
bind_addr = "[::]:3902"
|
||
|
root_domain = ".web.garage"
|
||
|
index = "index.html"
|
||
|
EOF
|
||
|
```
|
||
|
|
||
|
And now, the deployment:
|
||
|
|
||
|
```bash
|
||
|
nuage run ./deploy_conf.sh < ./garage-inventory.txt
|
||
|
```
|
||
|
|
||
|
## Binary and service
|
||
|
|
||
|
And this is already the last step of our deployment, installing the binary and the systemd service.
|
||
|
Again, we write a deployment script, named `deploy_bin.sh` this time:
|
||
|
|
||
|
```bash
|
||
|
#!/bin/bash
|
||
|
# Downloading Garage
|
||
|
wget https://garagehq.deuxfleurs.fr/_releases/v0.3.0/x86_64-unknown-linux-musl/garage -O /usr/local/bin/garage
|
||
|
chmod +x /usr/local/bin/garage
|
||
|
# Creating a control command
|
||
|
cat > /usr/local/bin/garagectl <<EOF
|
||
|
#!/bin/bash
|
||
|
/usr/local/bin/garage \
|
||
|
--ca-cert /etc/garage/pki/garage-ca.crt \
|
||
|
--client-cert /etc/garage/pki/garage.crt \
|
||
|
--client-key /etc/garage/pki/garage.key \
|
||
|
\$@
|
||
|
EOF
|
||
|
chmod +x /usr/local/bin/garagectl
|
||
|
# Creating a systemd service
|
||
|
cat > /etc/systemd/system/garage.service <<EOF
|
||
|
[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 -c /etc/garage/config.toml
|
||
|
DynamicUser=true
|
||
|
StateDirectory=garage
|
||
|
[Install]
|
||
|
WantedBy=multi-user.target
|
||
|
EOF
|
||
|
# Activating it
|
||
|
systemctl daemon-reload
|
||
|
systemctl enable garage
|
||
|
systemctl start garage
|
||
|
```
|
||
|
|
||
|
And we execute it:
|
||
|
|
||
|
```
|
||
|
nuage run ./deploy_bin.sh < ./garage-inventory.txt
|
||
|
```
|
||
|
|
||
|
## garagectl
|
||
|
|
||
|
Now that we have built a cluster, we can connect on a machine and use garagectl to configure cluster-wide parameters.
|
||
|
So, first connect on any server (you can run `nuage spawn < garage-inventory.txt` to get your nodes IP addresses).
|
||
|
For example:
|
||
|
|
||
|
```bash
|
||
|
ssh root@51.158.182.206
|
||
|
```
|
||
|
|
||
|
You can see the current cluster status with:
|
||
|
|
||
|
```bash
|
||
|
garagectl status
|
||
|
```
|
||
|
|
||
|
We will then configure each node, assigning them:
|
||
|
- a relative size, because all our nodes have the same storage space, we will put a size of 1 everywhere.
|
||
|
- a zone, that will match the country where our instances are hosted
|
||
|
|
||
|
|
||
|
```bash
|
||
|
garagectl status
|
||
|
garagectl node configure -c 1 -z pl ??
|
||
|
garagectl node configure -c 1 -z pl ??
|
||
|
garagectl node configure -c 1 -z fr ??
|
||
|
# etc.
|
||
|
```
|
||
|
|
||
|
Now, we can create a key, a bucket, and allow the key to access the bucket:
|
||
|
|
||
|
```bash
|
||
|
garagectl key new --name quentin
|
||
|
garagectl bucket create my_files
|
||
|
garagectl bucket allow my_files --read --write --key GKfd49e3906e5d2e3e23ee07f9
|
||
|
```
|
||
|
|
||
|
Back to our local machine, we can already interact with our cluster through `awscli`.
|
||
|
|
||
|
You can install `awscli` as follow:
|
||
|
|
||
|
```
|
||
|
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||
|
unzip awscliv2.zip
|
||
|
sudo ./aws/install
|
||
|
```
|
||
|
|
||
|
And quickly setup it by creating a file `~/.awsrc` (edit with your access key, secret key and endpoint):
|
||
|
|
||
|
```bash
|
||
|
export AWS_ACCESS_KEY_ID=GKfd49e3906e5d2e3e23ee07f9
|
||
|
export AWS_SECRET_ACCESS_KEY=xxxxxx
|
||
|
export AWS_DEFAULT_REGION='garage'
|
||
|
function aws { command aws --endpoint-url http://51.158.182.206:3900 $@ ; }
|
||
|
aws --version
|
||
|
```
|
||
|
|
||
|
And then, each time you want to use it, run:
|
||
|
|
||
|
```bash
|
||
|
source ~/.awsrc
|
||
|
```
|
||
|
|
||
|
Now, you should be able to use the awscli command freely:
|
||
|
|
||
|
```bash
|
||
|
aws s3 ls # list buckets
|
||
|
aws s3 cp garage-inventory.txt s3://my_files/inventory.txt # send a file
|
||
|
aws s3 ls my_files # list files in the bucket
|
||
|
```
|
||
|
|
||
|
## Nextcloud
|
||
|
|
||
|
We will provision another machine specifically for Nextcloud.
|
||
|
We start by creating a file named `nextcloud-inventory.txt` containing:
|
||
|
|
||
|
```
|
||
|
fr-par-1 dev1-s debian_bullseye nextcloud-fr-1
|
||
|
```
|
||
|
|
||
|
And spawn it:
|
||
|
|
||
|
```
|
||
|
nuage spawn < ./nextcloud-inventory.txt
|
||
|
```
|
||
|
|
||
|
Then we create an install script for Nextcloud named `deploy_nextcloud.sh`:
|
||
|
|
||
|
```bash
|
||
|
#!/bin/bash
|
||
|
apt-get update
|
||
|
apt-get install -y apache2 mariadb-server libapache2-mod-php7.4 php7.4-gd \
|
||
|
php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl php7.4-gmp \
|
||
|
php7.4-bcmath php-imagick php7.4-xml php7.4-zip unzip
|
||
|
systemctl start mysql
|
||
|
mysql -u root --password="" <<EOF
|
||
|
CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||
|
CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'nextcloud';
|
||
|
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';
|
||
|
FLUSH PRIVILEGES;
|
||
|
EOF
|
||
|
rm -fr nextcloud.zip nextcloud/
|
||
|
wget https://download.nextcloud.com/server/releases/nextcloud-22.1.1.zip -O nextcloud.zip
|
||
|
unzip nextcloud.zip
|
||
|
rm -fr /var/www/nextcloud
|
||
|
mv nextcloud /var/www
|
||
|
cat > /etc/apache2/sites-available/nextcloud.conf <<EOF
|
||
|
Alias /nextcloud "/var/www/nextcloud/"
|
||
|
|
||
|
<Directory /var/www/nextcloud/>
|
||
|
Require all granted
|
||
|
AllowOverride All
|
||
|
Options FollowSymLinks MultiViews
|
||
|
|
||
|
<IfModule mod_dav.c>
|
||
|
Dav off
|
||
|
</IfModule>
|
||
|
</Directory>
|
||
|
EOF
|
||
|
a2ensite nextcloud.conf
|
||
|
a2enmod rewrite
|
||
|
a2enmod headers
|
||
|
a2enmod env
|
||
|
a2enmod dir
|
||
|
a2enmod mime
|
||
|
systemctl restart apache2
|
||
|
chown -R www-data:www-data /var/www/nextcloud/
|
||
|
```
|
||
|
|
||
|
Then deploy it:
|
||
|
|
||
|
```
|
||
|
nuage run ./deploy_nextcloud.sh < ./nextcloud-inventory.txt
|
||
|
```
|
||
|
|
||
|
Then open in your browser Nextcloud, for me http://212.47.230.180/nextcloud.
|
||
|
Finish the installation by providing requested information.
|
||
|
|
||
|
Now we will configure Nextcloud to use Garage as its primary object storage.
|
||
|
You can also [read its documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/primary_storage.html).
|
||
|
|
||
|
First, we need to create the bucket and the key (and will also allow our own key):
|
||
|
|
||
|
```bash
|
||
|
garagectl bucket create nextcloud
|
||
|
garagectl key new --name nextcloud
|
||
|
garagectl bucket allow nextcloud --read --write GK872ebec80feae4ad663e82ec
|
||
|
garagectl bucket allow nextcloud --read GKfd49e3906e5d2e3e23ee07f9
|
||
|
```
|
||
|
|
||
|
We will SSH on the server and edit `config.php`
|
||
|
|
||
|
```bash
|
||
|
ssh root@212.47.230.180
|
||
|
vim /var/www/nextcloud/config/config.php
|
||
|
```
|
||
|
|
||
|
and add:
|
||
|
|
||
|
```php
|
||
|
<?php
|
||
|
$CONFIG = array(
|
||
|
/* some other config */
|
||
|
'objectstore' => [
|
||
|
'class' => '\\OC\\Files\\ObjectStore\\S3',
|
||
|
'arguments' => [
|
||
|
'bucket' => 'nextcloud',
|
||
|
'autocreate' => false,
|
||
|
'key' => 'GK872ebec80feae4ad663e82ec',
|
||
|
'secret' => 'xxxxxxxxxxxxx',
|
||
|
'hostname' => '51.158.182.206',
|
||
|
'port' => 3900,
|
||
|
'use_ssl' => false,
|
||
|
'region' => 'garage',
|
||
|
// required for some non Amazon S3 implementations
|
||
|
'use_path_style' => true
|
||
|
],
|
||
|
],
|
||
|
```
|
||
|
|
||
|
*If you have some errors after reloading the page, run `tail -f /var/www/nextcloud/media/nextcloud.log`*
|
||
|
|
||
|
Primary storage is only one way to integrate Garage in Nextcloud, it is also possible to integrate it through the "External storage" plugin.
|
||
|
This method is not covered here but you can refer to [Nextcloud's documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/external_storage_configuration_gui.html).
|
||
|
|
||
|
After uploading a file, you can see how nextcloud store them internally through awscli:
|
||
|
|
||
|
```
|
||
|
aws s3 ls nextcloud
|
||
|
```
|
||
|
|
||
|
Our current deployment has some drawbacks: we have a single point of failure with only one server and data are not sent encrypted.
|
||
|
One solution is to deploy garage on our server, locally, as a gateway.
|
||
|
First, we install it normally:
|
||
|
|
||
|
```bash
|
||
|
nuage run ./deploy_pki.sh < ./nextcloud-inventory.txt
|
||
|
nuage run ./deploy_conf.sh < ./nextcloud-inventory.txt
|
||
|
nuage run ./deploy_bin.sh < ./nextcloud-inventory.txt
|
||
|
```
|
||
|
|
||
|
Then, we configure our new node as a gateway because we do not want to store data on it, we just want to use it to route data:
|
||
|
|
||
|
```bash
|
||
|
garagectl status
|
||
|
garagectl node configure -z fr -g 2b145f7b4c15c2a4
|
||
|
```
|
||
|
|
||
|
Then we edit Nextcloud's configuration `/var/www/nextcloud/config/config.php` to just change the hostname:
|
||
|
|
||
|
```php
|
||
|
<?php
|
||
|
$CONFIG = array(
|
||
|
/* some other config */
|
||
|
'objectstore' => [
|
||
|
'class' => '\\OC\\Files\\ObjectStore\\S3',
|
||
|
'arguments' => [
|
||
|
/* other arguments */
|
||
|
'hostname' => '127.0.0.1',
|
||
|
/* other arguments */
|
||
|
],
|
||
|
]
|
||
|
```
|
||
|
|
||
|
Now we have a high availability backend as our local gateway will try to route our request to available servers.
|
||
|
|
||
|
## Handle crashes
|
||
|
|
||
|
Start by choosing a node you want to crash:
|
||
|
|
||
|
```bash
|
||
|
nuage spawn < ./garage-inventory.txt
|
||
|
```
|
||
|
|
||
|
SSH on it, we will simulate its failure by just stopping it (there is no difference between graceful shutdown and crashes):
|
||
|
|
||
|
```bash
|
||
|
ssh root@151.115.34.19
|
||
|
systemctl stop garage
|
||
|
```
|
||
|
|
||
|
Connect on another node and note that the node is unavailable:
|
||
|
|
||
|
```bash
|
||
|
ssh root@51.15.59.148
|
||
|
garagectl status
|
||
|
```
|
||
|
|
||
|
For now, no re-balancing has been triggered.
|
||
|
Garage allows for transient failures.
|
||
|
If you want to re-balance, you have to explicitly remove the node from Garage.
|
||
|
|
||
|
Now, let's assume this is only a transient failure, and let's restart it:
|
||
|
|
||
|
```bash
|
||
|
ssh root@151.115.34.19
|
||
|
systemctl start garage
|
||
|
journalctl -fu garage
|
||
|
```
|
||
|
|
||
|
Note how the repair is automatically triggered.
|
||
|
You can still manually trigger a repair if you want:
|
||
|
|
||
|
```bash
|
||
|
garagectl repair --yes
|
||
|
```
|
||
|
|
||
|
Now let's assume that the machine burnt and all its disks are losts:
|
||
|
|
||
|
```bash
|
||
|
systemctl stop garage
|
||
|
rm -r /var/lib/garage/
|
||
|
systemctl start garage
|
||
|
garagectl status
|
||
|
```
|
||
|
|
||
|
Now our node is seen as a new one and its old ID is seen as failed.
|
||
|
We will replace the old node with this new one with a simple command:
|
||
|
|
||
|
```
|
||
|
garagectl node configure --replace 212027752f40c4d4 -c 1 -z pl 375690c499627ea8
|
||
|
garagectl status
|
||
|
```
|
||
|
|
||
|
We do not cover this part, but you can also add or remove nodes at any time and trigger a re-balance.
|
||
|
|
||
|
## Destroy our VM
|
||
|
|
||
|
When you're done with this tour, just destroy the resources you created:
|
||
|
|
||
|
```bash
|
||
|
nuage destroy < ./garage-inventory.txt
|
||
|
nuage destroy < ./nextcloud-inventory.txt
|
||
|
```
|
||
|
|
||
|
Thanks a lot, this is the end of my tour of Garage, see you next time :)
|