Doce construcciones

Un Nextcloud privado sobre NVMe con almacenamiento de objetos detrás

Nextcloud sobre una instancia Ryzen con Postgres y Redis en NVMe local, cada archivo de usuario guardado en un bucket en un nodo de almacenamiento, unidos por un túnel privado.

Qué se construye

Dos máquinas. Una instancia Ryzen ejecuta Nextcloud, PHP-FPM, Postgres y Redis, todo en NVMe Gen4 local, que es donde vive el trabajo sensible a la latencia. El nodo de almacenamiento junto a él mantiene el contenido real de los archivos en un bucket de objetos, que es donde viven los terabytes baratos. Entre ellos se encuentra un túnel WireGuard, de modo que nada en la disposición queda expuesto a nada más.

La división importa porque Nextcloud con un almacén de objetos primario deja de escribir datos de usuario en el disco local por completo. Tu NVMe guarda la base de datos y el código; el bucket guarda cada byte que alguien sube. Crecer de doscientos gigabytes a veinte terabytes se convierte así en una actualización del nodo de almacenamiento en lugar de una migración.

Antes de empezar

  • Una R-8 para la aplicación y una S-50 para el bucket, idealmente en la misma ciudad para que el viaje de ida y vuelta del túnel sea inferior a un milisegundo.
  • Un túnel entre ellos. La guía de WireGuard construye exactamente esto; usa 10.9.0.1 para la aplicación y 10.9.0.2 para el nodo de almacenamiento.
  • Un nombre de host que apunte a la instancia de la aplicación, cloud.example.com abajo.

1. Almacenamiento de objetos en el nodo de almacenamiento

MinIO es un solo binario y no necesita nada más. Descárgalo, verifícalo, instálalo:

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

El nivel masivo en un nodo de almacenamiento se monta por separado del NVMe. Pon el bucket en el nivel masivo y deja el NVMe para la caché de escritura, que es para lo que está ahí. Luego la unidad, en /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

Vincular a la dirección del túnel en lugar de a todo es la línea importante. El almacén de objetos no tiene por qué responder en la interfaz pública, y así un error de firewall no puede exponerlo.

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

2. Host de la aplicación: paquetes

En la instancia 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. Base de datos

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;"

Los ajustes predeterminados de Postgres son suficientes a esta escala. Si este Nextcloud termina sirviendo a unos cientos de personas, la guía de ajuste de Postgres aplica y los números que contiene valen una hora.

4. Nextcloud, configurado antes de la instalación

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

Ahora la parte que debe ocurrir antes de que el instalador se ejecute, porque un almacén de objetos primario no se puede añadir a una instancia existente sin mover cada archivo 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 es falso a propósito. El tráfico cruza un túnel WireGuard que ya está autenticado y cifrado, y terminar TLS una segunda vez en el nodo de almacenamiento no compra nada excepto ciclos de CPU y un certificado que olvidar renovar. Si los dos servidores alguna vez están en ciudades diferentes, actívalo.

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. Caché, cron y los últimos ajustes

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 sobre un socket unix en lugar de TCP ahorra un viaje de syscall en cada operación de bloqueo, y Nextcloud toma muchos bloqueos. Añade www-data al grupo redis para que pueda alcanzar el socket, luego conecta el trabajo en segundo plano a un temporizador:

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 -

Los ajustes predeterminados de PHP-FPM asumen una máquina compartida. La tuya no es compartida, así que en /etc/php/8.4/fpm/pool.d/www.conf configura pm = dynamic, pm.max_children = 60, pm.start_servers = 12, pm.min_spare_servers = 8 y pm.max_spare_servers = 20, y sube memory_limit a 1G en /etc/php/8.4/fpm/php.ini. Las subidas quieren upload_max_filesize y post_max_size en 16G a menos que disfrutes explicando por qué un video falló al noventa por ciento.

Verifícalo

El punto de esta construcción es que los archivos de usuario no están en el servidor de la aplicación. Pruébalo, en tres comandos.

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

El estado debe informar instalado y no en modo de mantenimiento. El directorio de datos debe ser un puñado de megabytes y permanecer así para siempre; guarda registros y nada más una vez que el almacenamiento de objetos es primario.

Ahora sube algo y observa dónde aterriza:

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

La subida tiene éxito, y el directorio de datos no ha crecido en cincuenta megabytes. En el nodo de almacenamiento, el objeto es visible directamente:

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

Los objetos se nombran urn:oid: seguido de la identificación del archivo, que es por lo que el bucket no es una copia navegable de las carpetas del usuario y por lo que haces una copia de seguridad de la base de datos y del bucket juntos o no en absoluto.

Después

Toma el complemento de copia de seguridad fuera del nodo para el volcado de Postgres y el bucket, o ejecuta el tuyo a un tercer sitio. Un bucket sin su base de datos es un montón de blobs numerados, y una base de datos sin su bucket es un índice de archivos que ya no existen. Perder uno pierde ambos.

Listo cuando tú lo estés

Elige una ciudad. Elige un tamaño. Paga con monedas.

Sin fórmulas sobre quién eres, sin esperar a que un humano te apruebe, sin llamada telefónica para verificar nada. La factura se liquida y las credenciales llegan a tu bandeja de entrada.