Nextcloud Object Storage¶
Siehe auch
- Verwandte Artikel
- Offizielle Dokumentation
Nextcloud unterstützt neben dem klassischen Dateisystem auch Object Storage als Primary Storage - also den Ort, an dem die Datei-Inhalte selbst liegen. Metadaten (Dateibaum, Rechte, Shares) bleiben in der SQL-Datenbank. Vorteil: praktisch beliebige Skalierung bis in den Petabyte-Bereich, typische Cloud-Preisstruktur, einfaches Backup auf Object-Store-Ebene. Nachteil: der Dateibaum ist ohne Nextcloud nicht mehr direkt lesbar (die Files liegen unter Keys wie urn:oid:12345 im Bucket), und einige wenige Features sind im Object-Modus komplizierter (z.B. Preview-Dateien).
Nextcloud 33 unterstützt diese Backends:
Amazon S3 und S3-kompatible Stores (Ceph RadosGW, Minio, Wasabi, Infomaniak, etc.) - Klasse
\OC\Files\ObjectStore\S3OpenStack Swift (API v2 und v3) - Klasse
\OC\Files\ObjectStore\SwiftMicrosoft Azure Blob Storage - Klasse
\OC\Files\ObjectStore\Azure
Begriffe¶
- Bucket / Container
Oberste Organisationseinheit im Object Store. Bei S3 „Bucket“, bei Swift „Container“. Für Nextcloud jeweils genau eines pro Instanz; weitere Buckets lassen sich über Multibucket-Konfiguration anbinden.
- MountProvider
Nextcloud-interne Abstraktion, die entscheidet, wo die Dateien eines Benutzers liegen. Für Local Storage ist das
OC\Files\Mount\LocalHomeMountProvider, für Object StorageOC\Files\Mount\ObjectHomeMountProvider. Die aktive Variante steht in der Tabelleoc_mounts.- Object Key
Identifier einer Datei im Bucket. Nextcloud verwendet das Schema
urn:oid:<fileid>, wobeifileiddirekt ausoc_filecache.fileidstammt. Der ursprüngliche Dateiname wird dabei nicht als Key benutzt, der lebt nur in der Datenbank.- Primary Storage
Ort, an dem Nextcloud die eigentlichen Datei-Inhalte ablegt. Steht in
config.phpunterobjectstore(bzw. ist nicht gesetzt für Local Storage).
Nextcloud von Beginn an mit Object Storage aufsetzen¶
Der sauberste Weg: vor dem occ maintenance:install-Aufruf die Object-Storage-Konfiguration als Partial-Config neben config.php ablegen. Der Installer führt beide Dateien zusammen, und der Object Store wird zum Primary Storage. Im Beispiel mit S3 gegen Infomaniak:
<?php
$CONFIG = array (
'objectstore' => [
'class' => 'OC\\Files\\ObjectStore\\S3',
'arguments' => [
'autocreate' => true,
'bucket' => 'bucket-name',
'hostname' => 's3.pub1.infomaniak.cloud',
'key' => '7ca40b8a-2572-4ff7-9a9a-57051bf33dc7',
'region' => 'us-east-1',
'secret' => '51c9d8b3-25f7-45dc-812f-2146e38d2167',
'use_ssl' => true,
'use_path_style' => true,
],
],
);
sudo -u apache php /var/www/html/nextcloud/occ maintenance:install \
--admin-pass 'linuxfabrik' \
--admin-user 'nextcloud-admin' \
--data-dir '/data' \
--database 'mysql' \
--database-host 'localhost' \
--database-name 'nextcloud' \
--database-user 'nextcloud' \
--database-pass 'linuxfabrik'
Migration Local Storage zu Object Storage¶
Migration eines bestehenden Block-Storage-Setups zu S3 oder Swift. Das Vorgehen ist bei beiden Backends identisch; unterschiedlich sind nur die Kopiertools und die abschliessende Nextcloud-Konfiguration. Die Auswahl des Kopier-Tools (aws oder rclone) spielt keine Rolle, das Ergebnis ist dasselbe.
Bemerkung
Die Zeitangaben im Beispiel stammen von einem System mit rund 90 Benutzern, etwas mehr als 2.1 Millionen Dateien und einer Gesamtgrösse von rund 4 TB.
Achtung
Vor der Migration unbedingt ein Datenbank-Backup erstellen. Die Schritte ab „Nextcloud auf Object Storage umkonfigurieren“ sind nicht trivial rückgängig zu machen.
Voraussetzungen¶
Datenbank heisst
nextcloud.Lokaler Nextcloud-Storage liegt auf
/var/www/html/nextcloud/data/.Object Store (S3-Bucket oder Swift-Container) mit dem Namen
bucket-nameist angelegt, Credentials liegen vor.Im Beispiel wird gegen Infomaniak im Zürcher Datacenter migriert: S3-Endpoint
https://s3.pub1.infomaniak.cloud, Swift-Endpointhttps://api.pub1.infomaniak.cloud/identity/v3.
Vorbereitung¶
# 30 minutes
chown -R apache:apache /var/www/html/nextcloud/data/
# 30 minutes
sudo -u apache /var/www/html/nextcloud/occ files:scan --all
Maintenance-Mode aktivieren (sperrt Benutzer für die Dauer der Migration aus und hält den Datenbestand konsistent):
sudo -u apache php /var/www/html/nextcloud/occ maintenance:mode --on
systemctl restart php-fpm
Aus der Datenbank eine Liste aller Benutzer-Dateien und Metadaten-Dateien exportieren (die beiden Dumps zusammen rund 500 MB im Beispiel):
mysql_user='root'
# user files
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/user_file_list
SELECT CONCAT('urn:oid:', fileid, ' ', '/var/www/html/nextcloud/data/', SUBSTRING(id FROM 7), '/', path)
FROM oc_filecache JOIN oc_storages ON storage = numeric_id
WHERE id LIKE 'home::%'
ORDER BY id;
EOF
# meta files
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/meta_file_list
SELECT CONCAT('urn:oid:', fileid, ' ', SUBSTRING(id FROM 8), path)
FROM oc_filecache JOIN oc_storages ON storage = numeric_id
WHERE id LIKE 'local::%'
ORDER BY id;
EOF
Symlinks als Stellvertreter¶
Für jede Datei im Local Storage einen Symlink mit dem zukünftigen Object-Key anlegen. Das erlaubt einen anschliessenden Bulk-Sync, der die echten Inhalte unter den korrekten Keys in den Object Store schreibt:
mkdir --parents /var/www/html/nextcloud/data/objectstore_files
cd /var/www/html/nextcloud/data/objectstore_files
# count files to process
# 3 minutes
find /var/www/html/nextcloud/data/ -type f | wc -l
# user files, 50 minutes
while read -r target source ; do
if [ -f "$source" ] ; then
ln --symbolic "$source" "$target" || echo "Error: Failed to link $source to $target!"
else
echo "Error: Could not find $source!"
fi
done < /tmp/user_file_list | tee /tmp/user_file.log
# meta files, 30 minutes
while read -r target source ; do
if [ -f "$source" ] ; then
ln --symbolic "$source" "$target" || echo "Error: Failed to link $source to $target!"
else
echo "Error: Could not find $source!"
fi
done < /tmp/meta_file_list | tee /tmp/meta_file.log
grep '^Error: ' /tmp/*.log
Das Zielverzeichnis enthält danach im Beispiel etwas über 2.3 Millionen urn:oid:<nnnnn>-Symlinks.
Sync per aws (S3)¶
Das Python-basierte AWS CLI installieren (die Version in den OS-Repos ist meist zu alt) und konfigurieren:
cd /tmp
dnf --assumeyes install unzip
curl --output awscliv2.zip https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip
unzip awscliv2.zip
./aws/install --bin-dir /usr/local/bin
mkdir /root/.aws
cat > /root/.aws/config << 'EOF'
[default]
region = us-east-1
s3 =
max_concurrent_requests = 100
EOF
cat > /root/.aws/credentials << 'EOF'
[default]
aws_access_key_id = 7ca40b8a-2572-4ff7-9a9a-57051bf33dc7
aws_secret_access_key = 51c9d8b3-25f7-45dc-812f-2146e38d2167
EOF
Sync ausführen:
cd /var/www/html/nextcloud/data/objectstore_files
# ~75 MiB/s = 4.4 GiB/min = 263 GiB/h = 6.1 TiB/d
# first sync takes 14h 40min; a second sync a day later takes 1h 08min
aws s3 sync . s3://bucket-name --endpoint-url https://s3.pub1.infomaniak.cloud --color auto
Sync per rclone (Swift)¶
dnf --assumeyes install rclone # from EPEL
cd /var/www/html/nextcloud/data/objectstore_files
export RCLONE_CONFIG_MYPROJECT_TYPE=swift
export RCLONE_CONFIG_MYPROJECT_ENV_AUTH=true
source path/to/openstack.rc
# ~25 MB/s
rclone sync --copy-links --progress . myproject:mybucket
Nach erfolgreichem Sync die Symlink-Staging-Area wieder entfernen:
rm --recursive --force /var/www/html/nextcloud/data/objectstore_files
Nextcloud auf Object Storage umkonfigurieren¶
Ab hier wird die laufende Instanz tatsächlich umgestellt. Datenbank-Backup zur Hand haben.
Datenbank-Einträge auf den Bucket umbiegen:
- S3
cat > /tmp/update-database.sql << 'EOF' UPDATE oc_storages SET id = CONCAT('object::user:', SUBSTRING(id FROM 7)) WHERE id LIKE 'home::%'; UPDATE oc_storages SET id = 'object::store:amazon::bucket-name' WHERE id LIKE 'local::%'; UPDATE oc_mounts SET mount_provider_class = 'OC\\Files\\Mount\\ObjectHomeMountProvider' WHERE mount_provider_class = 'OC\\Files\\Mount\\LocalHomeMountProvider'; EOF # a few milliseconds mariadb --user=root --password --database=nextcloud < /tmp/update-database.sql
Maintenance-Mode deaktivieren und den Objectstore in
config.phpschreiben:sudo -u apache php /var/www/html/nextcloud/occ maintenance:mode --off systemctl restart php-fpm cd /var/www/html/nextcloud/ sudo -u apache php occ config:system:set objectstore class --value='OC\Files\ObjectStore\S3' sudo -u apache php occ config:system:set objectstore arguments autocreate --type=boolean --value=true sudo -u apache php occ config:system:set objectstore arguments bucket --value=bucket-name sudo -u apache php occ config:system:set objectstore arguments hostname --value=s3.pub1.infomaniak.cloud sudo -u apache php occ config:system:set objectstore arguments key --value=7ca40b8a-2572-4ff7-9a9a-57051bf33dc7 sudo -u apache php occ config:system:set objectstore arguments port --type=integer --value=443 sudo -u apache php occ config:system:set objectstore arguments region --value=us-east-1 sudo -u apache php occ config:system:set objectstore arguments secret --value=51c9d8b3-25f7-45dc-812f-2146e38d2167 sudo -u apache php occ config:system:set objectstore arguments use_path_style --type=boolean --value=true sudo -u apache php occ config:system:set objectstore arguments use_ssl --type=boolean --value=true
Hintergrund zum Storage-Umschreiben: https://github.com/nextcloud/server/issues/25781.
- Swift
cat > /tmp/update-database.sql << 'EOF' UPDATE oc_storages SET id = CONCAT('object::user:', SUBSTRING(id FROM 7)) WHERE id LIKE 'home::%'; UPDATE oc_storages SET id = 'object::store:bucket-name' WHERE id LIKE 'local::%'; UPDATE oc_mounts SET mount_provider_class = 'OC\\Files\\Mount\\ObjectHomeMountProvider' WHERE mount_provider_class = 'OC\\Files\\Mount\\LocalHomeMountProvider'; EOF mariadb --user=root --password --database=nextcloud < /tmp/update-database.sql
sudo -u apache php /var/www/html/nextcloud/occ maintenance:mode --off systemctl restart php-fpm cd /var/www/html/nextcloud/ sudo -u apache php occ config:system:set objectstore class --value='OC\Files\ObjectStore\Swift' sudo -u apache php occ config:system:set objectstore arguments autocreate --type=boolean --value=true sudo -u apache php occ config:system:set objectstore arguments bucket --value=bucket-name sudo -u apache php occ config:system:set objectstore arguments region --value=dc3-a sudo -u apache php occ config:system:set objectstore arguments 'scope project domain name' --value=Default sudo -u apache php occ config:system:set objectstore arguments 'scope project name' --value=cabfeec2-ee88-4fba-8197-5bcae1ac96eb sudo -u apache php occ config:system:set objectstore arguments serviceName --value=swift sudo -u apache php occ config:system:set objectstore arguments url --value=https://api.pub1.infomaniak.cloud/identity/v3 sudo -u apache php occ config:system:set objectstore arguments 'user domain name' --value=Default sudo -u apache php occ config:system:set objectstore arguments 'user name' --value=f187e2b0-7d6c-419a-b5b0-9a5743677005 sudo -u apache php occ config:system:set objectstore arguments 'user password' --value=linuxfabrik # remove local OpenStack credentials file rm --force path/to/openstack.rc
Abschluss¶
systemctl restart php-fpm
sudo -u apache /var/www/html/nextcloud/occ maintenance:repair
Anschliessend die Instanz testen: Upload, Download, Preview für Bilder und PDFs, Sharing, App-Funktionen.
Achtung
Kein Bild-Preview, Dateien lassen sich nicht laden, Logeinträge zu „Encryption“ oder „Seeking“?
Prüfen, ob die Tabelle oc_mounts wirklich aktualisiert und der LocalHomeMountProvider durchgängig auf ObjectHomeMountProvider umgestellt wurde.
Migration Object Storage zu Local Storage¶
Die Rückrichtung: von S3 oder Swift zurück auf Local Block Storage.
Voraussetzungen¶
Datenbank heisst
nextcloud.Nextcloud-Files liegen im Object Store in einem Bucket/Container namens
bucket-name.Der lokale Ziel-Datadir liegt unter
$nextcloud_datadir(im Beispiel/data).
Vorbereitung¶
nextcloud_datadir='/data'
chown --recursive apache:apache "$nextcloud_datadir"
sudo -u apache /var/www/html/nextcloud/occ files:scan --all
Maintenance-Mode aktivieren, Background-Jobs stoppen:
sudo -u apache php /var/www/html/nextcloud/occ maintenance:mode --on
systemctl restart php-fpm
systemctl disable --now nextcloud-jobs.timer nextcloud-scan-files.timer
Datei-Listen aus der Datenbank¶
Je eine Liste für Verzeichnisse (mkdir-Input) und Dateien (mv-Input), getrennt für Benutzer- und Metadaten-Storage:
mysql_user='root'
# user directories
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/user_file_mkdir_list
SELECT CONCAT(SUBSTRING(oc_storages.id FROM 14), '/', path)
FROM oc_filecache
LEFT JOIN oc_storages ON oc_filecache.storage = oc_storages.numeric_id
LEFT JOIN oc_mimetypes ON oc_filecache.mimetype = oc_mimetypes.id
WHERE oc_storages.id LIKE 'object::user:%'
AND oc_mimetypes.mimetype = 'httpd/unix-directory'
ORDER BY oc_storages.id;
EOF
# user files
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/user_file_mv_list
SELECT CONCAT('urn:oid:', oc_filecache.fileid, ' ', SUBSTRING(oc_storages.id FROM 14), '/', path)
FROM oc_filecache
LEFT JOIN oc_storages ON oc_filecache.storage = oc_storages.numeric_id
LEFT JOIN oc_mimetypes ON oc_filecache.mimetype = oc_mimetypes.id
WHERE oc_storages.id LIKE 'object::user:%'
AND oc_mimetypes.mimetype != 'httpd/unix-directory'
ORDER BY oc_storages.id;
EOF
# meta directories
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/meta_file_mkdir_list
SELECT path
FROM oc_filecache
LEFT JOIN oc_storages ON oc_filecache.storage = oc_storages.numeric_id
LEFT JOIN oc_mimetypes ON oc_filecache.mimetype = oc_mimetypes.id
WHERE oc_storages.id LIKE 'object::store:%'
AND oc_mimetypes.mimetype = 'httpd/unix-directory'
ORDER BY oc_storages.id;
EOF
# meta files
mariadb --user="$mysql_user" --password --batch --disable-column-names --database=nextcloud << 'EOF' > /tmp/meta_file_mv_list
SELECT CONCAT('urn:oid:', fileid, ' ', path)
FROM oc_filecache
LEFT JOIN oc_storages ON oc_filecache.storage = oc_storages.numeric_id
LEFT JOIN oc_mimetypes ON oc_filecache.mimetype = oc_mimetypes.id
WHERE oc_storages.id LIKE 'object::store:%'
AND oc_mimetypes.mimetype != 'httpd/unix-directory'
ORDER BY oc_storages.id;
EOF
Object-Store in ein lokales Staging-Verzeichnis ziehen¶
dnf --assumeyes install rclone # from EPEL
nextcloud_datadir='/data'
mkdir --parents "$nextcloud_datadir/objectstore_files"
cd "$nextcloud_datadir/objectstore_files"
export RCLONE_CONFIG_MYPROJECT_TYPE=swift
export RCLONE_CONFIG_MYPROJECT_ENV_AUTH=true
source path/to/openstack.rc
rclone sync --progress myproject:mybucket .
# example timing:
# Transferred: 129.321 GiB / 129.321 GiB, 100%, 120.399 KiB/s, ETA 0s
# Checks: 101520 / 101520, 100%
# Transferred: 101537 / 101537, 100%
# Elapsed time: 2h30m56.7s
Dateien an den richtigen Ort verschieben¶
nextcloud_datadir='/data'
cd "$nextcloud_datadir/objectstore_files"
# user directories
while read -r folder ; do
folder_path="$nextcloud_datadir/$folder"
mkdir --parents "$folder_path" || echo "Error: Failed to create $folder_path!"
done < /tmp/user_file_mkdir_list | tee /tmp/user_file_mkdir.log
# user files
while read -r source target ; do
if [ -z "$target" ]; then
echo "Error: target for $source is empty!"
continue
fi
target_path="$nextcloud_datadir/$target"
\mv "$source" "$target_path" || echo "Error: Failed to move $source to $target_path!"
done < /tmp/user_file_mv_list | tee /tmp/user_file_mv.log
# meta directories
while read -r folder ; do
folder_path="$nextcloud_datadir/$folder"
mkdir --parents "$folder_path" || echo "Error: Failed to create $folder_path!"
done < /tmp/meta_file_mkdir_list | tee /tmp/meta_file_mkdir.log
# meta files
while read -r source target ; do
if [ -z "$target" ]; then
echo "Error: target for $source is empty!"
continue
fi
target_path="$nextcloud_datadir/$target"
\mv "$source" "$target_path" || echo "Error: Failed to move $source to $target_path!"
done < /tmp/meta_file_mv_list | tee /tmp/meta_file_mv.log
grep '^Error: ' /tmp/*.log
Das Staging-Verzeichnis sollte jetzt leer sein:
ls -l /data/objectstore_files/
rmdir /data/objectstore_files/
chown --recursive apache:apache "$nextcloud_datadir"
restorecon -Fvr "$nextcloud_datadir"
Datenbank auf Local Storage umbiegen¶
old_objectstore_bucket_name='mybucket'
nextcloud_datadir='/data'
cat > /tmp/update-database.sql << EOF
UPDATE oc_storages
SET id = CONCAT('home::', SUBSTRING(oc_storages.id FROM 14))
WHERE id LIKE 'object::user:%';
UPDATE oc_storages
SET id = 'local::$nextcloud_datadir/'
WHERE id = 'object::store:$old_objectstore_bucket_name';
UPDATE oc_mounts
SET mount_provider_class = 'OC\\\\Files\\\\Mount\\\\LocalHomeMountProvider'
WHERE mount_provider_class = 'OC\\\\Files\\\\Mount\\\\ObjectHomeMountProvider';
EOF
mysql_user='root'
mariadb --user="$mysql_user" --password --database=nextcloud < /tmp/update-database.sql
# sanity check: there should only be one entry starting with `local::`
mariadb --user="$mysql_user" --password --database=nextcloud \
--execute="SELECT * FROM nextcloud.oc_storages WHERE id LIKE 'local::%';"
Object-Store-Konfiguration aus der Nextcloud-Config entfernen¶
Den objectstore-Block aus config.php entfernen und Maintenance-Mode deaktivieren:
$EDITOR /var/www/html/nextcloud/config/config.php
sudo -u apache php /var/www/html/nextcloud/occ maintenance:mode --off
systemctl restart php-fpm
systemctl enable --now nextcloud-jobs.timer nextcloud-scan-files.timer
Anschliessend ist die Instanz wieder im Local-Storage-Modus. Upload, Preview und Sharing testen.
Best Practices¶
Den Bucket-Inhalt niemals von Hand anfassen. Dateinamen existieren nur in der Datenbank; im Bucket liegen die Files unter Keys der Form
urn:oid:<fileid>. Ein manuelles Umbenennen, Löschen oder Kopieren zerstört die Konsistenz zwischen Datenbank und Storage.Backups müssen Datenbank und Object Store gemeinsam und zeitnah sichern. Auseinanderlaufende Stände führen zu verwaisten Einträgen in
oc_filecache(Datei in DB, nicht im Bucket) oder verwaisten Objects (Datei im Bucket, nicht in DB).Keine S3-Lifecycle-Rules oder Swift-Expiry-Policies auf dem Bucket aktivieren, die Nextcloud nicht kennt. Nextcloud verwaltet Versionen, Papierkorb und Previews selbst in der Datenbank; ein „automatisches Aufräumen“ im Bucket entkoppelt die beiden Sichten.
Der Preview-Generator läuft auch bei Object Storage vollständig über das Backend und erzeugt pro Bild mehrere Varianten als eigene Objekte. Auf langsamen oder per-Request abgerechneten Object Stores kann das spürbar kosten - ggf. den Preview-Generator drosseln oder
preview_max_*inconfig.phpeinschränken.Multi-Bucket-Konfiguration (
multibuckettrue, Bucket-Name mit Prefix pro User) ist für grosse Instanzen möglich, erhöht aber die Komplexität beim Backup und beim Provider-Wechsel. Wer sie einsetzt, dokumentiert Bucket-Verteilung und Prefix-Schema pro Instanz.Umstellen eines bestehenden Local-Storage-Systems auf Object Storage macht alle bestehenden Files unzugänglich, bis die Inhalte migriert sind. Die Migration lässt sich nicht nachträglich „einfach einschalten“ - immer den vollständigen Umzug gemäss obiger Anleitung fahren.