Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend docs with section on single node and cluster setups #1338

Merged
merged 7 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ overview of all settings configurable through the CLI.
| `Worker.Enabled` | Enables/disables worker | `true` | `--worker.enabled` | `RENTERD_WORKER_ENABLED` | `worker.enabled` |
| `Worker.AllowUnauthenticatedDownloads` | Allows unauthenticated downloads | - | `--worker.unauthenticatedDownloads` | `RENTERD_WORKER_UNAUTHENTICATED_DOWNLOADS` | `worker.allowUnauthenticatedDownloads` |
| `Worker.ExternalAddress` | Address of the worker on the network, only necessary when the bus is remote | - | - | `RENTERD_WORKER_EXTERNAL_ADDR` | `worker.externalAddress` |
| `Worker.RemoteAddrs` | List of remote worker addresses (semicolon delimited) | - | - | `RENTERD_WORKER_REMOTE_ADDRS` | `worker.remotes` |
| `Worker.RemotePassword` | API password for the remote workers | - | - | `RENTERD_WORKER_API_PASSWORD` | `worker.remotes` |
| `Autopilot.Enabled` | Enables/disables autopilot | `true` | `--autopilot.enabled` | `RENTERD_AUTOPILOT_ENABLED` | `autopilot.enabled` |
| `Autopilot.AccountsRefillInterval` | Interval for refilling workers' account balances | `24h` | `--autopilot.accountRefillInterval` | - | `autopilot.accountsRefillInterval` |
| `Autopilot.Heartbeat` | Interval for autopilot loop execution | `30m` | `--autopilot.heartbeat` | - | `autopilot.heartbeat` |
| `Autopilot.MigrationHealthCutoff` | Threshold for migrating slabs based on health | `0.75` | `--autopilot.migrationHealthCutoff` | - | `autopilot.migrationHealthCutoff` |
Expand All @@ -100,6 +103,113 @@ overview of all settings configurable through the CLI.
| `S3.HostBucketEnabled` | Enables bucket rewriting in the router | - | `--s3.hostBucketEnabled` | `RENTERD_S3_HOST_BUCKET_ENABLED` | `s3.hostBucketEnabled` |
| `S3.KeypairsV4 (DEPRECATED)` | V4 keypairs for S3 | - | - | - | `s3.keypairsV4` |

### Single-Node Setup

A single-node setup involves running all components (bus, worker, and autopilot)
on the same machine. This is ideal for testing, development, or small-scale
deployments. This setup is the default when running `renterd` without any flags.

### Cluster Setup

In a cluster setup, the bus, worker, and autopilot run on separate nodes. This
setup is ideal for large-scale deployments where you want to horizontally scale
your renter. The worker nodes can be spread across multiple machines, and the
autopilot can be run on a separate machine.

#### Bus Node Configuration

The bus is the only node that exposes the UI. To run the bus separately, the
autopilot and worker have to be disabled using the `--autopilot.enabled` and
`--worker.enabled` flags. The only other requirement to run a bus is the (walet)
seed.

#### Worker Node Configuration

To configure the worker as a standalone node, the autopilot has to be disabled
using the `--autopilot.enabled` flag, and the bus has to be disabled. There's no
flag to explicitly disable the `bus`, it's implied by configuring a remote
address for the bus using the `--bus.remoteAddr` and `--bus.remotePassword`
flags. When the bus is remote, the worker has to be configured with an external
address of the form `http://<worker-ip>:<port>`, on localhost however this can be
the same as the worker's HTTP address. The worker needs to know its location on
the network because it relies on some webhooks it needs to register with the
bus, which in turn needs to know how to reach the worker when certain events
occur. Therefor it is important to start the worker after the bus is reachable.

#### Autopilot Node Configuration

To run the autopilot separately, the worker has to be disabled using the
`--worker.enabled` flag. Similar to the worker, the autopilot has to be
configured with a remote bus for the node not to start a bus itself. Alongside
with knowing where the bus is located, the autopilot also has to be aware of the
workers. These remote workers can be configured through yaml under the option
`worker.remotes`, or through environment variables
(`RENTERD_WORKER_REMOTE_ADDRS` and `RENTERD_WORKER_API_PASSWORD`).

#### Example docker-compose with minimal configuration

```yaml
version: '3.9'

services:
bus:
image: ghcr.io/siafoundation/renterd:master
container_name: renterd_bus
environment:
- RENTERD_SEED=<enter seed here>
- RENTERD_API_PASSWORD=bus-pass
ports:
- "9980:9980"
- "9981:9981"

worker-1:
ChrisSchinnerl marked this conversation as resolved.
Show resolved Hide resolved
image: ghcr.io/siafoundation/renterd:master
container_name: renterd_worker-1
environment:
- RENTERD_AUTOPILOT_ENABLED=false
- RENTERD_SEED=<enter seed here>
- RENTERD_API_PASSWORD=worker-pass
- RENTERD_BUS_API_PASSWORD=bus-pass
- RENTERD_BUS_REMOTE_ADDR=http://bus:9980/api/bus
- RENTERD_WORKER_EXTERNAL_ADDR=http://worker-1:9980/api/worker
ports:
- "9982:9980"
- "8082:8080"
depends_on:
- bus

worker-2:
image: ghcr.io/siafoundation/renterd:master
container_name: renterd_worker-2
environment:
- RENTERD_SEED=<enter seed here>
- RENTERD_API_PASSWORD=worker-pass
- RENTERD_BUS_API_PASSWORD=bus-pass
- RENTERD_BUS_REMOTE_ADDR=http://bus:9980/api/bus
- RENTERD_WORKER_EXTERNAL_ADDR=http://worker-2:9980/api/worker
ports:
- "9983:9980"
- "8083:8080"
depends_on:
- bus

autopilot:
image: ghcr.io/siafoundation/renterd:master
container_name: renterd_autopilot
environment:
- RENTERD_API_PASSWORD=autopilot-pass
- RENTERD_BUS_API_PASSWORD=bus-pass
- RENTERD_BUS_REMOTE_ADDR=http://bus:9980/api/bus
- RENTERD_WORKER_API_PASSWORD=<worker-password>
- RENTERD_WORKER_REMOTE_ADDRS=http://worker-1:9980/api/worker;http://worker-2:9980/api/worker
ports:
- "9984:9980"
depends_on:
- bus
- worker-1
- worker-2
```

## Tweaking Performance

Depending on hardware specs, you can change the [configuration](#configuration)
Expand Down
12 changes: 5 additions & 7 deletions cmd/renterd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,19 +205,17 @@ func setSeedPhrase() {
// already set via environment variable or config file.
func setAPIPassword() {
// retry until a valid API password is entered
for {
for len(cfg.HTTP.Password) < 4 {
fmt.Println("Please choose a password for the renterd admin UI.")
fmt.Println("This password will be required to access the admin UI in your web browser.")
fmt.Println("(The password must be at least 4 characters.)")

cfg.HTTP.Password = readPasswordInput("Enter password")
if len(cfg.HTTP.Password) >= 4 {
break
if len(cfg.HTTP.Password) < 4 {
// invalid password, retry
fmt.Println(wrapANSI("\033[31m", "Password must be at least 4 characters!", "\033[0m"))
fmt.Println("")
}

// invalid password, retry
fmt.Println(wrapANSI("\033[31m", "Password must be at least 4 characters!", "\033[0m"))
fmt.Println("")
}
}

Expand Down
80 changes: 37 additions & 43 deletions cmd/renterd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"syscall"
"time"

"go.sia.tech/core/types"
"go.sia.tech/coreutils/wallet"
"go.sia.tech/jape"
"go.sia.tech/renterd/api"
Expand All @@ -33,7 +34,6 @@ import (
"go.sia.tech/web/renterd"
"go.uber.org/zap"
"golang.org/x/sys/cpu"
"golang.org/x/term"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -153,20 +153,6 @@ var (
disableStdin bool
)

func mustLoadAPIPassword() {
if cfg.HTTP.Password != "" {
return
}

fmt.Print("Enter API password: ")
pw, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Println()
if err != nil {
log.Fatal(err)
}
cfg.HTTP.Password = string(pw)
}

func mustParseWorkers(workers, password string) {
if workers == "" {
return
Expand Down Expand Up @@ -342,8 +328,6 @@ func main() {
}

// Overwrite flags from environment if set.
parseEnvVar("RENTERD_LOG_PATH", &cfg.Log.Path)

parseEnvVar("RENTERD_BUS_REMOTE_ADDR", &cfg.Bus.RemoteAddr)
parseEnvVar("RENTERD_BUS_API_PASSWORD", &cfg.Bus.RemotePassword)
parseEnvVar("RENTERD_BUS_GATEWAY_ADDR", &cfg.Bus.GatewayAddr)
Expand All @@ -359,10 +343,6 @@ func main() {
parseEnvVar("RENTERD_DB_LOGGER_LOG_LEVEL", &cfg.Log.Level)
parseEnvVar("RENTERD_DB_LOGGER_SLOW_THRESHOLD", &cfg.Database.Log.SlowThreshold)

var depWorkerRemotePassStr string
var depWorkerRemoteAddrsStr string
parseEnvVar("RENTERD_WORKER_REMOTE_ADDRS", &depWorkerRemoteAddrsStr)
parseEnvVar("RENTERD_WORKER_API_PASSWORD", &depWorkerRemotePassStr)
parseEnvVar("RENTERD_WORKER_ENABLED", &cfg.Worker.Enabled)
parseEnvVar("RENTERD_WORKER_ID", &cfg.Worker.ID)
parseEnvVar("RENTERD_WORKER_UNAUTHENTICATED_DOWNLOADS", &cfg.Worker.AllowUnauthenticatedDownloads)
Expand All @@ -380,6 +360,7 @@ func main() {
parseEnvVar("RENTERD_S3_HOST_BUCKET_ENABLED", &cfg.S3.HostBucketEnabled)
parseEnvVar("RENTERD_S3_HOST_BUCKET_BASES", &cfg.S3.HostBucketBases)

parseEnvVar("RENTERD_LOG_PATH", &cfg.Log.Path)
parseEnvVar("RENTERD_LOG_LEVEL", &cfg.Log.Level)
parseEnvVar("RENTERD_LOG_FILE_ENABLED", &cfg.Log.File.Enabled)
parseEnvVar("RENTERD_LOG_FILE_FORMAT", &cfg.Log.File.Format)
Expand All @@ -392,6 +373,20 @@ func main() {
parseEnvVar("RENTERD_LOG_DATABASE_IGNORE_RECORD_NOT_FOUND_ERROR", &cfg.Log.Database.IgnoreRecordNotFoundError)
parseEnvVar("RENTERD_LOG_DATABASE_SLOW_THRESHOLD", &cfg.Log.Database.SlowThreshold)

// parse remotes
var workerRemotePassStr string
var workerRemoteAddrsStr string
parseEnvVar("RENTERD_WORKER_REMOTE_ADDRS", &workerRemoteAddrsStr)
parseEnvVar("RENTERD_WORKER_API_PASSWORD", &workerRemotePassStr)
if workerRemoteAddrsStr != "" && workerRemotePassStr != "" {
mustParseWorkers(workerRemoteAddrsStr, workerRemotePassStr)
}

// disable worker if remotes are set
if len(cfg.Worker.Remotes) > 0 {
cfg.Worker.Enabled = false
}

// combine host bucket bases
for _, base := range strings.Split(hostBasesStr, ",") {
if trimmed := strings.TrimSpace(base); trimmed != "" {
Expand All @@ -405,24 +400,29 @@ func main() {
stdoutFatalError("API password must be set via environment variable or config file when --env flag is set")
return
}
setAPIPassword()
}
setAPIPassword()

// check that the seed is set
if cfg.Seed == "" {
if cfg.Seed == "" && (cfg.Worker.Enabled || cfg.Bus.RemoteAddr == "") { // only worker & bus require a seed
if disableStdin {
stdoutFatalError("Seed must be set via environment variable or config file when --env flag is set")
return
}
setSeedPhrase()
}

var rawSeed [32]byte
if err := wallet.SeedFromPhrase(&rawSeed, cfg.Seed); err != nil {
log.Fatal("failed to load wallet", zap.Error(err))
// generate private key from seed
var pk types.PrivateKey
if cfg.Seed != "" {
var rawSeed [32]byte
if err := wallet.SeedFromPhrase(&rawSeed, cfg.Seed); err != nil {
log.Fatal("failed to load wallet", zap.Error(err))
}
pk = wallet.KeyFromSeed(&rawSeed, 0)
}
seed := wallet.KeyFromSeed(&rawSeed, 0)

// parse S3 auth keys
if cfg.S3.Enabled {
var keyPairsV4 string
parseEnvVar("RENTERD_S3_KEYPAIRS_V4", &keyPairsV4)
Expand All @@ -435,12 +435,7 @@ func main() {
}
}

mustLoadAPIPassword()
if depWorkerRemoteAddrsStr != "" && depWorkerRemotePassStr != "" {
mustParseWorkers(depWorkerRemoteAddrsStr, depWorkerRemotePassStr)
}

// Create logger.
// create logger
if cfg.Log.Level == "" {
cfg.Log.Level = "info" // default to 'info' if not set
}
Expand Down Expand Up @@ -474,14 +469,13 @@ func main() {
}
var shutdownFns []shutdownFnEntry

if cfg.Bus.RemoteAddr != "" {
if len(cfg.Worker.Remotes) != 0 && !cfg.Autopilot.Enabled {
logger.Fatal("remote bus, remote worker, and no autopilot -- nothing to do!")
} else if cfg.Worker.ExternalAddress == "" {
logger.Fatal("if the bus is remote, the worker needs to be able to tell it where to find its API, this can be configured using worker.externalAddress")
}
if cfg.Bus.RemoteAddr != "" && !cfg.Worker.Enabled && !cfg.Autopilot.Enabled {
logger.Fatal("remote bus, remote worker, and no autopilot -- nothing to do!")
}
if cfg.Worker.Enabled && cfg.Bus.RemoteAddr != "" && cfg.Worker.ExternalAddress == "" {
logger.Fatal("can't enable the worker using a remote bus, without configuring the worker's external address")
}
if len(cfg.Worker.Remotes) == 0 && !cfg.Worker.Enabled && cfg.Autopilot.Enabled {
if cfg.Autopilot.Enabled && !cfg.Worker.Enabled && len(cfg.Worker.Remotes) == 0 {
logger.Fatal("can't enable autopilot without providing either workers to connect to or creating a worker")
}

Expand Down Expand Up @@ -514,7 +508,7 @@ func main() {
busAddr, busPassword := cfg.Bus.RemoteAddr, cfg.Bus.RemotePassword
setupBusFn := node.NoopFn
if cfg.Bus.RemoteAddr == "" {
b, setupFn, shutdownFn, err := node.NewBus(busCfg, cfg.Directory, seed, logger)
b, setupFn, shutdownFn, err := node.NewBus(busCfg, cfg.Directory, pk, logger)
if err != nil {
logger.Fatal("failed to create bus, err: " + err.Error())
}
Expand Down Expand Up @@ -547,13 +541,13 @@ func main() {
AuthDisabled: cfg.S3.DisableAuth,
HostBucketBases: cfg.S3.HostBucketBases,
HostBucketEnabled: cfg.S3.HostBucketEnabled,
}, bc, seed, logger)
}, bc, pk, logger)
if err != nil {
logger.Fatal("failed to create worker: " + err.Error())
}
var workerExternAddr string
if cfg.Bus.RemoteAddr != "" {
workerExternAddr = cfg.Worker.ExternalAddress + "/api/worker"
workerExternAddr = cfg.Worker.ExternalAddress
} else {
workerExternAddr = workerAddr
}
Expand Down
Loading