Doze construções

Um Nextcloud privado em NVMe com armazenamento de objetos por trás

Nextcloud em uma instância Ryzen com Postgres e Redis em NVMe local, cada arquivo de usuário vivendo em um bucket em um nó de armazenamento, unidos por um túnel privado.

O que isto constrói

Duas máquinas. Uma instância Ryzen executa Nextcloud, PHP-FPM, Postgres e Redis, tudo em NVMe Gen4 local, que é onde fica o trabalho sensível à latência. O nó de armazenamento ao lado mantém o conteúdo real dos arquivos em um bucket de objetos, que é onde ficam os terabytes baratos. Entre eles há um túnel WireGuard, então nada na configuração fica exposto a mais nada.

A separação importa porque o Nextcloud com armazenamento de objetos primário para de gravar dados de usuário no disco local por completo. Sua NVMe mantém o banco de dados e o código; o bucket mantém cada byte que alguém envia. Crescer de duzentos gigabytes para vinte terabytes torna-se uma atualização do nó de armazenamento em vez de uma migração.

Antes de começar

  • Um R-8 para o aplicativo e um S-50 para o bucket, idealmente na mesma cidade para que o round-trip do túnel fique abaixo de um milissegundo.
  • Um túnel entre eles. O guia WireGuard constrói exatamente isso; use 10.9.0.1 para o aplicativo e 10.9.0.2 para o nó de armazenamento.
  • Um hostname apontando para a instância do aplicativo, cloud.example.com abaixo.

1. Armazenamento de objetos no nó de armazenamento

MinIO é um único binário e não precisa de mais nada. Baixe, verifique, instale:

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

O tier em massa em um nó de armazenamento é montado separadamente do NVMe. Coloque o bucket no tier em massa e deixe o NVMe para o cache de escrita, que é para isso que ele serve. Então a unit, em /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 ao endereço do túnel em vez de tudo é a linha importante. O armazenamento de objetos não tem motivo para responder na interface pública, e assim um erro de firewall não pode expô-lo.

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

2. Host do aplicativo: pacotes

Na instância 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. Banco de dados

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

As configurações padrão do Postgres são suficientes nessa escala. Se este Nextcloud acabar servindo algumas centenas de pessoas, o guia de ajuste do Postgres se aplica e os números nele valem uma hora.

4. Nextcloud, configurado antes da instalação

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

Agora a parte que tem que acontecer antes de o instalador rodar, porque um armazenamento de objetos primário não pode ser adicionado a uma instância existente sem mover cada arquivo manualmente. Crie /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 de propósito. O tráfego cruza um túnel WireGuard que já é autenticado e criptografado, e encerrar TLS uma segunda vez no nó de armazenamento não compra nada exceto ciclos de CPU e um certificado para esquecer de renovar. Caso as duas máquinas algum dia fiquem em cidades diferentes, ligue-o.

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 as últimas configurações

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 em um soquete unix em vez de TCP economiza uma chamada de sistema round trip em cada operação de lock, e o Nextcloud usa muitos locks. Adicione www-data ao grupo redis para que ele possa alcançar o soquete, então conecte o job de segundo plano a um 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 -

Os padrões do PHP-FPM assumem uma máquina compartilhada. A sua não é compartilhada, então em /etc/php/8.4/fpm/pool.d/www.conf defina pm = dynamic, pm.max_children = 60, pm.start_servers = 12, pm.min_spare_servers = 8 e pm.max_spare_servers = 20, e aumente memory_limit para 1G em /etc/php/8.4/fpm/php.ini. Uploads querem upload_max_filesize e post_max_size em 16G a menos que você goste de explicar por que um vídeo falhou aos noventa por cento.

Verificar

O objetivo desta construção é que os arquivos do usuário não ficam no servidor do aplicativo. Prove isso, em três comandos.

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

O status deve reportar instalado e não em modo de manutenção. O diretório de dados deve ter alguns megabytes e permanecer assim para sempre; ele mantém logs e nada mais uma vez que o armazenamento de objetos é primário.

Agora envie algo e veja onde vai parar:

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

O upload é bem-sucedido, e o diretório de dados não cresceu cinquenta megabytes. No nó de armazenamento, o objeto é visível diretamente:

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

Os objetos são nomeados urn:oid: seguido pelo id do arquivo, que é por que o bucket não é uma cópia navegável das pastas do usuário e por que você faz backup do banco de dados e do bucket juntos ou não faz de jeito nenhum.

Depois

Pegue o complemento de backup off-node para o dump do Postgres e o bucket, ou execute o seu próprio para um terceiro site. Um bucket sem seu banco de dados é uma pilha de blobs numerados, e um banco de dados sem seu bucket é um índice de arquivos que não existem mais. Perder um perde ambos.

Pronto quando você estiver

Escolha uma cidade. Escolha um tamanho. Pague em cripto.

Sem formulários sobre quem você é, sem esperar aprovação de um humano, sem ligação para verificar nada. O pagamento é confirmado e as credenciais chegam na sua caixa de entrada.