Dodici build

Un Nextcloud privato su NVMe con object storage dietro

Nextcloud su un'istanza Ryzen con Postgres e Redis su NVMe locale, ogni file utente in un bucket su un nodo di storage, collegati da un tunnel privato.

Che cosa costruisce questa guida

Due macchine. Una istanza Ryzen esegue Nextcloud, PHP-FPM, Postgres e Redis, tutto su NVMe Gen4 locale, dove vive il lavoro sensibile alla latenza. Il nodo di storage accanto contiene il contenuto dei file vero e proprio in un bucket a oggetti, dove vivono i terabyte economici. Tra i due si trova un tunnel WireGuard, quindi nulla nella configurazione è esposto a nient'altro.

La separazione è importante perché Nextcloud con un object store primario smette del tutto di scrivere i dati utente sul disco locale. La tua NVMe contiene il database e il codice; il bucket contiene ogni byte caricato da chiunque. Passare da duecento gigabyte a venti terabyte diventa allora un upgrade del nodo di storage, non una migrazione.

Prima di iniziare

  • Una R-8 per l'applicazione e una S-50 per il bucket, idealmente nella stessa città così il round-trip del tunnel è sotto il millisecondo.
  • Un tunnel tra di loro. La guida WireGuard costruisce esattamente questo; usa 10.9.0.1 per l'app e 10.9.0.2 per il nodo di storage.
  • Un hostname che punta all'istanza dell'app, cloud.example.com sotto.

1. Object storage sul nodo di storage

MinIO è un singolo binario e non serve altro. Scaricalo, verificalo, installalo:

cd /usr/local/bin
wget https://dl.min.io/server/minio/release/linux-amd64/minio
wget https://dl.min.io/server/minio/release/linux-amd64/minio.sha256sum
sha256sum -c minio.sha256sum
chmod +x minio
useradd -r -s /sbin/nologin minio
install -d -o minio -g minio /srv/objects

Il livello bulk su un nodo di storage è montato separatamente dalla NVMe. Metti il bucket sul livello bulk e lascia la NVMe per la cache di scrittura, che è il suo scopo. Poi la unit, a /etc/systemd/system/minio.service:

[Unit]
Description=MinIO
After=network-online.target [email protected]
Wants=network-online.target

[Service]
User=minio
Group=minio
Environment=MINIO_ROOT_USER=nextcloud
Environment=MINIO_ROOT_PASSWORD=<forty random characters>
ExecStart=/usr/local/bin/minio server /srv/objects --address 10.9.0.2:9000
Restart=always
LimitNOFILE=65535
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/srv/objects

[Install]
WantedBy=multi-user.target

Il binding all'indirizzo del tunnel invece che a tutto è la riga importante. L'object store non deve rispondere sull'interfaccia pubblica, e così un errore di firewall non può esporlo.

systemctl daemon-reload && systemctl enable --now minio
ss -ltn | grep 9000

2. Host dell'applicazione: pacchetti

Sull'istanza Ryzen:

apt update && apt install -y nginx postgresql redis-server unzip \
  php8.4-fpm php8.4-pgsql php8.4-gd php8.4-curl php8.4-zip php8.4-xml \
  php8.4-mbstring php8.4-intl php8.4-bcmath php8.4-gmp php8.4-imagick \
  php8.4-redis php8.4-apcu

3. Database

sudo -u postgres psql -c "CREATE USER nextcloud WITH PASSWORD '<a long password>';"
sudo -u postgres psql -c "CREATE DATABASE nextcloud OWNER nextcloud TEMPLATE template0 ENCODING UTF8;"

Le impostazioni predefinite di Postgres vanno bene a questa scala. Se questo Nextcloud finisce per servire qualche centinaio di persone, la guida alla ottimizzazione di Postgres si applica e i numeri che contiene valgono un'ora.

4. Nextcloud, configurato prima dell'installazione

cd /var/www
wget https://download.nextcloud.com/server/releases/latest.zip
unzip -q latest.zip && rm latest.zip
chown -R www-data:www-data /var/www/nextcloud

Ora la parte che deve avvenire prima che l'installer giri, perché un object store primario non può essere aggiunto a un'istanza esistente senza spostare ogni file a mano. Crea /var/www/nextcloud/config/config.php:

<?php
$CONFIG = array(
  "objectstore" => array(
    "class" => "\\OC\\Files\\ObjectStore\\S3",
    "arguments" => array(
      "bucket" => "nextcloud",
      "autocreate" => true,
      "key" => "nextcloud",
      "secret" => "<the same forty characters>",
      "hostname" => "10.9.0.2",
      "port" => 9000,
      "use_ssl" => false,
      "use_path_style" => true,
      "region" => "local",
    ),
  ),
);

use_ssl è falso di proposito. Il traffico attraversa un tunnel WireGuard che è già autenticato e cifrato, e terminare TLS una seconda volta sul nodo di storage non compra nulla se non cicli di CPU e un certificato da dimenticare di rinnovare. Se le due macchine mai si trovassero in città diverse, attivalo.

chown www-data:www-data /var/www/nextcloud/config/config.php
chmod 640 /var/www/nextcloud/config/config.php
cd /var/www/nextcloud
sudo -u www-data php occ maintenance:install --database pgsql \
  --database-name nextcloud --database-user nextcloud \
  --database-pass "<a long password>" \
  --admin-user admin --admin-pass "<another long password>"

5. Cache, cron e le ultime impostazioni

sudo -u www-data php occ config:system:set memcache.local --value "\\OC\\Memcache\\APCu"
sudo -u www-data php occ config:system:set memcache.distributed --value "\\OC\\Memcache\\Redis"
sudo -u www-data php occ config:system:set memcache.locking --value "\\OC\\Memcache\\Redis"
sudo -u www-data php occ config:system:set redis host --value /run/redis/redis-server.sock
sudo -u www-data php occ config:system:set redis port --value 0
sudo -u www-data php occ config:system:set trusted_domains 0 --value cloud.example.com
sudo -u www-data php occ config:system:set overwrite.cli.url --value https://cloud.example.com
sudo -u www-data php occ background:cron

Redis su un socket unix invece che su TCP risparmia un round trip di syscall su ogni operazione di lock, e Nextcloud prende un sacco di lock. Aggiungi www-data al gruppo redis così può raggiungere il socket, poi collega il job in background a un timer:

usermod -aG redis www-data
systemctl restart php8.4-fpm redis-server
echo "*/5 * * * * php -f /var/www/nextcloud/cron.php" | crontab -u www-data -

I predefiniti di PHP-FPM assumono una macchina condivisa. La tua non lo è, quindi in /etc/php/8.4/fpm/pool.d/www.conf imposta pm = dynamic, pm.max_children = 60, pm.start_servers = 12, pm.min_spare_servers = 8 e pm.max_spare_servers = 20, e alza memory_limit a 1G in /etc/php/8.4/fpm/php.ini. Gli upload vogliono upload_max_filesize e post_max_size a 16G a meno che non ti diverta spiegare perché un video è fallito al novanta percento.

Verifica

Il punto di questa build è che i file utente non sono sul server dell'applicazione. Dimostralo, con tre comandi.

sudo -u www-data php occ status
du -sh /var/www/nextcloud/data

Lo stato deve riportare installato e non in modalità manutenzione. La directory dati deve essere una manciata di megabyte e restare tale per sempre; contiene log e nient'altro una volta che l'object storage è primario.

Ora carica qualcosa e guarda dove finisce:

head -c 50M /dev/urandom > /tmp/test.bin
curl -u admin -T /tmp/test.bin https://cloud.example.com/remote.php/dav/files/admin/test.bin
du -sh /var/www/nextcloud/data

L'upload ha successo, e la directory dati non è cresciuta di cinquanta megabyte. Sul nodo di storage, l'oggetto è visibile direttamente:

ls -R /srv/objects/nextcloud | tail -5
du -sh /srv/objects/nextcloud

Gli oggetti sono nominati urn:oid: seguito dall'id del file, che è il motivo per cui il bucket non è una copia navigabile delle cartelle dell'utente e perché fai il backup del database e del bucket insieme o per niente.

Dopo

Prendi il componente aggiuntivo di backup fuori nodo per il dump di Postgres e il bucket, o esegui il tuo verso un terzo sito. Un bucket senza il suo database è una pila di blob numerati, e un database senza il suo bucket è un indice di file che non esistono più. Perdere l'uno perde entrambi.

Pronto quando lo sei

Scegli una città. Scegli una dimensione. Paga in criptovaluta.

Nessun modulo su chi sei, nessuna attesa per l'approvazione di una persona, nessuna chiamata per verificare nulla. La fattura viene saldata e le credenziali arrivano nella tua casella di posta.