mirror of
https://github.com/deuxfleurs-org/garage.git
synced 2026-08-16 16:58:19 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 740b863750 | |||
| fa394dcd27 | |||
| 30a7dee920 | |||
| b568765c75 | |||
| e9c265e9dc | |||
| 42f692b1e0 | |||
| 14fd3df654 | |||
| 56ac9fd460 | |||
| d76a8576f4 | |||
| 289521886b | |||
| ebd21b325e |
+10
-11
@@ -2,23 +2,22 @@
|
|||||||
|
|
||||||
[The Garage Data Store](./intro.md)
|
[The Garage Data Store](./intro.md)
|
||||||
|
|
||||||
- [Getting Started](./getting_started/index.md)
|
- [Quick start](./quick_start/index.md)
|
||||||
- [Get a binary](./getting_started/binary.md)
|
|
||||||
- [Configure the daemon](./getting_started/daemon.md)
|
|
||||||
- [Control the daemon](./getting_started/control.md)
|
|
||||||
- [Configure a cluster](./getting_started/cluster.md)
|
|
||||||
- [Create buckets and keys](./getting_started/bucket.md)
|
|
||||||
- [Handle files](./getting_started/files.md)
|
|
||||||
|
|
||||||
- [Cookbook](./cookbook/index.md)
|
- [Cookbook](./cookbook/index.md)
|
||||||
- [Host a website](./cookbook/website.md)
|
- [Deploying Garage](./cookbook/real_world.md)
|
||||||
|
- [Configuring S3 clients](./cookbook/clients.md)
|
||||||
|
- [Hosting a website](./cookbook/website.md)
|
||||||
|
- [Recovering from failures](./cookbook/recovering.md)
|
||||||
|
- [Building from source](./cookbook/from_source.md)
|
||||||
|
- [Starting with Systemd](./cookbook/systemd.md)
|
||||||
- [Integrate as a media backend]()
|
- [Integrate as a media backend]()
|
||||||
- [Operate a cluster]()
|
- [Operate a cluster]()
|
||||||
- [Recovering from failures](./cookbook/recovering.md)
|
|
||||||
|
|
||||||
- [Reference Manual](./reference_manual/index.md)
|
- [Reference Manual](./reference_manual/index.md)
|
||||||
- [Garage CLI]()
|
- [Garage configuration file](./reference_manual/configuration.md)
|
||||||
- [S3 API](./reference_manual/s3_compatibility.md)
|
- [Garage CLI](./reference_manual/cli.md)
|
||||||
|
- [S3 compatibility status](./reference_manual/s3_compatibility.md)
|
||||||
|
|
||||||
- [Design](./design/index.md)
|
- [Design](./design/index.md)
|
||||||
- [Related Work](./design/related_work.md)
|
- [Related Work](./design/related_work.md)
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# Configuring S3 clients to interact with Garage
|
||||||
|
|
||||||
|
To configure an S3 client to interact with Garage, you will need the following
|
||||||
|
parameters:
|
||||||
|
|
||||||
|
- An **API endpoint**: this corresponds to the HTTP or HTTPS address
|
||||||
|
used to contact the Garage server. When runing Garage locally this will usually
|
||||||
|
be `http://127.0.0.1:3900`. In a real-world setting, you would usually have a reverse-proxy
|
||||||
|
that adds TLS support and makes your Garage server available under a public hostname
|
||||||
|
such as `https://garage.example.com`.
|
||||||
|
|
||||||
|
- An **API access key** and its associated **secret key**. These usually look something
|
||||||
|
like this: `GK3515373e4c851ebaad366558` (access key),
|
||||||
|
`7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34` (secret key).
|
||||||
|
These keys are created and managed using the `garage` CLI, as explained in the
|
||||||
|
[quick start](../quick_start/index.md) guide.
|
||||||
|
|
||||||
|
Most S3 clients can be configured easily with these parameters,
|
||||||
|
provided that you follow the following guidelines:
|
||||||
|
|
||||||
|
- **Force path style:** Garage does not support DNS-style buckets, which are now by default
|
||||||
|
on Amazon S3. Instead, Garage uses the legacy path-style bucket addressing.
|
||||||
|
Remember to configure your client to acknowledge this fact.
|
||||||
|
|
||||||
|
- **Configuring the S3 region:** Garage requires your client to talk to the correct "S3 region",
|
||||||
|
which is set in the configuration file. This is often set just to `garage`.
|
||||||
|
If this is not configured explicitly, clients usually try to talk to region `us-east-1`.
|
||||||
|
Garage should normally redirect your client to the correct region,
|
||||||
|
but in case your client does not support this you might have to configure it manually.
|
||||||
|
|
||||||
|
We will now provide example configurations for the most common S3 clients.
|
||||||
|
|
||||||
|
## AWS CLI
|
||||||
|
|
||||||
|
Export the following environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export AWS_ACCESS_KEY_ID=<access key>
|
||||||
|
export AWS_SECRET_ACCESS_KEY=<secret key>
|
||||||
|
export AWS_DEFAULT_REGION=<region>
|
||||||
|
```
|
||||||
|
|
||||||
|
Now invoke `aws` as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aws --endpoint-url <endpoint> s3 <command...>
|
||||||
|
```
|
||||||
|
|
||||||
|
For instance: `aws --endpoint-url http://127.0.0.1:3901 s3 ls s3://my-bucket/`.
|
||||||
|
|
||||||
|
## Minio client
|
||||||
|
|
||||||
|
Use the following command to set an "alias", i.e. define a new S3 server to be
|
||||||
|
used by the Minio client:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mc alias set \
|
||||||
|
garage \
|
||||||
|
<endpoint> \
|
||||||
|
<access key> \
|
||||||
|
<secret key> \
|
||||||
|
--api S3v4
|
||||||
|
```
|
||||||
|
|
||||||
|
Remember that `mc` is sometimes called `mcli` (such as on Arch Linux), to avoid conflicts
|
||||||
|
with the Midnight Commander.
|
||||||
|
|
||||||
|
|
||||||
|
## `rclone`
|
||||||
|
|
||||||
|
`rclone` can be configured using the interactive assistant invoked using `rclone configure`.
|
||||||
|
|
||||||
|
You can also configure `rclone` by writing directly its configuration file.
|
||||||
|
Here is a template `rclone.ini` configuration file:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[garage]
|
||||||
|
type = s3
|
||||||
|
provider = Other
|
||||||
|
env_auth = false
|
||||||
|
access_key_id = <access key>
|
||||||
|
secret_access_key = <secret key>
|
||||||
|
region = <region>
|
||||||
|
endpoint = <endpoint>
|
||||||
|
force_path_style = true
|
||||||
|
acl = private
|
||||||
|
bucket_acl = private
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cyberduck
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## `s3cmd`
|
||||||
|
|
||||||
|
Here is a template for the `s3cmd.cfg` file to talk with Garage:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[default]
|
||||||
|
access_key = <access key>
|
||||||
|
secret_key = <secret key>
|
||||||
|
host_base = <endpoint without http(s)://>
|
||||||
|
host_bucket = <same as host_base>
|
||||||
|
use_https = False | True
|
||||||
|
```
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Compiling Garage from source
|
||||||
|
|
||||||
|
|
||||||
|
Garage is a standard Rust project.
|
||||||
|
First, you need `rust` and `cargo`.
|
||||||
|
For instance on Debian:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y rustc cargo
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use [Rustup](https://rustup.rs/) to setup a Rust toolchain easily.
|
||||||
|
|
||||||
|
## Using source from `crates.io`
|
||||||
|
|
||||||
|
Garage's source code is published on `crates.io`, Rust's official package repository.
|
||||||
|
This means you can simply ask `cargo` to download and build this source code for you:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install garage
|
||||||
|
```
|
||||||
|
|
||||||
|
That's all, `garage` should be in `$HOME/.cargo/bin`.
|
||||||
|
|
||||||
|
You can add this folder to your `$PATH` or copy the binary somewhere else on your system.
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp $HOME/.cargo/bin/garage /usr/local/bin/garage
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Using source from the Gitea repository
|
||||||
|
|
||||||
|
The primary location for Garage's source code is the
|
||||||
|
[Gitea repository](https://git.deuxfleurs.fr/Deuxfleurs/garage).
|
||||||
|
|
||||||
|
Clone the repository and build Garage with the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.deuxfleurs.fr/Deuxfleurs/garage.git
|
||||||
|
cd garage
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
Be careful, as this will make a debug build of Garage, which will be extremely slow!
|
||||||
|
To make a release build, invoke `cargo build --release` (this takes much longer).
|
||||||
|
|
||||||
|
The binaries built this way are found in `target/{debug,release}/garage`.
|
||||||
|
|
||||||
@@ -3,3 +3,23 @@
|
|||||||
A cookbook, when you cook, is a collection of recipes.
|
A cookbook, when you cook, is a collection of recipes.
|
||||||
Similarly, Garage's cookbook contains a collection of recipes that are known to works well!
|
Similarly, Garage's cookbook contains a collection of recipes that are known to works well!
|
||||||
This chapter could also be referred as "Tutorials" or "Best practices".
|
This chapter could also be referred as "Tutorials" or "Best practices".
|
||||||
|
|
||||||
|
- **[Deploying Garage](real_world.md):** This page will walk you through all of the necessary
|
||||||
|
steps to deploy Garage in a real-world setting.
|
||||||
|
|
||||||
|
- **[Configuring S3 clients](clients.md):** This page will explain how to configure
|
||||||
|
popular S3 clients to interact with a Garage server.
|
||||||
|
|
||||||
|
- **[Hosting a website](website.md):** This page explains how to use Garage
|
||||||
|
to host a static website.
|
||||||
|
|
||||||
|
- **[Recovering from failures](recovering.md):** Garage's first selling point is resilience
|
||||||
|
to hardware failures. This section explains how to recover from such a failure in the
|
||||||
|
best possible way.
|
||||||
|
|
||||||
|
- **[Building from source](from_source.md):** This page explains how to build Garage from
|
||||||
|
source in case a binary is not provided for your architecture, or if you want to
|
||||||
|
hack with us!
|
||||||
|
|
||||||
|
- **[Starting with Systemd](from_source.md):** This page explains how to run Garage
|
||||||
|
as a Systemd service (instead of as a Docker container).
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
# Deploying Garage on a real-world cluster
|
||||||
|
|
||||||
|
To run Garage in cluster mode, we recommend having at least 3 nodes.
|
||||||
|
This will allow you to setup Garage for three-way replication of your data,
|
||||||
|
the safest and most available mode proposed by Garage.
|
||||||
|
|
||||||
|
We recommend first following the [quick start guide](../quick_start/index.md) in order
|
||||||
|
to get familiar with Garage's command line and usage patterns.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
To run a real-world deployment, make sure you the following conditions are met:
|
||||||
|
|
||||||
|
- You have at least three machines with sufficient storage space available.
|
||||||
|
|
||||||
|
- Each machine has a public IP address which is reachable by other machines.
|
||||||
|
Running behind a NAT is possible, but having several Garage nodes behind a single NAT
|
||||||
|
is slightly more involved as each will have to have a different RPC port number
|
||||||
|
(the local port number of a node must be the same as the port number exposed publicly
|
||||||
|
by the NAT).
|
||||||
|
|
||||||
|
- Ideally, each machine should have a SSD available in addition to the HDD you are dedicating
|
||||||
|
to Garage. This will allow for faster access to metadata and has the potential
|
||||||
|
to drastically reduce Garage's response times.
|
||||||
|
|
||||||
|
- This guide will assume you are using Docker containers to deploy Garage on each node.
|
||||||
|
Garage can also be run independently, for instance as a [Systemd service](systemd.md).
|
||||||
|
You can also use an orchestrator such as Nomad or Kubernetes to automatically manage
|
||||||
|
Docker containers on a fleet of nodes.
|
||||||
|
|
||||||
|
Before deploying Garage on your infrastructure, you must inventory your machines.
|
||||||
|
For our example, we will suppose the following infrastructure with IPv6 connectivity:
|
||||||
|
|
||||||
|
| Location | Name | IP Address | Disk Space |
|
||||||
|
|----------|---------|------------|------------|
|
||||||
|
| Paris | Mercury | fc00:1::1 | 1 To |
|
||||||
|
| Paris | Venus | fc00:1::2 | 2 To |
|
||||||
|
| London | Earth | fc00:B::1 | 2 To |
|
||||||
|
| Brussels | Mars | fc00:F::1 | 1.5 To |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Get a Docker image
|
||||||
|
|
||||||
|
Our docker image is currently named `lxpz/garage_amd64` and is stored on the [Docker Hub](https://hub.docker.com/r/lxpz/garage_amd64/tags?page=1&ordering=last_updated).
|
||||||
|
We encourage you to use a fixed tag (eg. `v0.3.0`) and not the `latest` tag.
|
||||||
|
For this example, we will use the latest published version at the time of the writing which is `v0.3.0` but it's up to you
|
||||||
|
to check [the most recent versions on the Docker Hub](https://hub.docker.com/r/lxpz/garage_amd64/tags?page=1&ordering=last_updated).
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo docker pull lxpz/garage_amd64:v0.3.0
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Generating TLS certificates
|
||||||
|
|
||||||
|
You first need to generate TLS certificates to encrypt traffic between Garage nodes
|
||||||
|
(reffered to as RPC traffic).
|
||||||
|
|
||||||
|
To generate your TLS certificates, run on your machine:
|
||||||
|
|
||||||
|
```
|
||||||
|
wget https://git.deuxfleurs.fr/Deuxfleurs/garage/raw/branch/main/genkeys.sh
|
||||||
|
chmod +x genkeys.sh
|
||||||
|
./genkeys.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
It will creates a folder named `pki/` containing the keys that you will used for the cluster.
|
||||||
|
These files will have to be copied to all of your cluster nodes, as explained below.
|
||||||
|
|
||||||
|
|
||||||
|
## Deploying and configuring Garage
|
||||||
|
|
||||||
|
On each machine, we will have a similar setup,
|
||||||
|
especially you must consider the following folders/files:
|
||||||
|
|
||||||
|
- `/etc/garage/garage.toml`: Garage daemon's configuration (see below)
|
||||||
|
|
||||||
|
- `/etc/garage/pki/`: Folder containing Garage certificates,
|
||||||
|
must be generated on your computer and copied on the servers.
|
||||||
|
Only the files `garage-ca.crt`, `garage.crt` and `garage.key` are necessary.
|
||||||
|
|
||||||
|
- `/var/lib/garage/meta/`: Folder containing Garage's metadata,
|
||||||
|
put this folder on a SSD if possible
|
||||||
|
|
||||||
|
- `/var/lib/garage/data/`: Folder containing Garage's data,
|
||||||
|
this folder will be your main data storage and must be on a large storage (e.g. large HDD)
|
||||||
|
|
||||||
|
|
||||||
|
A valid `/etc/garage/garage.toml` for our cluster would be:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
metadata_dir = "/var/lib/garage/meta"
|
||||||
|
data_dir = "/var/lib/garage/data"
|
||||||
|
|
||||||
|
replication_mode = "3"
|
||||||
|
|
||||||
|
rpc_bind_addr = "[::]:3901"
|
||||||
|
|
||||||
|
bootstrap_peers = [
|
||||||
|
"[fc00:1::1]:3901",
|
||||||
|
"[fc00:1::2]:3901",
|
||||||
|
"[fc00:B::1]:3901",
|
||||||
|
"[fc00:F::1]: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"
|
||||||
|
```
|
||||||
|
|
||||||
|
Please make sure to change `bootstrap_peers` to **your** IP addresses!
|
||||||
|
|
||||||
|
Check the [configuration file reference documentation](../reference_manual/configuration.md)
|
||||||
|
to learn more about all available configuration options.
|
||||||
|
|
||||||
|
|
||||||
|
## Starting Garage using Docker
|
||||||
|
|
||||||
|
On each machine, you can run the daemon with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run \
|
||||||
|
-d \
|
||||||
|
--name garaged \
|
||||||
|
--restart always \
|
||||||
|
--network host \
|
||||||
|
-v /etc/garage/pki:/etc/garage/pki \
|
||||||
|
-v /etc/garage/garage.toml:/garage/garage.toml \
|
||||||
|
-v /var/lib/garage/meta:/var/lib/garage/meta \
|
||||||
|
-v /var/lib/garage/data:/var/lib/garage/data \
|
||||||
|
lxpz/garage_amd64:v0.3.0
|
||||||
|
```
|
||||||
|
|
||||||
|
It should be restarted automatically at each reboot.
|
||||||
|
Please note that we use host networking as otherwise Docker containers
|
||||||
|
can not communicate with IPv6.
|
||||||
|
|
||||||
|
Upgrading between Garage versions should be supported transparently,
|
||||||
|
but please check the relase notes before doing so!
|
||||||
|
To upgrade, simply stop and remove this container and
|
||||||
|
start again the command with a new version of Garage.
|
||||||
|
|
||||||
|
|
||||||
|
## Controling the daemon
|
||||||
|
|
||||||
|
The `garage` binary has two purposes:
|
||||||
|
- it acts as a daemon when launched with `garage server ...`
|
||||||
|
- it acts as a control tool for the daemon when launched with any other command
|
||||||
|
|
||||||
|
In this section, we will see how to use the `garage` binary as a control tool for the daemon we just started.
|
||||||
|
You first need to get a shell having access to this binary. For instance, enter the Docker container with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker exec -ti garaged bash
|
||||||
|
```
|
||||||
|
|
||||||
|
You will now have a shell where the Garage binary is available as `/garage/garage`
|
||||||
|
|
||||||
|
*You can also install the binary on your machine to remotely control the cluster.*
|
||||||
|
|
||||||
|
## Talk to the daemon and create an alias
|
||||||
|
|
||||||
|
`garage` requires 4 options to talk with the daemon:
|
||||||
|
|
||||||
|
```
|
||||||
|
--ca-cert <ca-cert>
|
||||||
|
--client-cert <client-cert>
|
||||||
|
--client-key <client-key>
|
||||||
|
-h, --rpc-host <rpc-host>
|
||||||
|
```
|
||||||
|
|
||||||
|
The 3 first ones are certificates and keys needed by TLS, the last one is simply the address of Garage's RPC endpoint.
|
||||||
|
|
||||||
|
If you are invoking `garage` from a server node directly, you do not need to set `--rpc-host`
|
||||||
|
as the default value `127.0.0.1:3901` will allow it to contact Garage correctly.
|
||||||
|
|
||||||
|
To avoid typing the 3 first options each time we want to run a command,
|
||||||
|
you can use the following alias:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
alias garagectl='/garage/garage \
|
||||||
|
--ca-cert /etc/garage/pki/garage-ca.crt \
|
||||||
|
--client-cert /etc/garage/pki/garage.crt \
|
||||||
|
--client-key /etc/garage/pki/garage.key'
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now use all of the commands presented in the [quick start guide](../quick_start/index.md),
|
||||||
|
simply replace occurences of `garage` by `garagectl`.
|
||||||
|
|
||||||
|
#### Test the alias
|
||||||
|
|
||||||
|
You can test your alias by running a simple command such as:
|
||||||
|
|
||||||
|
```
|
||||||
|
garagectl status
|
||||||
|
```
|
||||||
|
|
||||||
|
You should get something like that as result:
|
||||||
|
|
||||||
|
```
|
||||||
|
Healthy nodes:
|
||||||
|
8781c50c410a41b3… Mercury [fc00:1::1]:3901 UNCONFIGURED/REMOVED
|
||||||
|
2a638ed6c775b69a… Venus [fc00:1::2]:3901 UNCONFIGURED/REMOVED
|
||||||
|
68143d720f20c89d… Earth [fc00:B::1]:3901 UNCONFIGURED/REMOVED
|
||||||
|
212f7572f0c89da9… Mars [fc00:F::1]:3901 UNCONFIGURED/REMOVED
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Configuring a cluster
|
||||||
|
|
||||||
|
We will now inform Garage of the disk space available on each node of the cluster
|
||||||
|
as well as the zone (e.g. datacenter) in which each machine is located.
|
||||||
|
|
||||||
|
For our example, we will suppose we have the following infrastructure (Capacity, Identifier and Datacenter are specific values to Garage described in the following):
|
||||||
|
|
||||||
|
| Location | Name | Disk Space | `Capacity` | `Identifier` | `Zone` |
|
||||||
|
|----------|---------|------------|------------|--------------|--------------|
|
||||||
|
| Paris | Mercury | 1 To | `2` | `8781c5` | `par1` |
|
||||||
|
| Paris | Venus | 2 To | `4` | `2a638e` | `par1` |
|
||||||
|
| London | Earth | 2 To | `4` | `68143d` | `lon1` |
|
||||||
|
| Brussels | Mars | 1.5 To | `3` | `212f75` | `bru1` |
|
||||||
|
|
||||||
|
#### Node identifiers
|
||||||
|
|
||||||
|
After its first launch, Garage generates a random and unique identifier for each nodes, such as:
|
||||||
|
|
||||||
|
```
|
||||||
|
8781c50c410a41b363167e9d49cc468b6b9e4449b6577b64f15a249a149bdcbc
|
||||||
|
```
|
||||||
|
|
||||||
|
Often a shorter form can be used, containing only the beginning of the identifier, like `8781c5`,
|
||||||
|
which identifies the server "Mercury" located in "Paris" according to our previous table.
|
||||||
|
|
||||||
|
The most simple way to match an identifier to a node is to run:
|
||||||
|
|
||||||
|
```
|
||||||
|
garagectl status
|
||||||
|
```
|
||||||
|
|
||||||
|
It will display the IP address associated with each node;
|
||||||
|
from the IP address you will be able to recognize the node.
|
||||||
|
|
||||||
|
#### Zones
|
||||||
|
|
||||||
|
Zones are simply a user-chosen identifier that identify a group of server that are grouped together logically.
|
||||||
|
It is up to the system administrator deploying Garage to identify what does "grouped together" means.
|
||||||
|
|
||||||
|
In most cases, a zone will correspond to a geographical location (i.e. a datacenter).
|
||||||
|
Behind the scene, Garage will use zone definition to try to store the same data on different zones,
|
||||||
|
in order to provide high availability despite failure of a zone.
|
||||||
|
|
||||||
|
#### Capacity
|
||||||
|
|
||||||
|
Garage reasons on an abstract metric about disk storage that is named the *capacity* of a node.
|
||||||
|
The capacity configured in Garage must be proportional to the disk space dedicated to the node.
|
||||||
|
Due to the way the Garage allocation algorithm works, capacity values must
|
||||||
|
be **integers**, and must be **as small as possible**, for instance with
|
||||||
|
1 representing the size of your smallest server.
|
||||||
|
|
||||||
|
Here we chose that 1 unit of capacity = 0.5 To, so that we can express servers of size
|
||||||
|
1 To and 2 To, as wel as the intermediate size 1.5 To, with the integer values 2, 4 and
|
||||||
|
3 respectively (see table above).
|
||||||
|
|
||||||
|
Note that the amount of data stored by Garage on each server may not be strictly proportional to
|
||||||
|
its capacity value, as Garage will priorize having 3 copies of data in different zones,
|
||||||
|
even if this means that capacities will not be strictly respected. For example in our above examples,
|
||||||
|
nodes Earth and Mars will always store a copy of everything each, and the third copy will
|
||||||
|
have 66% chance of being stored by Venus and 33% chance of being stored by Mercury.
|
||||||
|
|
||||||
|
#### Injecting the topology
|
||||||
|
|
||||||
|
Given the information above, we will configure our cluster as follow:
|
||||||
|
|
||||||
|
```
|
||||||
|
garagectl node configure -z par1 -c 2 -t mercury 8781c5
|
||||||
|
garagectl node configure -z par1 -c 4 -t venus 2a638e
|
||||||
|
garagectl node configure -z lon1 -c 4 -t earth 68143d
|
||||||
|
garagectl node configure -z bru1 -c 3 -t mars 212f75
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Using your Garage cluster
|
||||||
|
|
||||||
|
Creating buckets and managing keys is done using the `garagectl` CLI,
|
||||||
|
and is covered in the [quick start guide](../quick_start/index.md).
|
||||||
|
Remember also that the CLI is self-documented thanks to the `--help` flag and
|
||||||
|
the `help` subcommand (e.g. `garage help`, `garage key --help`).
|
||||||
|
|
||||||
|
Configuring an S3 client to interact with Garage is covered
|
||||||
|
[in the next section](clients.md).
|
||||||
@@ -6,14 +6,18 @@ Fear not! For Garage is fully equipped to handle drive failures, in most common
|
|||||||
|
|
||||||
## A note on availability of Garage
|
## A note on availability of Garage
|
||||||
|
|
||||||
With nodes dispersed in 3 datacenters or more, here are the guarantees Garage provides with the default replication strategy (3 copies of all data, which is the recommended value):
|
With nodes dispersed in 3 zones or more, here are the guarantees Garage provides with the 3-way replication strategy (3 copies of all data, which is the recommended replication mode):
|
||||||
|
|
||||||
- The cluster remains fully functional as long as the machines that fail are in only one datacenter. This includes a whole datacenter going down due to power/Internet outage.
|
- The cluster remains fully functional as long as the machines that fail are in only one zone. This includes a whole zone going down due to power/Internet outage.
|
||||||
- No data is lost as long as the machines that fail are in at most two datacenters.
|
- No data is lost as long as the machines that fail are in at most two zones.
|
||||||
|
|
||||||
Of course this only works if your Garage nodes are correctly configured to be aware of the datacenter in which they are located.
|
Of course this only works if your Garage nodes are correctly configured to be aware of the zone in which they are located.
|
||||||
Make sure this is the case using `garage status` to check on the state of your cluster's configuration.
|
Make sure this is the case using `garage status` to check on the state of your cluster's configuration.
|
||||||
|
|
||||||
|
In case of temporarily disconnected nodes, Garage should automatically re-synchronize
|
||||||
|
when the nodes come back up. This guide will deal with recovering from disk failures
|
||||||
|
that caused the loss of the data of a node.
|
||||||
|
|
||||||
|
|
||||||
## First option: removing a node
|
## First option: removing a node
|
||||||
|
|
||||||
@@ -92,7 +96,7 @@ Then, replace the broken node by the new one, using:
|
|||||||
|
|
||||||
```
|
```
|
||||||
garage node configure --replace <old_node_id> \
|
garage node configure --replace <old_node_id> \
|
||||||
-c <capacity> -d <datacenter> -t <node_tag> <new_node_id>
|
-c <capacity> -z <zone> -t <node_tag> <new_node_id>
|
||||||
```
|
```
|
||||||
|
|
||||||
Garage will then start synchronizing all required data on the new node.
|
Garage will then start synchronizing all required data on the new node.
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Starting Garage with systemd instead of Docker
|
||||||
|
|
||||||
|
NOTE: This guide is incomplete. Typicall you would also want to create a separate
|
||||||
|
Unix user to run Garage.
|
||||||
|
|
||||||
|
Make sure you have the Garage binary installed on your system (see [quick start](../quick_start/index.md)), e.g. at `/usr/local/bin/garage`.
|
||||||
|
|
||||||
|
Create a file named `/etc/systemd/system/garage.service`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[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/garage.toml
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
To start the service then automatically enable it at boot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl start garage
|
||||||
|
sudo systemctl enable garage
|
||||||
|
```
|
||||||
|
|
||||||
|
To see if the service is running and to browse its logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status garage
|
||||||
|
sudo journalctl -u garage
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to modify the service file, do not forget to run `systemctl daemon-reload`
|
||||||
|
to inform `systemd` of your modifications.
|
||||||
@@ -1 +1,3 @@
|
|||||||
# Host a website
|
# Hosting a website
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
# Get a binary
|
|
||||||
|
|
||||||
Currently, only two installations procedures are supported for Garage: from Docker (x86\_64 for Linux) and from source.
|
|
||||||
In the future, we plan to add a third one, by publishing a compiled binary (x86\_64 for Linux).
|
|
||||||
We did not test other architecture/operating system but, as long as your architecture/operating system is supported by Rust, you should be able to run Garage (feel free to report your tests!).
|
|
||||||
|
|
||||||
## From Docker
|
|
||||||
|
|
||||||
Our docker image is currently named `lxpz/garage_amd64` and is stored on the [Docker Hub](https://hub.docker.com/r/lxpz/garage_amd64/tags?page=1&ordering=last_updated).
|
|
||||||
We encourage you to use a fixed tag (eg. `v0.2.1`) and not the `latest` tag.
|
|
||||||
For this example, we will use the latest published version at the time of the writing which is `v0.2.1` but it's up to you
|
|
||||||
to check [the most recent versions on the Docker Hub](https://hub.docker.com/r/lxpz/garage_amd64/tags?page=1&ordering=last_updated).
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo docker pull lxpz/garage_amd64:v0.2.1
|
|
||||||
```
|
|
||||||
|
|
||||||
## From source
|
|
||||||
|
|
||||||
Garage is a standard Rust project.
|
|
||||||
First, you need `rust` and `cargo`.
|
|
||||||
On Debian:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y rustc cargo
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, you can ask cargo to install the binary for you:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install garage
|
|
||||||
```
|
|
||||||
|
|
||||||
That's all, `garage` should be in `$HOME/.cargo/bin`.
|
|
||||||
You can add this folder to your `$PATH` or copy the binary somewhere else on your system.
|
|
||||||
For the following, we will assume you copied it in `/usr/local/bin/garage`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo cp $HOME/.cargo/bin/garage /usr/local/bin/garage
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# Create buckets and keys
|
|
||||||
|
|
||||||
*We use a command named `garagectl` which is in fact an alias you must define as explained in the [Control the daemon](./daemon.md) section.*
|
|
||||||
|
|
||||||
In this section, we will suppose that we want to create a bucket named `nextcloud-bucket`
|
|
||||||
that will be accessed through a key named `nextcloud-app-key`.
|
|
||||||
|
|
||||||
Don't forget that `help` command and `--help` subcommands can help you anywhere, the CLI tool is self-documented! Two examples:
|
|
||||||
|
|
||||||
```
|
|
||||||
garagectl help
|
|
||||||
garagectl bucket allow --help
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create a bucket
|
|
||||||
|
|
||||||
Fine, now let's create a bucket (we imagine that you want to deploy nextcloud):
|
|
||||||
|
|
||||||
```
|
|
||||||
garagectl bucket create nextcloud-bucket
|
|
||||||
```
|
|
||||||
|
|
||||||
Check that everything went well:
|
|
||||||
|
|
||||||
```
|
|
||||||
garagectl bucket list
|
|
||||||
garagectl bucket info nextcloud-bucket
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create an API key
|
|
||||||
|
|
||||||
Now we will generate an API key to access this bucket.
|
|
||||||
Note that API keys are independent of buckets: one key can access multiple buckets, multiple keys can access one bucket.
|
|
||||||
|
|
||||||
Now, let's start by creating a key only for our PHP application:
|
|
||||||
|
|
||||||
```
|
|
||||||
garagectl key new --name nextcloud-app-key
|
|
||||||
```
|
|
||||||
|
|
||||||
You will have the following output (this one is fake, `key_id` and `secret_key` were generated with the openssl CLI tool):
|
|
||||||
|
|
||||||
```
|
|
||||||
Key name: nextcloud-app-key
|
|
||||||
Key ID: GK3515373e4c851ebaad366558
|
|
||||||
Secret key: 7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34
|
|
||||||
Authorized buckets:
|
|
||||||
```
|
|
||||||
|
|
||||||
Check that everything works as intended:
|
|
||||||
|
|
||||||
```
|
|
||||||
garagectl key list
|
|
||||||
garagectl key info nextcloud-app-key
|
|
||||||
```
|
|
||||||
|
|
||||||
## Allow a key to access a bucket
|
|
||||||
|
|
||||||
Now that we have a bucket and a key, we need to give permissions to the key on the bucket!
|
|
||||||
|
|
||||||
```
|
|
||||||
garagectl bucket allow \
|
|
||||||
--read \
|
|
||||||
--write
|
|
||||||
nextcloud-bucket \
|
|
||||||
--key nextcloud-app-key
|
|
||||||
```
|
|
||||||
|
|
||||||
You can check at any times allowed keys on your bucket with:
|
|
||||||
|
|
||||||
```
|
|
||||||
garagectl bucket info nextcloud-bucket
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# Configure a cluster
|
|
||||||
|
|
||||||
*We use a command named `garagectl` which is in fact an alias you must define as explained in the [Control the daemon](./daemon.md) section.*
|
|
||||||
|
|
||||||
In this section, we will inform garage of the disk space available on each node of the cluster
|
|
||||||
as well as the site (think datacenter) of each machine.
|
|
||||||
|
|
||||||
## Test cluster
|
|
||||||
|
|
||||||
As this part is not relevant for a test cluster, you can use this one-liner to create a basic topology:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
garagectl status | grep UNCONFIGURED | grep -Po '^[0-9a-f]+' | while read id; do
|
|
||||||
garagectl node configure -d dc1 -c 1 $id
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
## Real-world cluster
|
|
||||||
|
|
||||||
For our example, we will suppose we have the following infrastructure (Capacity, Identifier and Datacenter are specific values to garage described in the following):
|
|
||||||
|
|
||||||
| Location | Name | Disk Space | `Capacity` | `Identifier` | `Datacenter` |
|
|
||||||
|----------|---------|------------|------------|--------------|--------------|
|
|
||||||
| Paris | Mercury | 1 To | `2` | `8781c5` | `par1` |
|
|
||||||
| Paris | Venus | 2 To | `4` | `2a638e` | `par1` |
|
|
||||||
| London | Earth | 2 To | `4` | `68143d` | `lon1` |
|
|
||||||
| Brussels | Mars | 1.5 To | `3` | `212f75` | `bru1` |
|
|
||||||
|
|
||||||
### Identifier
|
|
||||||
|
|
||||||
After its first launch, garage generates a random and unique identifier for each nodes, such as:
|
|
||||||
|
|
||||||
```
|
|
||||||
8781c50c410a41b363167e9d49cc468b6b9e4449b6577b64f15a249a149bdcbc
|
|
||||||
```
|
|
||||||
|
|
||||||
Often a shorter form can be used, containing only the beginning of the identifier, like `8781c5`,
|
|
||||||
which identifies the server "Mercury" located in "Paris" according to our previous table.
|
|
||||||
|
|
||||||
The most simple way to match an identifier to a node is to run:
|
|
||||||
|
|
||||||
```
|
|
||||||
garagectl status
|
|
||||||
```
|
|
||||||
|
|
||||||
It will display the IP address associated with each node; from the IP address you will be able to recognize the node.
|
|
||||||
|
|
||||||
### Capacity
|
|
||||||
|
|
||||||
Garage reasons on an arbitrary metric about disk storage that is named the *capacity* of a node.
|
|
||||||
The capacity configured in Garage must be proportional to the disk space dedicated to the node.
|
|
||||||
Additionaly, the capacity values used in Garage should be as small as possible, with
|
|
||||||
1 ideally representing the size of your smallest server.
|
|
||||||
|
|
||||||
Here we chose that 1 unit of capacity = 0.5 To, so that we can express servers of size
|
|
||||||
1 To and 2 To, as wel as the intermediate size 1.5 To.
|
|
||||||
|
|
||||||
### Datacenter
|
|
||||||
|
|
||||||
Datacenter are simply a user-chosen identifier that identify a group of server that are located in the same place.
|
|
||||||
It is up to the system administrator deploying garage to identify what does "the same place" means.
|
|
||||||
Behind the scene, garage will try to store the same data on different sites to provide high availability despite a data center failure.
|
|
||||||
|
|
||||||
### Inject the topology
|
|
||||||
|
|
||||||
Given the information above, we will configure our cluster as follow:
|
|
||||||
|
|
||||||
```
|
|
||||||
garagectl node configure --datacenter par1 -c 2 -t mercury 8781c5
|
|
||||||
garagectl node configure --datacenter par1 -c 4 -t venus 2a638e
|
|
||||||
garagectl node configure --datacenter lon1 -c 4 -t earth 68143d
|
|
||||||
garagectl node configure --datacenter bru1 -c 3 -t mars 212f75
|
|
||||||
```
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
# Control the daemon
|
|
||||||
|
|
||||||
The `garage` binary has two purposes:
|
|
||||||
- it acts as a daemon when launched with `garage server ...`
|
|
||||||
- it acts as a control tool for the daemon when launched with any other command
|
|
||||||
|
|
||||||
In this section, we will see how to use the `garage` binary as a control tool for the daemon we just started.
|
|
||||||
You first need to get a shell having access to this binary, which depends of your configuration:
|
|
||||||
- with `docker-compose`, run `sudo docker-compose exec g1 bash` then `/garage/garage`
|
|
||||||
- with `docker`, run `sudo docker exec -ti garaged bash` then `/garage/garage`
|
|
||||||
- with `systemd`, simply run `/usr/local/bin/garage` if you followed previous instructions
|
|
||||||
|
|
||||||
*You can also install the binary on your machine to remotely control the cluster.*
|
|
||||||
|
|
||||||
## Talk to the daemon and create an alias
|
|
||||||
|
|
||||||
`garage` requires 4 options to talk with the daemon:
|
|
||||||
|
|
||||||
```
|
|
||||||
--ca-cert <ca-cert>
|
|
||||||
--client-cert <client-cert>
|
|
||||||
--client-key <client-key>
|
|
||||||
-h, --rpc-host <rpc-host>
|
|
||||||
```
|
|
||||||
|
|
||||||
The 3 first ones are certificates and keys needed by TLS, the last one is simply the address of garage's RPC endpoint.
|
|
||||||
Because we configure garage directly from the server, we do not need to set `--rpc-host`.
|
|
||||||
To avoid typing the 3 first options each time we want to run a command, we will create an alias.
|
|
||||||
|
|
||||||
### `docker-compose` alias
|
|
||||||
|
|
||||||
```bash
|
|
||||||
alias garagectl='/garage/garage \
|
|
||||||
--ca-cert /pki/garage-ca.crt \
|
|
||||||
--client-cert /pki/garage.crt \
|
|
||||||
--client-key /pki/garage.key'
|
|
||||||
```
|
|
||||||
|
|
||||||
### `docker` alias
|
|
||||||
|
|
||||||
```bash
|
|
||||||
alias garagectl='/garage/garage \
|
|
||||||
--ca-cert /etc/garage/pki/garage-ca.crt \
|
|
||||||
--client-cert /etc/garage/pki/garage.crt \
|
|
||||||
--client-key /etc/garage/pki/garage.key'
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### raw binary alias
|
|
||||||
|
|
||||||
```bash
|
|
||||||
alias garagectl='/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'
|
|
||||||
```
|
|
||||||
|
|
||||||
Of course, if your deployment does not match exactly one of this alias, feel free to adapt it to your needs!
|
|
||||||
|
|
||||||
## Test the alias
|
|
||||||
|
|
||||||
You can test your alias by running a simple command such as:
|
|
||||||
|
|
||||||
```
|
|
||||||
garagectl status
|
|
||||||
```
|
|
||||||
|
|
||||||
You should get something like that as result:
|
|
||||||
|
|
||||||
```
|
|
||||||
Healthy nodes:
|
|
||||||
2a638ed6c775b69a… 37f0ba978d27 [::ffff:172.20.0.101]:3901 UNCONFIGURED/REMOVED
|
|
||||||
68143d720f20c89d… 9795a2f7abb5 [::ffff:172.20.0.103]:3901 UNCONFIGURED/REMOVED
|
|
||||||
8781c50c410a41b3… 758338dde686 [::ffff:172.20.0.102]:3901 UNCONFIGURED/REMOVED
|
|
||||||
```
|
|
||||||
|
|
||||||
...which means that you are ready to configure your cluster!
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
# Configure the daemon
|
|
||||||
|
|
||||||
Garage is a software that can be run only in a cluster and requires at least 3 instances.
|
|
||||||
In our getting started guide, we document two deployment types:
|
|
||||||
- [Test deployment](#test-deployment) though `docker-compose`
|
|
||||||
- [Real-world deployment](#real-world-deployment) through `docker` or `systemd`
|
|
||||||
|
|
||||||
In any case, you first need to generate TLS certificates, as traffic is encrypted between Garage's nodes.
|
|
||||||
|
|
||||||
## Generating a TLS Certificate
|
|
||||||
|
|
||||||
To generate your TLS certificates, run on your machine:
|
|
||||||
|
|
||||||
```
|
|
||||||
wget https://git.deuxfleurs.fr/Deuxfleurs/garage/raw/branch/master/genkeys.sh
|
|
||||||
chmod +x genkeys.sh
|
|
||||||
./genkeys.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
It will creates a folder named `pki` containing the keys that you will used for the cluster.
|
|
||||||
|
|
||||||
## Test deployment
|
|
||||||
|
|
||||||
Single machine deployment is only described through `docker-compose`.
|
|
||||||
|
|
||||||
Before starting, we recommend you create a folder for our deployment:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir garage-single
|
|
||||||
cd garage-single
|
|
||||||
```
|
|
||||||
|
|
||||||
We start by creating a file named `docker-compose.yml` describing our network and our containers:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
version: '3.4'
|
|
||||||
|
|
||||||
networks: { virtnet: { ipam: { config: [ subnet: 172.20.0.0/24 ]}}}
|
|
||||||
|
|
||||||
services:
|
|
||||||
g1:
|
|
||||||
image: lxpz/garage_amd64:v0.1.1d
|
|
||||||
networks: { virtnet: { ipv4_address: 172.20.0.101 }}
|
|
||||||
volumes:
|
|
||||||
- "./pki:/pki"
|
|
||||||
- "./config.toml:/garage/config.toml"
|
|
||||||
|
|
||||||
g2:
|
|
||||||
image: lxpz/garage_amd64:v0.1.1d
|
|
||||||
networks: { virtnet: { ipv4_address: 172.20.0.102 }}
|
|
||||||
volumes:
|
|
||||||
- "./pki:/pki"
|
|
||||||
- "./config.toml:/garage/config.toml"
|
|
||||||
|
|
||||||
g3:
|
|
||||||
image: lxpz/garage_amd64:v0.1.1d
|
|
||||||
networks: { virtnet: { ipv4_address: 172.20.0.103 }}
|
|
||||||
volumes:
|
|
||||||
- "./pki:/pki"
|
|
||||||
- "./config.toml:/garage/config.toml"
|
|
||||||
```
|
|
||||||
|
|
||||||
*We define a static network here which is not considered as a best practise on Docker.
|
|
||||||
The rational is that Garage only supports IP address and not domain names in its configuration, so we need to know the IP address in advance.*
|
|
||||||
|
|
||||||
and then create the `config.toml` file next to it as follow:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
metadata_dir = "/garage/meta"
|
|
||||||
data_dir = "/garage/data"
|
|
||||||
rpc_bind_addr = "[::]:3901"
|
|
||||||
bootstrap_peers = [
|
|
||||||
"172.20.0.101:3901",
|
|
||||||
"172.20.0.102:3901",
|
|
||||||
"172.20.0.103:3901",
|
|
||||||
]
|
|
||||||
|
|
||||||
[rpc_tls]
|
|
||||||
ca_cert = "/pki/garage-ca.crt"
|
|
||||||
node_cert = "/pki/garage.crt"
|
|
||||||
node_key = "/pki/garage.key"
|
|
||||||
|
|
||||||
[s3_api]
|
|
||||||
s3_region = "garage"
|
|
||||||
api_bind_addr = "[::]:3900"
|
|
||||||
|
|
||||||
[s3_web]
|
|
||||||
bind_addr = "[::]:3902"
|
|
||||||
root_domain = ".web.garage"
|
|
||||||
index = "index.html"
|
|
||||||
```
|
|
||||||
|
|
||||||
*Please note that we have not mounted `/garage/meta` or `/garage/data` on the host: data will be lost when the container will be destroyed.*
|
|
||||||
|
|
||||||
And that's all, you are ready to launch your cluster!
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo docker-compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
While your daemons are up, your cluster is still not configured yet.
|
|
||||||
However, you can check that your services are still listening as expected by querying them from your host:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl http://172.20.0.{101,102,103}:3902
|
|
||||||
```
|
|
||||||
|
|
||||||
which should give you:
|
|
||||||
|
|
||||||
```
|
|
||||||
Not found
|
|
||||||
Not found
|
|
||||||
Not found
|
|
||||||
```
|
|
||||||
|
|
||||||
That's all, you are ready to [configure your cluster!](./cluster.md).
|
|
||||||
|
|
||||||
## Real-world deployment
|
|
||||||
|
|
||||||
Before deploying garage on your infrastructure, you must inventory your machines.
|
|
||||||
For our example, we will suppose the following infrastructure:
|
|
||||||
|
|
||||||
| Location | Name | IP Address | Disk Space |
|
|
||||||
|----------|---------|------------|------------|
|
|
||||||
| Paris | Mercury | fc00:1::1 | 1 To |
|
|
||||||
| Paris | Venus | fc00:1::2 | 2 To |
|
|
||||||
| London | Earth | fc00:B::1 | 2 To |
|
|
||||||
| Brussels | Mars | fc00:F::1 | 1.5 To |
|
|
||||||
|
|
||||||
On each machine, we will have a similar setup, especially you must consider the following folders/files:
|
|
||||||
- `/etc/garage/pki`: Garage certificates, must be generated on your computer and copied on the servers
|
|
||||||
- `/etc/garage/config.toml`: Garage daemon's configuration (defined below)
|
|
||||||
- `/etc/systemd/system/garage.service`: Service file to start garage at boot automatically (defined below, not required if you use docker)
|
|
||||||
- `/var/lib/garage/meta`: Contains Garage's metadata, put this folder on a SSD if possible
|
|
||||||
- `/var/lib/garage/data`: Contains Garage's data, this folder will grows and must be on a large storage, possibly big HDDs.
|
|
||||||
|
|
||||||
A valid `/etc/garage/config.toml` for our cluster would be:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
metadata_dir = "/var/lib/garage/meta"
|
|
||||||
data_dir = "/var/lib/garage/data"
|
|
||||||
rpc_bind_addr = "[::]:3901"
|
|
||||||
bootstrap_peers = [
|
|
||||||
"[fc00:1::1]:3901",
|
|
||||||
"[fc00:1::2]:3901",
|
|
||||||
"[fc00:B::1]:3901",
|
|
||||||
"[fc00:F::1]: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"
|
|
||||||
```
|
|
||||||
|
|
||||||
Please make sure to change `bootstrap_peers` to **your** IP addresses!
|
|
||||||
|
|
||||||
### For docker users
|
|
||||||
|
|
||||||
On each machine, you can run the daemon with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run \
|
|
||||||
-d \
|
|
||||||
--name garaged \
|
|
||||||
--restart always \
|
|
||||||
--network host \
|
|
||||||
-v /etc/garage/pki:/etc/garage/pki \
|
|
||||||
-v /etc/garage/config.toml:/garage/config.toml \
|
|
||||||
-v /var/lib/garage/meta:/var/lib/garage/meta \
|
|
||||||
-v /var/lib/garage/data:/var/lib/garage/data \
|
|
||||||
lxpz/garage_amd64:v0.1.1d
|
|
||||||
```
|
|
||||||
|
|
||||||
It should be restart automatically at each reboot.
|
|
||||||
Please note that we use host networking as otherwise Docker containers can no communicate with IPv6.
|
|
||||||
|
|
||||||
To upgrade, simply stop and remove this container and start again the command with a new version of garage.
|
|
||||||
|
|
||||||
### For systemd/raw binary users
|
|
||||||
|
|
||||||
Create a file named `/etc/systemd/system/garage.service`:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[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
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
|
||||||
|
|
||||||
To start the service then automatically enable it at boot:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl start garage
|
|
||||||
sudo systemctl enable garage
|
|
||||||
```
|
|
||||||
|
|
||||||
To see if the service is running and to browse its logs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl status garage
|
|
||||||
sudo journalctl -u garage
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to modify the service file, do not forget to run `systemctl daemon-reload`
|
|
||||||
to inform `systemd` of your modifications.
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Handle files
|
|
||||||
|
|
||||||
We recommend the use of MinIO Client to interact with Garage files (`mc`).
|
|
||||||
Instructions to install it and use it are provided on the [MinIO website](https://docs.min.io/docs/minio-client-quickstart-guide.html).
|
|
||||||
Before reading the following, you need a working `mc` command on your path.
|
|
||||||
|
|
||||||
## Configure `mc`
|
|
||||||
|
|
||||||
You need your access key and secret key created in the [previous section](bucket.md).
|
|
||||||
You also need to set the endpoint: it must match the IP address of one of the node of the cluster and the API port (3900 by default).
|
|
||||||
For this whole configuration, you must set an alias name: we chose `my-garage`, that you will used for all commands.
|
|
||||||
|
|
||||||
Adapt the following command accordingly and run it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mc alias set \
|
|
||||||
my-garage \
|
|
||||||
http://172.20.0.101:3900 \
|
|
||||||
<access key> \
|
|
||||||
<secret key> \
|
|
||||||
--api S3v4
|
|
||||||
```
|
|
||||||
|
|
||||||
You must also add an environment variable to your configuration to inform MinIO of our region (`garage` by default).
|
|
||||||
The best way is to add the following snippet to your `$HOME/.bash_profile` or `$HOME/.bashrc` file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export MC_REGION=garage
|
|
||||||
```
|
|
||||||
|
|
||||||
## Use `mc`
|
|
||||||
|
|
||||||
You can not list buckets from `mc` currently.
|
|
||||||
|
|
||||||
But the following commands and many more should work:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mc cp image.png my-garage/nextcloud-bucket
|
|
||||||
mc cp my-garage/nextcloud-bucket/image.png .
|
|
||||||
mc ls my-garage/nextcloud-bucket
|
|
||||||
mc mirror localdir/ my-garage/another-bucket
|
|
||||||
```
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# Getting Started
|
|
||||||
|
|
||||||
Let's start your Garage journey!
|
|
||||||
In this chapter, we explain how to deploy a simple garage cluster and start interacting with it.
|
|
||||||
Our goal is to introduce you to Garage's workflows.
|
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
# Quick Start
|
||||||
|
|
||||||
|
Let's start your Garage journey!
|
||||||
|
In this chapter, we explain how to deploy Garage as a single-node server
|
||||||
|
and how to interact with it.
|
||||||
|
|
||||||
|
Our goal is to introduce you to Garage's workflows.
|
||||||
|
Following this guide is recommended before moving on to
|
||||||
|
[configuring a real-world deployment](../cookbook/real_world.md).
|
||||||
|
|
||||||
|
Note that this kind of deployment should not be used in production, as it provides
|
||||||
|
no redundancy for your data!
|
||||||
|
We will also skip intra-cluster TLS configuration, meaning that if you add nodes
|
||||||
|
to your cluster, communication between them will not be secure.
|
||||||
|
|
||||||
|
## Get a binary
|
||||||
|
|
||||||
|
Download the latest Garage binary from the release pages on our repository:
|
||||||
|
|
||||||
|
<https://git.deuxfleurs.fr/Deuxfleurs/garage/releases>
|
||||||
|
|
||||||
|
Place this binary somewhere in your `$PATH` so that you can invoke the `garage`
|
||||||
|
command directly (for instance you can copy the binary in `/usr/local/bin`
|
||||||
|
or in `~/.local/bin`).
|
||||||
|
|
||||||
|
If a binary of the last version is not available for your architecture,
|
||||||
|
you can [build Garage from source](../cookbook/from_source.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Writing a first configuration file
|
||||||
|
|
||||||
|
This first configuration file should allow you to get started easily with the simplest
|
||||||
|
possible Garage deployment:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
metadata_dir = "/tmp/meta"
|
||||||
|
data_dir = "/tmp/data"
|
||||||
|
|
||||||
|
replication_mode = "none"
|
||||||
|
|
||||||
|
rpc_bind_addr = "[::]:3901"
|
||||||
|
|
||||||
|
bootstrap_peers = [
|
||||||
|
"127.0.0.1:3901",
|
||||||
|
]
|
||||||
|
|
||||||
|
[s3_api]
|
||||||
|
s3_region = "garage"
|
||||||
|
api_bind_addr = "[::]:3900"
|
||||||
|
|
||||||
|
[s3_web]
|
||||||
|
bind_addr = "[::]:3902"
|
||||||
|
root_domain = ".web.garage"
|
||||||
|
index = "index.html"
|
||||||
|
```
|
||||||
|
|
||||||
|
Save your configuration file as `garage.toml`.
|
||||||
|
|
||||||
|
As you can see in the `metadata_dir` and `data_dir` parameters, we are saving Garage's data
|
||||||
|
in `/tmp` which gets erased when your system reboots. This means that data stored on this
|
||||||
|
Garage server will not be persistent. Change these to locations on your local disk if you want
|
||||||
|
your data to be persisted properly.
|
||||||
|
|
||||||
|
|
||||||
|
## Launching the Garage server
|
||||||
|
|
||||||
|
Use the following command to launch the Garage server with our configuration file:
|
||||||
|
|
||||||
|
```
|
||||||
|
RUST_LOG=garage=info garage server -c garage.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
You can tune Garage's verbosity as follows (from less verbose to more verbose):
|
||||||
|
|
||||||
|
```
|
||||||
|
RUST_LOG=garage=info garage server -c garage.toml
|
||||||
|
RUST_LOG=garage=debug garage server -c garage.toml
|
||||||
|
RUST_LOG=garage=trace garage server -c garage.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Log level `info` is recommended for most use cases.
|
||||||
|
Log level `debug` can help you check why your S3 API calls are not working.
|
||||||
|
|
||||||
|
|
||||||
|
## Checking that Garage runs correctly
|
||||||
|
|
||||||
|
The `garage` utility is also used as a CLI tool to configure your Garage deployment.
|
||||||
|
It tries to connect to a Garage server through the RPC protocol, by default looking
|
||||||
|
for a Garage server at `localhost:3901`.
|
||||||
|
|
||||||
|
Since our deployment already binds to port 3901, the following command should be sufficient
|
||||||
|
to show Garage's status:
|
||||||
|
|
||||||
|
```
|
||||||
|
garage status
|
||||||
|
```
|
||||||
|
|
||||||
|
This should show something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Healthy nodes:
|
||||||
|
2a638ed6c775b69a… linuxbox 127.0.0.1:3901 UNCONFIGURED/REMOVED
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuring your Garage node
|
||||||
|
|
||||||
|
Configuring the nodes in a Garage deployment means informing Garage
|
||||||
|
of the disk space available on each node of the cluster
|
||||||
|
as well as the zone (e.g. datacenter) each machine is located in.
|
||||||
|
|
||||||
|
For our test deployment, we are using only one node. The way in which we configure
|
||||||
|
it does not matter, you can simply write:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
garage node configure -z dc1 -c 1 <node_id>
|
||||||
|
```
|
||||||
|
|
||||||
|
where `<node_id>` corresponds to the identifier of the node shown by `garage status` (first column).
|
||||||
|
You can enter simply a prefix of that identifier.
|
||||||
|
For instance here you could write just `garage node configure -z dc1 -c 1 2a63`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Creating buckets and keys
|
||||||
|
|
||||||
|
In this section, we will suppose that we want to create a bucket named `nextcloud-bucket`
|
||||||
|
that will be accessed through a key named `nextcloud-app-key`.
|
||||||
|
|
||||||
|
Don't forget that `help` command and `--help` subcommands can help you anywhere,
|
||||||
|
the CLI tool is self-documented! Two examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
garage help
|
||||||
|
garage bucket allow --help
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Create a bucket
|
||||||
|
|
||||||
|
Let's take an example where we want to deploy NextCloud using Garage as the
|
||||||
|
main data storage.
|
||||||
|
|
||||||
|
First, create a bucket with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
garage bucket create nextcloud-bucket
|
||||||
|
```
|
||||||
|
|
||||||
|
Check that everything went well:
|
||||||
|
|
||||||
|
```
|
||||||
|
garage bucket list
|
||||||
|
garage bucket info nextcloud-bucket
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Create an API key
|
||||||
|
|
||||||
|
The `nextcloud-bucket` bucket now exists on the Garage server,
|
||||||
|
however it cannot be accessed until we add an API key with the proper access rights.
|
||||||
|
|
||||||
|
Note that API keys are independent of buckets:
|
||||||
|
one key can access multiple buckets, multiple keys can access one bucket.
|
||||||
|
|
||||||
|
Create an API key using the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
garage key new --name nextcloud-app-key
|
||||||
|
```
|
||||||
|
|
||||||
|
The output should look as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
Key name: nextcloud-app-key
|
||||||
|
Key ID: GK3515373e4c851ebaad366558
|
||||||
|
Secret key: 7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34
|
||||||
|
Authorized buckets:
|
||||||
|
```
|
||||||
|
|
||||||
|
Check that everything works as intended:
|
||||||
|
|
||||||
|
```
|
||||||
|
garage key list
|
||||||
|
garage key info nextcloud-app-key
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Allow a key to access a bucket
|
||||||
|
|
||||||
|
Now that we have a bucket and a key, we need to give permissions to the key on the bucket:
|
||||||
|
|
||||||
|
```
|
||||||
|
garage bucket allow \
|
||||||
|
--read \
|
||||||
|
--write
|
||||||
|
nextcloud-bucket \
|
||||||
|
--key nextcloud-app-key
|
||||||
|
```
|
||||||
|
|
||||||
|
You can check at any time the allowed keys on your bucket with:
|
||||||
|
|
||||||
|
```
|
||||||
|
garage bucket info nextcloud-bucket
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Uploading and downlading from Garage
|
||||||
|
|
||||||
|
We recommend the use of MinIO Client to interact with Garage files (`mc`).
|
||||||
|
Instructions to install it and use it are provided on the
|
||||||
|
[MinIO website](https://docs.min.io/docs/minio-client-quickstart-guide.html).
|
||||||
|
Before reading the following, you need a working `mc` command on your path.
|
||||||
|
|
||||||
|
Note that on certain Linux distributions such as Arch Linux, the Minio client binary
|
||||||
|
is called `mcli` instead of `mc` (to avoid name clashes with the Midnight Commander).
|
||||||
|
|
||||||
|
#### Configure `mc`
|
||||||
|
|
||||||
|
You need your access key and secret key created above.
|
||||||
|
We will assume you are invoking `mc` on the same machine as the Garage server,
|
||||||
|
your S3 API endpoint is therefore `http://127.0.0.1:3900`.
|
||||||
|
For this whole configuration, you must set an alias name: we chose `my-garage`, that you will used for all commands.
|
||||||
|
|
||||||
|
Adapt the following command accordingly and run it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mc alias set \
|
||||||
|
my-garage \
|
||||||
|
http://127.0.0.1:3900 \
|
||||||
|
<access key> \
|
||||||
|
<secret key> \
|
||||||
|
--api S3v4
|
||||||
|
```
|
||||||
|
|
||||||
|
You must also add an environment variable to your configuration to
|
||||||
|
inform MinIO of our region (`garage` by default, corresponding to the `s3_region` parameter
|
||||||
|
in the configuration file).
|
||||||
|
The best way is to add the following snippet to your `$HOME/.bash_profile`
|
||||||
|
or `$HOME/.bashrc` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export MC_REGION=garage
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Use `mc`
|
||||||
|
|
||||||
|
You can not list buckets from `mc` currently.
|
||||||
|
|
||||||
|
But the following commands and many more should work:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mc cp image.png my-garage/nextcloud-bucket
|
||||||
|
mc cp my-garage/nextcloud-bucket/image.png .
|
||||||
|
mc ls my-garage/nextcloud-bucket
|
||||||
|
mc mirror localdir/ my-garage/another-bucket
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Other tools for interacting with Garage
|
||||||
|
|
||||||
|
The following tools can also be used to send and recieve files from/to Garage:
|
||||||
|
|
||||||
|
- the [AWS CLI](https://aws.amazon.com/cli/)
|
||||||
|
- [`rclone`](https://rclone.org/)
|
||||||
|
- [Cyberduck](https://cyberduck.io/)
|
||||||
|
- [`s3cmd`](https://s3tools.org/s3cmd)
|
||||||
|
|
||||||
|
Refer to the ["configuring clients"](../cookbook/clients.md) page to learn how to configure
|
||||||
|
these clients to interact with a Garage server.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Garage CLI
|
||||||
|
|
||||||
|
The Garage CLI is mostly self-documented. Make use of the `help` subcommand
|
||||||
|
and the `--help` flag to discover all available options.
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
# Garage configuration file format reference
|
||||||
|
|
||||||
|
Here is an example `garage.toml` configuration file that illustrates all of the possible options:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
metadata_dir = "/var/lib/garage/meta"
|
||||||
|
data_dir = "/var/lib/garage/data"
|
||||||
|
|
||||||
|
block_size = 1048576
|
||||||
|
|
||||||
|
replication_mode = "3"
|
||||||
|
|
||||||
|
rpc_bind_addr = "[::]:3901"
|
||||||
|
|
||||||
|
bootstrap_peers = [
|
||||||
|
"[fc00:1::1]:3901",
|
||||||
|
"[fc00:1::2]:3901",
|
||||||
|
"[fc00:B::1]:3901",
|
||||||
|
"[fc00:F::1]:3901",
|
||||||
|
]
|
||||||
|
|
||||||
|
consul_host = "consul.service"
|
||||||
|
consul_service_name = "garage-daemon"
|
||||||
|
|
||||||
|
max_concurrent_rpc_requests = 12
|
||||||
|
|
||||||
|
sled_cache_capacity = 134217728
|
||||||
|
sled_flush_every_ms = 2000
|
||||||
|
|
||||||
|
[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"
|
||||||
|
```
|
||||||
|
|
||||||
|
The following gives details about each available configuration option.
|
||||||
|
|
||||||
|
## Available configuration options
|
||||||
|
|
||||||
|
#### `metadata_dir`
|
||||||
|
|
||||||
|
The directory in which Garage will store its metadata. This contains the node identifier,
|
||||||
|
the network configuration and the peer list, the list of buckets and keys as well
|
||||||
|
as the index of all objects, object version and object blocks.
|
||||||
|
|
||||||
|
Store this folder on a fast SSD drive if possible to maximize Garage's performance.
|
||||||
|
|
||||||
|
#### `data_dir`
|
||||||
|
|
||||||
|
The directory in which Garage will store the data blocks of objects.
|
||||||
|
This folder can be placed on an HDD. The space available for `data_dir`
|
||||||
|
should be counted to determine a node's capacity
|
||||||
|
when [configuring it](../getting_started/05_cluster.md).
|
||||||
|
|
||||||
|
#### `block_size`
|
||||||
|
|
||||||
|
Garage splits stored objects in consecutive chunks of size `block_size` (except the last
|
||||||
|
one which might be standard). The default size is 1MB and should work in most cases.
|
||||||
|
If you are interested in tuning this, feel free to do so (and remember to report your
|
||||||
|
findings to us!)
|
||||||
|
|
||||||
|
#### `replication_mode`
|
||||||
|
|
||||||
|
Garage supports the following replication modes:
|
||||||
|
|
||||||
|
- `none` or `1`: data stored on Garage is stored on a single node. There is no redundancy,
|
||||||
|
and data will be unavailable as soon as one node fails or its network is disconnected.
|
||||||
|
Do not use this for anything else than test deployments.
|
||||||
|
|
||||||
|
- `2`: data stored on Garage will be stored on two different nodes, if possible in different
|
||||||
|
zones. Garage tolerates one node failure before losing data. Data should be available
|
||||||
|
read-only when one node is down, but write operations will fail.
|
||||||
|
Use this only if you really have to.
|
||||||
|
|
||||||
|
- `3`: data stored on Garage will be stored on three different nodes, if possible each in
|
||||||
|
a different zones.
|
||||||
|
Garage tolerates two node failure before losing data. Data should be available
|
||||||
|
read-only when two nodes are down, and writes should be possible if only a single node
|
||||||
|
is down.
|
||||||
|
|
||||||
|
Note that in modes `2` and `3`,
|
||||||
|
if at least the same number of zones are available, an arbitrary number of failures in
|
||||||
|
any given zone is tolerated as copies of data will be spread over several zones.
|
||||||
|
|
||||||
|
**Make sure `replication_mode` is the same in the configuration files of all nodes.
|
||||||
|
Never run a Garage cluster where that is not the case.**
|
||||||
|
|
||||||
|
Changing the `replication_mode` of a cluster might work (make sure to shut down all nodes
|
||||||
|
and changing it everywhere at the time), but is not officially supported.
|
||||||
|
|
||||||
|
#### `rpc_bind_addr`
|
||||||
|
|
||||||
|
The address and port on which to bind for inter-cluster communcations
|
||||||
|
(reffered to as RPC for remote procedure calls).
|
||||||
|
The port specified here should be the same one that other nodes will used to contact
|
||||||
|
the node, even in the case of a NAT: the NAT should be configured to forward the external
|
||||||
|
port number to the same internal port nubmer. This means that if you have several nodes running
|
||||||
|
behind a NAT, they should each use a different RPC port number.
|
||||||
|
|
||||||
|
#### `bootstrap_peers`
|
||||||
|
|
||||||
|
A list of IPs and ports on which to contact other Garage peers of this cluster.
|
||||||
|
This should correspond to the RPC ports set up with `rpc_bind_addr`.
|
||||||
|
|
||||||
|
#### `consul_host` and `consul_service_name`
|
||||||
|
|
||||||
|
Garage supports discovering other nodes of the cluster using Consul.
|
||||||
|
This works only when nodes are announced in Consul by an orchestrator such as Nomad,
|
||||||
|
as Garage is not able to announce itself.
|
||||||
|
|
||||||
|
The `consul_host` parameter should be set to the hostname of the Consul server,
|
||||||
|
and `consul_service_name` should be set to the service name under which Garage's
|
||||||
|
RPC ports are announced.
|
||||||
|
|
||||||
|
#### `max_concurrent_rpc_requests`
|
||||||
|
|
||||||
|
Garage implements rate limiting for RPC requests: no more than
|
||||||
|
`max_concurrent_rpc_requests` concurrent outbound RPC requests will be made
|
||||||
|
by a Garage node (additionnal requests will be put in a waiting queue).
|
||||||
|
|
||||||
|
#### `sled_cache_capacity`
|
||||||
|
|
||||||
|
This parameter can be used to tune the capacity of the cache used by
|
||||||
|
[sled](https://sled.rs), the database Garage uses internally to store metadata.
|
||||||
|
Tune this to fit the RAM you wish to make available to your Garage instance.
|
||||||
|
More cache means faster Garage, but the default value (128MB) should be plenty
|
||||||
|
for most use cases.
|
||||||
|
|
||||||
|
#### `sled_flush_every_ms`
|
||||||
|
|
||||||
|
This parameters can be used to tune the flushing interval of sled.
|
||||||
|
Increase this if sled is thrashing your SSD, at the risk of losing more data in case
|
||||||
|
of a power outage (though this should not matter much as data is replicated on other
|
||||||
|
nodes). The default value, 2000ms, should be appropriate for most use cases.
|
||||||
|
|
||||||
|
|
||||||
|
## The `[rpc_tls]` section
|
||||||
|
|
||||||
|
This section should be used to configure the TLS certificates used to encrypt
|
||||||
|
intra-cluster traffic (RPC traffic). The following parameters should be set:
|
||||||
|
|
||||||
|
- `ca_cert`: the certificate of the CA that is allowed to sign individual node certificates
|
||||||
|
- `node_cert`: the node certificate for the current node
|
||||||
|
- `node_key`: the key associated with the node certificate
|
||||||
|
|
||||||
|
Note tha several nodes may use the same node certificate, as long as it is signed
|
||||||
|
by the CA.
|
||||||
|
|
||||||
|
If this section is absent, TLS is not used to encrypt intra-cluster traffic.
|
||||||
|
|
||||||
|
|
||||||
|
## The `[s3_api]` section
|
||||||
|
|
||||||
|
#### `api_bind_addr`
|
||||||
|
|
||||||
|
The IP and port on which to bind for accepting S3 API calls.
|
||||||
|
This endpoint does not suport TLS: a reverse proxy should be used to provide it.
|
||||||
|
|
||||||
|
#### `s3_region`
|
||||||
|
|
||||||
|
Garage will accept S3 API calls that are targetted to the S3 region defined here.
|
||||||
|
API calls targetted to other regions will fail with a AuthorizationHeaderMalformed error
|
||||||
|
message that redirects the client to the correct region.
|
||||||
|
|
||||||
|
|
||||||
|
## The `[s3_web]` section
|
||||||
|
|
||||||
|
Garage allows to publish content of buckets as websites. This section configures the
|
||||||
|
behaviour of this module.
|
||||||
|
|
||||||
|
#### `bind_addr`
|
||||||
|
|
||||||
|
The IP and port on which to bind for accepting HTTP requests to buckets configured
|
||||||
|
for website access.
|
||||||
|
This endpoint does not suport TLS: a reverse proxy should be used to provide it.
|
||||||
|
|
||||||
|
#### `root_domain`
|
||||||
|
|
||||||
|
The optionnal suffix appended to bucket names for the corresponding HTTP Host.
|
||||||
|
|
||||||
|
For instance, if `root_domain` is `web.garage.eu`, a bucket called `deuxfleurs.fr`
|
||||||
|
will be accessible either with hostname `deuxfleurs.fr.web.garage.eu`
|
||||||
|
or with hostname `deuxfleurs.fr`.
|
||||||
|
|
||||||
|
#### `index`
|
||||||
|
|
||||||
|
The name of the index file to return for requests ending with `/` (usually `index.html`).
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
## S3 Compatibility status
|
# S3 Compatibility status
|
||||||
|
|
||||||
### Global S3 features
|
## Global S3 features
|
||||||
|
|
||||||
Implemented:
|
Implemented:
|
||||||
|
|
||||||
@@ -14,11 +14,12 @@ Not implemented:
|
|||||||
|
|
||||||
- vhost-style URLs (`bucket.garage.tld/key`)
|
- vhost-style URLs (`bucket.garage.tld/key`)
|
||||||
- object-level ACL
|
- object-level ACL
|
||||||
|
- object versioning
|
||||||
- encryption
|
- encryption
|
||||||
- most `x-amz-` headers
|
- most `x-amz-` headers
|
||||||
|
|
||||||
|
|
||||||
### Endpoint implementation
|
## Endpoint implementation
|
||||||
|
|
||||||
All APIs that are not mentionned are not implemented and will return a 400 bad request.
|
All APIs that are not mentionned are not implemented and will return a 400 bad request.
|
||||||
|
|
||||||
@@ -54,6 +55,15 @@ Implemented.
|
|||||||
|
|
||||||
Implemented.
|
Implemented.
|
||||||
|
|
||||||
|
#### GetBucketLocation
|
||||||
|
|
||||||
|
Implemented.
|
||||||
|
|
||||||
|
#### GetBucketVersioning
|
||||||
|
|
||||||
|
Stub implementation (Garage does not yet support versionning so this always returns
|
||||||
|
"versionning not enabled").
|
||||||
|
|
||||||
#### GetObject
|
#### GetObject
|
||||||
|
|
||||||
Implemented.
|
Implemented.
|
||||||
@@ -66,6 +76,10 @@ Implemented.
|
|||||||
|
|
||||||
Implemented.
|
Implemented.
|
||||||
|
|
||||||
|
#### ListBuckets
|
||||||
|
|
||||||
|
Implemented.
|
||||||
|
|
||||||
#### ListObjects
|
#### ListObjects
|
||||||
|
|
||||||
Implemented, but there isn't a very good specification of what `encoding-type=url` covers so there might be some encoding bugs. In our implementation the url-encoded fields are in the same in ListObjects as they are in ListObjectsV2.
|
Implemented, but there isn't a very good specification of what `encoding-type=url` covers so there might be some encoding bugs. In our implementation the url-encoded fields are in the same in ListObjects as they are in ListObjectsV2.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
## Load Balancing Data (planned for version 0.2)
|
# Load Balancing Data (planned for version 0.2)
|
||||||
|
|
||||||
I have conducted a quick study of different methods to load-balance data over different Garage nodes using consistent hashing.
|
I have conducted a quick study of different methods to load-balance data over different Garage nodes using consistent hashing.
|
||||||
|
|
||||||
### Requirements
|
## Requirements
|
||||||
|
|
||||||
- *good balancing*: two nodes that have the same announced capacity should receive close to the same number of items
|
- *good balancing*: two nodes that have the same announced capacity should receive close to the same number of items
|
||||||
|
|
||||||
@@ -15,9 +15,9 @@ I have conducted a quick study of different methods to load-balance data over di
|
|||||||
replicas, independently of the order in which nodes were added/removed (this
|
replicas, independently of the order in which nodes were added/removed (this
|
||||||
is to keep the implementation simple)
|
is to keep the implementation simple)
|
||||||
|
|
||||||
### Methods
|
## Methods
|
||||||
|
|
||||||
#### Naive multi-DC ring walking strategy
|
### Naive multi-DC ring walking strategy
|
||||||
|
|
||||||
This strategy can be used with any ring-like algorithm to make it aware of the *multi-datacenter* requirement:
|
This strategy can be used with any ring-like algorithm to make it aware of the *multi-datacenter* requirement:
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ This method was implemented in the first version of Garage, with the basic
|
|||||||
ring construction from Dynamo DB that consists in associating `n_token` random positions to
|
ring construction from Dynamo DB that consists in associating `n_token` random positions to
|
||||||
each node (I know it's not optimal, the Dynamo paper already studies this).
|
each node (I know it's not optimal, the Dynamo paper already studies this).
|
||||||
|
|
||||||
#### Better rings
|
### Better rings
|
||||||
|
|
||||||
The ring construction that selects `n_token` random positions for each nodes gives a ring of positions that
|
The ring construction that selects `n_token` random positions for each nodes gives a ring of positions that
|
||||||
is not well-balanced: the space between the tokens varies a lot, and some partitions are thus bigger than others.
|
is not well-balanced: the space between the tokens varies a lot, and some partitions are thus bigger than others.
|
||||||
@@ -150,7 +150,7 @@ removing grisou gipsie : 49.22% 36.52% 12.79% 1.46%
|
|||||||
on average: 62.94% 27.89% 8.61% 0.57% <-- WORSE THAN PREVIOUSLY
|
on average: 62.94% 27.89% 8.61% 0.57% <-- WORSE THAN PREVIOUSLY
|
||||||
```
|
```
|
||||||
|
|
||||||
#### The magical solution: multi-DC aware MagLev
|
### The magical solution: multi-DC aware MagLev
|
||||||
|
|
||||||
Suppose we want to select three replicas for each partition (this is what we do in our simulation and in most Garage deployments).
|
Suppose we want to select three replicas for each partition (this is what we do in our simulation and in most Garage deployments).
|
||||||
We apply MagLev three times consecutively, one for each replica selection.
|
We apply MagLev three times consecutively, one for each replica selection.
|
||||||
|
|||||||
+3
-3
@@ -11,7 +11,7 @@ cd pki
|
|||||||
# the RPC protocol will use to authenticate the other side.
|
# the RPC protocol will use to authenticate the other side.
|
||||||
if [ ! -f garage-ca.key ]; then
|
if [ ! -f garage-ca.key ]; then
|
||||||
echo "Generating Garage CA keys..."
|
echo "Generating Garage CA keys..."
|
||||||
openssl genrsa -out garage-ca.key 4096
|
openssl genpkey -algorithm ED25519 -out garage-ca.key
|
||||||
openssl req -x509 -new -nodes -key garage-ca.key -sha256 -days 3650 -out garage-ca.crt -subj "/C=FR/O=Garage"
|
openssl req -x509 -new -nodes -key garage-ca.key -sha256 -days 3650 -out garage-ca.crt -subj "/C=FR/O=Garage"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ fi
|
|||||||
if [ ! -f garage.crt ]; then
|
if [ ! -f garage.crt ]; then
|
||||||
echo "Generating Garage agent keys..."
|
echo "Generating Garage agent keys..."
|
||||||
if [ ! -f garage.key ]; then
|
if [ ! -f garage.key ]; then
|
||||||
openssl genrsa -out garage.key 4096
|
openssl genpkey -algorithm ED25519 -out garage.key
|
||||||
fi
|
fi
|
||||||
openssl req -new -sha256 -key garage.key -subj "/C=FR/O=Garage/CN=garage" \
|
openssl req -new -sha256 -key garage.key -subj "/C=FR/O=Garage/CN=garage" \
|
||||||
-out garage.csr
|
-out garage.csr
|
||||||
@@ -56,7 +56,7 @@ fi
|
|||||||
if [ ! -f garage-client.crt ]; then
|
if [ ! -f garage-client.crt ]; then
|
||||||
echo "Generating Garage client keys..."
|
echo "Generating Garage client keys..."
|
||||||
if [ ! -f garage-client.key ]; then
|
if [ ! -f garage-client.key ]; then
|
||||||
openssl genrsa -out garage-client.key 4096
|
openssl genpkey -algorithm ED25519 -out garage-client.key
|
||||||
fi
|
fi
|
||||||
openssl req -new -sha256 -key garage-client.key -subj "/C=FR/O=Garage" \
|
openssl req -new -sha256 -key garage-client.key -subj "/C=FR/O=Garage" \
|
||||||
-out garage-client.csr
|
-out garage-client.csr
|
||||||
|
|||||||
+60
-23
@@ -1,3 +1,4 @@
|
|||||||
|
use std::cmp::max;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -125,7 +126,7 @@ pub enum BucketOperation {
|
|||||||
#[structopt(name = "allow")]
|
#[structopt(name = "allow")]
|
||||||
Allow(PermBucketOpt),
|
Allow(PermBucketOpt),
|
||||||
|
|
||||||
/// Allow key to read or write to bucket
|
/// Deny key from reading or writing to bucket
|
||||||
#[structopt(name = "deny")]
|
#[structopt(name = "deny")]
|
||||||
Deny(PermBucketOpt),
|
Deny(PermBucketOpt),
|
||||||
|
|
||||||
@@ -338,22 +339,45 @@ pub async fn cmd_status(
|
|||||||
resp => return Err(Error::Message(format!("Invalid RPC response: {:?}", resp))),
|
resp => return Err(Error::Message(format!("Invalid RPC response: {:?}", resp))),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (hostname_len, addr_len, tag_len, zone_len) = status
|
||||||
|
.iter()
|
||||||
|
.map(|adv| (adv, config.members.get(&adv.id)))
|
||||||
|
.map(|(adv, cfg)| {
|
||||||
|
(
|
||||||
|
adv.state_info.hostname.len(),
|
||||||
|
adv.addr.to_string().len(),
|
||||||
|
cfg.map(|c| c.tag.len()).unwrap_or(0),
|
||||||
|
cfg.map(|c| c.zone.len()).unwrap_or(0),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.fold((0, 0, 0, 0), |(h, a, t, z), (mh, ma, mt, mz)| {
|
||||||
|
(max(h, mh), max(a, ma), max(t, mt), max(z, mz))
|
||||||
|
});
|
||||||
|
|
||||||
println!("Healthy nodes:");
|
println!("Healthy nodes:");
|
||||||
for adv in status.iter().filter(|x| x.is_up) {
|
for adv in status.iter().filter(|x| x.is_up) {
|
||||||
if let Some(cfg) = config.members.get(&adv.id) {
|
if let Some(cfg) = config.members.get(&adv.id) {
|
||||||
println!(
|
println!(
|
||||||
"{:?}\t{}\t{}\t[{}]\t{}\t{}",
|
"{id:?}\t{host}{h_pad}\t{addr}{a_pad}\t[{tag}]{t_pad}\t{zone}{z_pad}\t{capacity}",
|
||||||
adv.id,
|
id = adv.id,
|
||||||
adv.state_info.hostname,
|
host = adv.state_info.hostname,
|
||||||
adv.addr,
|
addr = adv.addr,
|
||||||
cfg.tag,
|
tag = cfg.tag,
|
||||||
cfg.zone,
|
zone = cfg.zone,
|
||||||
cfg.capacity_string()
|
capacity = cfg.capacity_string(),
|
||||||
|
h_pad = " ".repeat(hostname_len - adv.state_info.hostname.len()),
|
||||||
|
a_pad = " ".repeat(addr_len - adv.addr.to_string().len()),
|
||||||
|
t_pad = " ".repeat(tag_len - cfg.tag.len()),
|
||||||
|
z_pad = " ".repeat(zone_len - cfg.zone.len()),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"{:?}\t{}\t{}\tUNCONFIGURED/REMOVED",
|
"{id:?}\t{h}{h_pad}\t{addr}{a_pad}\tUNCONFIGURED/REMOVED",
|
||||||
adv.id, adv.state_info.hostname, adv.addr
|
id = adv.id,
|
||||||
|
h = adv.state_info.hostname,
|
||||||
|
addr = adv.addr,
|
||||||
|
h_pad = " ".repeat(hostname_len - adv.state_info.hostname.len()),
|
||||||
|
a_pad = " ".repeat(addr_len - adv.addr.to_string().len()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,25 +393,38 @@ pub async fn cmd_status(
|
|||||||
for adv in status.iter().filter(|x| !x.is_up) {
|
for adv in status.iter().filter(|x| !x.is_up) {
|
||||||
if let Some(cfg) = config.members.get(&adv.id) {
|
if let Some(cfg) = config.members.get(&adv.id) {
|
||||||
println!(
|
println!(
|
||||||
"{:?}\t{}\t{}\t[{}]\t{}\t{}\tlast seen: {}s ago",
|
"{id:?}\t{host}{h_pad}\t{addr}{a_pad}\t[{tag}]{t_pad}\t{zone}{z_pad}\t{capacity}\tlast seen: {last_seen}s ago",
|
||||||
adv.id,
|
id=adv.id,
|
||||||
adv.state_info.hostname,
|
host=adv.state_info.hostname,
|
||||||
adv.addr,
|
addr=adv.addr,
|
||||||
cfg.tag,
|
tag=cfg.tag,
|
||||||
cfg.zone,
|
zone=cfg.zone,
|
||||||
cfg.capacity_string(),
|
capacity=cfg.capacity_string(),
|
||||||
(now_msec() - adv.last_seen) / 1000,
|
last_seen=(now_msec() - adv.last_seen) / 1000,
|
||||||
|
h_pad=" ".repeat(hostname_len - adv.state_info.hostname.len()),
|
||||||
|
a_pad=" ".repeat(addr_len - adv.addr.to_string().len()),
|
||||||
|
t_pad=" ".repeat(tag_len - cfg.tag.len()),
|
||||||
|
z_pad=" ".repeat(zone_len - cfg.zone.len()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let (tag_len, zone_len) = config
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.filter(|(&id, _)| !status.iter().any(|x| x.id == id))
|
||||||
|
.map(|(_, cfg)| (cfg.tag.len(), cfg.zone.len()))
|
||||||
|
.fold((0, 0), |(t, z), (mt, mz)| (max(t, mt), max(z, mz)));
|
||||||
|
|
||||||
for (id, cfg) in config.members.iter() {
|
for (id, cfg) in config.members.iter() {
|
||||||
if !status.iter().any(|x| x.id == *id) {
|
if !status.iter().any(|x| x.id == *id) {
|
||||||
println!(
|
println!(
|
||||||
"{:?}\t{}\t{}\t{}\tnever seen",
|
"{id:?}\t{tag}{t_pad}\t{zone}{z_pad}\t{capacity}\tnever seen",
|
||||||
id,
|
id = id,
|
||||||
cfg.tag,
|
tag = cfg.tag,
|
||||||
cfg.zone,
|
zone = cfg.zone,
|
||||||
cfg.capacity_string(),
|
capacity = cfg.capacity_string(),
|
||||||
|
t_pad = " ".repeat(tag_len - cfg.tag.len()),
|
||||||
|
z_pad = " ".repeat(zone_len - cfg.zone.len()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -28,7 +28,7 @@ use cli::*;
|
|||||||
#[structopt(name = "garage")]
|
#[structopt(name = "garage")]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
/// RPC connect to this host to execute client operations
|
/// RPC connect to this host to execute client operations
|
||||||
#[structopt(short = "h", long = "rpc-host", default_value = "127.0.0.1:3901")]
|
#[structopt(short = "h", long = "rpc-host", default_value = "127.0.0.1:3901", parse(try_from_str = parse_address))]
|
||||||
pub rpc_host: SocketAddr,
|
pub rpc_host: SocketAddr,
|
||||||
|
|
||||||
#[structopt(long = "ca-cert")]
|
#[structopt(long = "ca-cert")]
|
||||||
@@ -87,3 +87,12 @@ async fn cli_command(opt: Opt) -> Result<(), Error> {
|
|||||||
|
|
||||||
cli_cmd(opt.cmd, membership_rpc_cli, admin_rpc_cli, opt.rpc_host).await
|
cli_cmd(opt.cmd, membership_rpc_cli, admin_rpc_cli, opt.rpc_host).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_address(address: &str) -> Result<SocketAddr, String> {
|
||||||
|
use std::net::ToSocketAddrs;
|
||||||
|
address
|
||||||
|
.to_socket_addrs()
|
||||||
|
.map_err(|_| format!("Could not resolve {}", address))?
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| format!("Could not resolve {}", address))
|
||||||
|
}
|
||||||
|
|||||||
@@ -141,7 +141,10 @@ impl StatusEntry {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct StateInfo {
|
pub struct StateInfo {
|
||||||
|
/// Hostname of the node
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
|
/// Replication factor configured on the node
|
||||||
|
pub replication_factor: Option<usize>, // TODO Option is just for retrocompatibility. It should become a simple usize at some point
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Status {
|
impl Status {
|
||||||
@@ -269,6 +272,7 @@ impl System {
|
|||||||
hostname: gethostname::gethostname()
|
hostname: gethostname::gethostname()
|
||||||
.into_string()
|
.into_string()
|
||||||
.unwrap_or_else(|_| "<invalid utf-8>".to_string()),
|
.unwrap_or_else(|_| "<invalid utf-8>".to_string()),
|
||||||
|
replication_factor: Some(replication_factor),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ring = Ring::new(net_config, replication_factor);
|
let ring = Ring::new(net_config, replication_factor);
|
||||||
@@ -504,6 +508,7 @@ impl System {
|
|||||||
let update_lock = self.update_lock.lock().await;
|
let update_lock = self.update_lock.lock().await;
|
||||||
let mut status: Status = self.status.borrow().as_ref().clone();
|
let mut status: Status = self.status.borrow().as_ref().clone();
|
||||||
let mut has_changed = false;
|
let mut has_changed = false;
|
||||||
|
let mut max_replication_factor = 0;
|
||||||
|
|
||||||
for node in adv.iter() {
|
for node in adv.iter() {
|
||||||
if node.id == self.id {
|
if node.id == self.id {
|
||||||
@@ -529,11 +534,22 @@ impl System {
|
|||||||
// Case 2: the node might have changed address
|
// Case 2: the node might have changed address
|
||||||
Some(our_node) => node.is_up && !our_node.is_up() && our_node.addr != node.addr,
|
Some(our_node) => node.is_up && !our_node.is_up() && our_node.addr != node.addr,
|
||||||
};
|
};
|
||||||
|
max_replication_factor = std::cmp::max(
|
||||||
|
max_replication_factor,
|
||||||
|
node.state_info.replication_factor.unwrap_or_default(),
|
||||||
|
);
|
||||||
if ping_them {
|
if ping_them {
|
||||||
to_ping.push((node.addr, Some(node.id)));
|
to_ping.push((node.addr, Some(node.id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.replication_factor < max_replication_factor {
|
||||||
|
error!("Some node have a higher replication factor ({}) than this one ({}). This is not supported and might lead to bugs",
|
||||||
|
max_replication_factor,
|
||||||
|
self.replication_factor);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
if has_changed {
|
if has_changed {
|
||||||
status.recalculate_hash();
|
status.recalculate_hash();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,9 +279,13 @@ impl RpcHttpClient {
|
|||||||
tls_config: &Option<TlsConfig>,
|
tls_config: &Option<TlsConfig>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let method = if let Some(cf) = tls_config {
|
let method = if let Some(cf) = tls_config {
|
||||||
let ca_certs = tls_util::load_certs(&cf.ca_cert)?;
|
let ca_certs = tls_util::load_certs(&cf.ca_cert).map_err(|e| {
|
||||||
let node_certs = tls_util::load_certs(&cf.node_cert)?;
|
Error::Message(format!("Failed to open CA certificate file: {:?}", e))
|
||||||
let node_key = tls_util::load_private_key(&cf.node_key)?;
|
})?;
|
||||||
|
let node_certs = tls_util::load_certs(&cf.node_cert)
|
||||||
|
.map_err(|e| Error::Message(format!("Failed to open certificate file: {:?}", e)))?;
|
||||||
|
let node_key = tls_util::load_private_key(&cf.node_key)
|
||||||
|
.map_err(|e| Error::Message(format!("Failed to open private key file: {:?}", e)))?;
|
||||||
|
|
||||||
let mut config = rustls::ClientConfig::new();
|
let mut config = rustls::ClientConfig::new();
|
||||||
|
|
||||||
|
|||||||
+9
-8
@@ -38,15 +38,16 @@ pub fn load_certs(filename: &str) -> Result<Vec<rustls::Certificate>, Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_private_key(filename: &str) -> Result<rustls::PrivateKey, Error> {
|
pub fn load_private_key(filename: &str) -> Result<rustls::PrivateKey, Error> {
|
||||||
let keyfile = fs::File::open(&filename)?;
|
let keydata = fs::read_to_string(filename)?;
|
||||||
let mut reader = io::BufReader::new(keyfile);
|
|
||||||
|
|
||||||
let keys = pemfile::rsa_private_keys(&mut reader).map_err(|_| {
|
let mut buf1 = keydata.as_bytes();
|
||||||
Error::Message(format!(
|
let rsa_keys = pemfile::rsa_private_keys(&mut buf1).unwrap_or_default();
|
||||||
"Could not decode private key from file: {}",
|
|
||||||
filename
|
let mut buf2 = keydata.as_bytes();
|
||||||
))
|
let pkcs8_keys = pemfile::pkcs8_private_keys(&mut buf2).unwrap_or_default();
|
||||||
})?;
|
|
||||||
|
let mut keys = rsa_keys;
|
||||||
|
keys.extend(pkcs8_keys.into_iter());
|
||||||
|
|
||||||
if keys.len() != 1 {
|
if keys.len() != 1 {
|
||||||
return Err(Error::Message(format!(
|
return Err(Error::Message(format!(
|
||||||
|
|||||||
Reference in New Issue
Block a user