Nextcloud – Betriebsdokumentation
Nextcloud 33 auf K3s (AlmaLinux 9) · Letzte Aktualisierung: Mai 2026
Überblick
Nextcloud läuft auf einem Single-Node-K3s-Cluster auf AlmaLinux 9.
Die gesamte Infrastruktur wird mit einem Ansible-Playbook
(nextcloud-k3s.yml) aufgebaut und verwaltet.
Das Playbook ist idempotent – es kann jederzeit neu ausgeführt werden,
ohne Daten zu verändern oder Dienste unnötig zu unterbrechen.
Recreate-Strategie im Deployment stellt sicher, dass immer nur
ein Pod auf die HostPath-Volumes zugreift.
Architektur
Gesamtüberblick
Warum dieser Ansatz?
| Komponente | Wo läuft es? | Begründung |
|---|---|---|
| Nextcloud PHP-FPM + nginx | K3s Pod Container | Einfache Updates durch Image-Wechsel; kein php-fpm auf dem Host |
| Collabora CODE | K3s Pod Container | Isoliert, eigene TLS-Terminierung durch nginx-ingress (F5) |
| MariaDB | Host-Dienst Host | Datenbankdaten liegen direkt auf dem Dateisystem; kein Container-Volume-Layer |
| Redis | Host-Dienst Host | Einfach, keine Persistenz nötig; bindet an Host-IP für Pod-Zugriff |
| nginx-ingress (F5) | K3s DaemonSet hostNetwork | Bindet direkt auf Port 80/443 des Hosts; kein externer Load Balancer nötig |
| cert-manager | K3s Container | Automatische Let's-Encrypt-Zertifikate; ersetzt certbot |
Nextcloud-Pod im Detail
Beide Container teilen sich denselben Pod (= dasselbe Netzwerk-Namespace und
die gleichen Volumes). Dadurch kann nginx via 127.0.0.1:9000 mit
PHP-FPM kommunizieren, ohne einen Kubernetes-Service zu benötigen.
Init-Container: init-dirs
Läuft einmalig vor dem Start der Hauptcontainer. Setzt die Ownership der
HostPath-Verzeichnisse auf UID 33 (www-data im Container) und setzt die
Berechtigungen. Ohne diesen Schritt kann Nextcloud nichts in /var/www/html
schreiben.
Container 1: nextcloud-fpm
Das offizielle nextcloud:33-fpm-Image. Führt PHP-FPM auf Port 9000 aus.
Bei einem leeren /var/www/html installiert der Docker-Entrypoint
Nextcloud automatisch anhand der Umgebungsvariablen (DB, Admin, Redis).
Bei einem Update läuft occ upgrade automatisch.
Container 2: nginx
nginx-Sidecar (nginx:1.30-alpine) auf Port 80. Beantwortet
statische Anfragen direkt aus dem gemeinsamen Volume; PHP-Anfragen werden per
FastCGI an Port 9000 weitergeleitet. TLS ist bereits durch nginx-ingress (F5)
terminiert – nginx spricht nur plain HTTP.
Speicher & Daten
/dev/vda4 (119 GB). Es gibt keine getrennten Volumes für App und Daten.
| Pfad auf dem Host | Mountpoint im Container | Inhalt | Besitzer |
|---|---|---|---|
/srv/nextcloud-www |
/var/www/html |
PHP-App, Apps, Themes, config/ |
root:root 755 (Verz.) 33:33 für config/ |
/data |
/var/www/html/data |
Benutzerdateien, nextcloud.log |
33:33 (www-data im Container) |
/var/lib/mysql |
– | MariaDB-Datenbank (läuft direkt auf dem Host) | mysql:mysql |
Warum sind App und Daten getrennt?
Nextcloud-App-Dateien (/srv/nextcloud-www) und Benutzerdaten
(/data) sind bewusst in zwei separate HostPath-Volumes
aufgeteilt, weil:
- Benutzerdaten können unabhängig gesichert werden (täglich empfohlen)
- App-Dateien werden bei einem Update durch das neue Image ersetzt
- Bei einer Neu-Installation genügt ein leeres
/srv/nextcloud-www– Nextcloud installiert sich selbst beim ersten Start
Wichtige Konfigurationsdatei: config.php
Nextcloud liest alle *.php-Dateien aus /var/www/html/config/
automatisch. Es gibt zwei Quellen:
config.php– von Nextcloud selbst geschrieben (Erstinstallation)custom.config.php– aus dem Kubernetes-ConfigMapnextcloud-custom-config, verwaltet durch Ansible. Diese Datei hat Vorrang und überschreibt Werte ausconfig.php.
/srv/nextcloud-www/config/
vorab mit owner: 33 angelegt werden (Ansible-Task
next_k3s_deploy/tasks/dirs.yml).
Netzwerk & TLS
IP-Adressbereiche
| Bereich | Zweck | Relevant für |
|---|---|---|
10.42.0.0/16 |
K3s Pod-CIDR (Flannel) | iptables-Regeln, trusted_proxies, WOPI-Allowlist, MariaDB-User-Grant |
10.43.0.0/16 |
K3s Service-CIDR | ClusterIP-Adressen der K8s Services |
82.165.165.230 |
Öffentliche Host-IP (Testserver) | DNS, WOPI-Allowlist, ExternalService-Endpoints |
Benötigte DNS-Einträge
Vor dem ersten Playbook-Lauf müssen die DNS-Einträge beim Domain-Registrar angelegt sein. cert-manager benötigt sie für den Let's Encrypt HTTP-01-Challenge – ohne gültige Einträge schlägt die Zertifikatsausstellung fehl.
| Name (Hostname) | Typ | Wert | Zweck |
|---|---|---|---|
nextcloud.example.de |
A | Öffentliche IP des Servers | Nextcloud-Weboberfläche, Let's Encrypt TLS |
collabora.example.de |
A | Öffentliche IP des Servers | Collabora CODE Online (Office-Bearbeitung), eigenes TLS-Zertifikat |
fw_ipv6.sh)
sind bereits konfiguriert.
Wie Pods den Host erreichen (MariaDB / Redis)
MariaDB und Redis laufen als Host-Dienste. Pods verbinden sich darüber via K8s ExternalServices: ein Service ohne Selector mit manuell gepflegten Endpoints, die auf die öffentliche IP des Hosts zeigen.
Pod → mariadb.nextcloud.svc.cluster.local (10.43.x.x)
→ Endpoint: 82.165.165.230:3306
→ iptables ACCEPT für 10.42.0.0/16 auf :3306
Der MariaDB-Datenbanknutzer ist auf den Pod-CIDR eingeschränkt
('nextcloud'@'10.42.%'). Eine direkte Verbindung vom Host
funktioniert nur als root via Unix-Socket.
TLS und Proxy-Kette
Internet
→ nginx-ingress (F5) (Port 443, TLS terminiert, setzt X-Forwarded-For / -Proto)
→ nginx-Sidecar (Port 80, plain HTTP)
→ PHP-FPM (Port 9000, FastCGI)
fastcgi_param HTTPS on
fastcgi_param HTTP_SCHEME https
Nextcloud muss wissen, dass Anfragen über einen Proxy kommen:
'trusted_proxies' => ['10.42.0.0/16'],
'forwarded_for_headers' => ['HTTP_X_FORWARDED_FOR'],
'overwriteprotocol' => 'https',
trusted_proxies muss den Pod-CIDR enthalten
Fehlt dieser Eintrag, zeigt Nextcloud im Admin-Panel die Warnung
"Konfiguration des Reverse-Proxy-Headers ist falsch".
nginx-ingress (F5) leitet Anfragen mit seiner Pod-IP (10.42.x.x) weiter –
diese muss als vertrauenswürdiger Proxy eingetragen sein.
WOPI (Collabora)
Collabora ruft für die Dokument-Bearbeitung Nextclouds WOPI-API auf.
Diese Rückrufe kommen von der Pod-IP des Collabora-Pods
(10.42.x.x) – nicht von der Service-IP. Die wopi_allowlist
muss daher den Pod-CIDR enthalten:
# inventory/host_vars/<host>/vars.yml
wopi_allowed_hosts:
- "10.42.0.0/16" # Pod-CIDR (Collabora-Pod → Nextcloud WOPI)
- "10.43.0.0/16" # Service-CIDR
- "collabora.inspired-as-code.de"
Wichtige Dateien im Repository
Inventory & Variablen
| Datei | Inhalt |
|---|---|
inventory/group_vars/all.yml |
Globale Einstellungen: K3s-CIDRs, Container-Image-Tags, Helm-Chart-Versionen, Monitoring-Versionen |
inventory/host_vars/<host>/vars.yml |
Hostspezifisch: Hostnames, IPs, DB-Name, Pfade, WOPI-Allowlist |
inventory/host_vars/<host>/vault.yml |
Verschlüsselt! Passwörter: DB-Root, DB-User, Admin, Redis,
SMTP, Grafana. Vor git-Commit verschlüsseln:
ansible-vault encrypt inventory/host_vars/test/vault.yml |
Ansible-Rollen (Nextcloud-spezifisch)
| Rolle | Aufgabe |
|---|---|
next_packages | Repositories (MariaDB 10.11), Pakete (MariaDB, Redis, SELinux-Tools) |
next_mariadb | MariaDB-Setup, Datenbank und Benutzer anlegen, bind-address auf 0.0.0.0 |
next_redis | Redis-Bind-Konfiguration (localhost + Host-IP) |
next_selinux | SELinux enforcing, container_file_t für HostPath-Verzeichnisse |
next_k3s_deploy | K8s-Manifeste: Namespace, Secrets, ConfigMaps, Deployments, Ingress, CronJob |
next_config | Post-Deploy via kubectl exec occ: trusted_domains, Apps, WOPI-Config |
Templates (erzeugen die K8s-Manifeste)
| Template | Erzeugt | Wichtig weil |
|---|---|---|
nextcloud-deployment.yml.j2 |
Deployment + Service | Image-Tags, Volumes, Readiness-Probes, Env-Variablen |
custom-config-cm.yml.j2 |
ConfigMap nextcloud-custom-config |
Nextcloud-PHP-Konfiguration: Redis, Proxy, Locale, Session |
nginx-cm.yml.j2 |
ConfigMap nextcloud-nginx-config |
nginx-Routing, .well-known-Redirects, MIME-Typen, PHP-FastCGI |
secret.yml.j2 |
Secret nextcloud-secrets |
DB-Credentials, Admin-Passwort, SMTP-Daten – in den Pod als Env-Variablen |
external-services.yml.j2 |
Services + Endpoints für MariaDB, Redis, Grafana | Verbindung vom Pod zum Host-Dienst |
collabora-deployment.yml.j2 |
Collabora Deployment + Service | aliasgroup1-Env begrenzt erlaubte Nextcloud-Instanzen |
cronjob.yml.j2 |
K8s CronJob | Führt occ cron.php alle 5 Minuten aus |
Variablen & Secrets
Wo Credentials liegen
Alle Passwörter stehen in inventory/host_vars/<host>/vault.yml,
verschlüsselt mit Ansible Vault. Diese Datei darf niemals unverschlüsselt
in git committed werden.
# Verschlüsseln vor dem Commit:
ansible-vault encrypt inventory/host_vars/test/vault.yml
# Entschlüsseln zum Bearbeiten:
ansible-vault decrypt inventory/host_vars/test/vault.yml
# (danach sofort wieder verschlüsseln!)
# Direkt im Editor öffnen (bevorzugt):
ansible-vault edit inventory/host_vars/test/vault.yml
Passwort-Eintrag für unbeaufsichtigte Runs
# .vault_pass (niemals committen – steht in .gitignore)
echo "mein-vault-passwort" > .vault_pass
# In ansible.cfg einkommentieren:
# vault_password_file = .vault_pass
Secrets im K8s-Cluster
Die Vault-Variablen fließen beim Playbook-Run in das K8s-Secret
nextcloud-secrets im Namespace nextcloud.
Das Secret wird als Umgebungsvariablen in den Pod injiziert.
# Secret anzeigen (dekodiert):
k3s kubectl get secret nextcloud-secrets -n nextcloud \
-o jsonpath='{.data}' | python3 -c \
"import sys,json,base64; [print(k,base64.b64decode(v).decode()) \
for k,v in json.load(sys.stdin).items()]"
Container-Images & Versionen
Alle Image-Tags sind in inventory/group_vars/all.yml
zentral definiert. Da imagePullPolicy standardmäßig
IfNotPresent für getaggte Images ist, zieht K3s automatisch
ein neues Image nur dann, wenn der Tag im Manifest geändert wurde.
# inventory/group_vars/all.yml
nextcloud_image_fpm: "nextcloud:33-fpm" # Nextcloud PHP-FPM
nextcloud_image_nginx: "nginx:1.30-alpine" # nginx Sidecar
nextcloud_image_collabora: "collabora/code:25.04.9.4.1" # Collabora CODE
nextcloud_image_busybox: "busybox:1" # Init-Container
Tag-Strategie
| Tag-Format | Bedeutung | Beispiel |
|---|---|---|
33-fpm |
Folgt Minor- und Patch-Updates in Nextcloud 33.x automatisch beim Pull | nextcloud:33-fpm |
34-fpm |
Nächste Major-Version – muss explizit gesetzt werden | Änderung in all.yml nötig |
1.29-alpine |
nginx Minor-Track; Patch-Updates kommen automatisch | nginx:1.30-alpine |
25.04 |
Collabora Release-Cycle (quartalsweise) | collabora/code:25.04 → 25.08 |
nextcloud:33-fpm von 33.0.2 auf 33.0.3 aktualisiert,
passiert auf dem Server nichts automatisch. Der Tag ist lokal gecacht.
Manuell updaten:
crictl pull docker.io/library/nextcloud:33-fpm
k3s kubectl rollout restart deployment/nextcloud -n nextcloud
Nextcloud-Konfiguration
Die Konfiguration erfolgt auf zwei Wegen:
- custom.config.php (via Ansible ConfigMap) – statische Einstellungen, die bei jedem Playbook-Run aktualisiert werden
-
occ-Befehle (via
kubectl exec) – dynamische Einstellungen wietrusted_domains, WOPI-URL, App-Installation
custom.config.php – Übersicht der Schlüsselwerte
// Sprache
'default_language' => 'de',
'default_locale' => 'de_DE',
'force_language' => 'de',
// Session (1 Stunde, kein "Angemeldet bleiben")
'session_lifetime' => 3600,
'auto_logout' => true,
'remember_login_cookie_lifetime' => 0,
// Redis (Cache, Locking, Sessions)
'memcache.local' => '\\OC\\Memcache\\Redis',
'memcache.distributed' => '\\OC\\Memcache\\Redis',
'memcache.locking' => '\\OC\\Memcache\\Redis',
'redis' => ['host' => 'redis', 'port' => 6379],
// Reverse-Proxy
'trusted_proxies' => ['10.42.0.0/16'],
'forwarded_for_headers' => ['HTTP_X_FORWARDED_FOR'],
'overwriteprotocol' => 'https',
// Logging (in /data/nextcloud.log auf dem Host)
'loglevel' => 4,
'logtimezone' => 'Europe/Berlin',
'log_rotate_size' => 104857600, // 100 MB
// Wartungsfenster (1 Uhr nachts)
'maintenance_window_start' => 1,
Konfiguration nach Playbook-Run prüfen
k3s kubectl exec -n nextcloud deployment/nextcloud -c nextcloud-fpm -- \
runuser -u www-data -- php /var/www/html/occ config:list system
k3s kubectl exec -n nextcloud deployment/nextcloud -c nextcloud-fpm -- \
runuser -u www-data -- php /var/www/html/occ setupchecks
Playbook ausführen
Vollständiger Erstlauf (neue Server-Installation)
-
SSH-Port auf
22setzen ininventory/host_vars/test/vars.yml:ansible_port: 22 -
Vault-Passwörter in
vault.ymleintragen und verschlüsseln -
Collections installieren:
ansible-galaxy collection install -r requirements.yml -
Playbook ausführen:
ansible-playbook nextcloud-k3s.yml --limit test --ask-vault-pass -
SSH-Port in
vars.ymlauf10022ändern (nach SSH-Hardening durchcommon_ssh)
Idempotente Wiederholung (z.B. nach Konfig-Änderung)
ansible-playbook nextcloud-k3s.yml --limit test --ask-vault-pass
Das Playbook erkennt, was bereits korrekt konfiguriert ist, und ändert nur,
was nötig ist. Kubernetes-Manifeste werden nur dann neu gerollt,
wenn sich das Manifest geändert hat (changed: "configured" in output).
Nur bestimmte Rollen ausführen
# Nur Nextcloud-Konfiguration (occ-Befehle)
ansible-playbook nextcloud-k3s.yml --limit test --tags next_config --ask-vault-pass
# Nur K8s-Deployments aktualisieren
ansible-playbook nextcloud-k3s.yml --limit test --tags next_k3s_deploy --ask-vault-pass
Wichtige Betriebsbefehle
Pod-Status
# Alle Pods im nextcloud-Namespace
k3s kubectl get pods -n nextcloud
# Detaillierter Status (Events bei Problemen)
k3s kubectl describe pod -l app=nextcloud -n nextcloud
# Logs
k3s kubectl logs deployment/nextcloud -c nextcloud-fpm -n nextcloud --tail=50
k3s kubectl logs deployment/nextcloud -c nginx -n nextcloud --tail=50
k3s kubectl logs deployment/collabora -n nextcloud --tail=50
occ-Befehle (Nextcloud-CLI)
# Prefix für alle occ-Befehle:
k3s kubectl exec -n nextcloud deployment/nextcloud -c nextcloud-fpm -- \
runuser -u www-data -- php /var/www/html/occ <BEFEHL>
# Beispiele:
... occ status
... occ check
... occ setupchecks
... occ maintenance:mode --on
... occ maintenance:mode --off
... occ files:scan --all
... occ db:add-missing-indices
... occ app:list
... occ config:list system
... occ config:system:get trusted_domains
Pod neustarten
# Nextcloud (rollt graceful durch wegen Recreate-Strategie)
k3s kubectl rollout restart deployment/nextcloud -n nextcloud
k3s kubectl rollout status deployment/nextcloud -n nextcloud --timeout=300s
# Collabora
k3s kubectl rollout restart deployment/collabora -n nextcloud
Namespace-Übersicht
k3s kubectl get all -n nextcloud
MariaDB (Host)
# Als root via Socket (Credentials in /root/.my.cnf):
mysql test_nextcloud
# DB-Dump erstellen:
mysqldump --single-transaction test_nextcloud > /tmp/dump.sql
Redis (Host)
# Verbindung testen:
redis-cli ping
# Cache leeren (z.B. nach Konfigurationsänderung):
redis-cli FLUSHALL
Monitoring
Der Monitoring-Stack (Prometheus + Grafana + Node Exporter + mysqld_exporter + php-fpm_exporter)
läuft teils als Host-Dienste, teils als Sidecar-Container im Nextcloud-K3s-Pod.
Grafana ist über K3s-Ingress am Nextcloud-Hostname unter /grafana/ erreichbar.
https://nextcloud.*/grafana/
Dashboards: Node Exporter Full (1860), MySQL Overview (7362), PHP-FPM (4912)
Login: Grafana-Admin-Passwort aus vault.yml
127.0.0.1:9090
Nur lokal erreichbar
Retention: 30 Tage
127.0.0.1:9100CPU, RAM, Disk, Netzwerk
127.0.0.1:9104 (Host)
Host-Dienst (common_mysqld_exporter); verbindet sich via TCP mit dem
Host-MariaDB als Nutzer prometheus
Prometheus-Job: mysqld_nextcloud
127.0.0.1:30254 (NodePort)
Sidecar im Nextcloud-Pod; Prometheus scrapt via NodePort 30254
Prometheus-Job: php_fpm_nextcloud
MariaDB Slow Query Log
Der Host-MariaDB schreibt langsame Queries (≥ 1 Sekunde) nach
/var/log/mariadb/slow.log. Die Datei wird von MariaDB automatisch angelegt
und kann per journalctl oder direkt gelesen werden.
# Slow-Query-Log live lesen
tail -f /var/log/mariadb/slow.log
# Queries der letzten 24 Stunden filtern
grep -A 3 "Query_time" /var/log/mariadb/slow.log | grep -v "^--$"
# Log-Größe prüfen
ls -lh /var/log/mariadb/slow.log
Konfiguration: /etc/my.cnf.d/nextcloud-k3s.cnf →
slow_query_log = 1, long_query_time = 1
PHP-FPM Slow Log
PHP-FPM loggt Requests, die länger als 5 Sekunden dauern, nach /proc/1/fd/2
(stderr des Pod-Hauptprozesses). Da ptrace in Containern nicht
verfügbar ist, werden keine Stack-Traces geschrieben – nur der Request-Zeitstempel.
# Slow FPM Requests verfolgen
k3s kubectl logs -n nextcloud deployment/nextcloud -c nextcloud-fpm -f | grep -i "slow"
Konfiguration: ConfigMap nextcloud-fpm-pool-config →
pm.status_path = /fpm-status, slowlog = /proc/1/fd/2,
request_slowlog_timeout = 5s
Grafana Dashboards
Node Exporter Full (ID 1860) – Host-Metriken
Zeigt den Gesundheitszustand des gesamten Servers in Echtzeit.
| Panel | Was man abliest |
|---|---|
| CPU Usage | Gesamtauslastung und Aufteilung je Kern (user / system / iowait). iowait > 20 % deutet auf einen Disk-Engpass hin. |
| Load Average (1/5/15 min) | Systemlast relativ zur Anzahl CPU-Kerne. Dauerhaft über der Kernzahl → Server überlastet. |
| RAM / Memory | Genutzter RAM, Buffers, Cache und Swap. Swap-Nutzung > 0 zeigt RAM-Mangel – Nextcloud/Collabora brauchen ggf. mehr Speicher. |
| Disk I/O | Lese- und Schreibrate je Gerät, Latenz.
Hohe Latenz bei /srv/nextcloud-www oder /data bremst Uploads/Downloads. |
| Disk Space | Belegung der Dateisysteme. /data (User-Dateien) wächst kontinuierlich –
Alarm wenn > 80 % belegt. |
| Network Traffic | Bytes ein/aus je Interface. Ungewöhnliche Spitzen können auf Angriffe oder Datenlecks hinweisen. |
MySQL Overview (ID 7362) – MariaDB-Datenbankmetriken
Zeigt die Performance der Nextcloud-Datenbank (Host-MariaDB, Port 9104).
| Panel | Was man abliest |
|---|---|
| Queries per Second (QPS) | Datenbankaktivität. Plötzliche Spitzen können auf ineffiziente Anfragen oder Angriffe hinweisen. |
| Slow Queries | Queries die länger als long_query_time (1 s) dauern.
Dauerhaft > 0 → fehlende Indizes oder zu schwache Hardware. |
| InnoDB Buffer Pool | Cache-Auslastung und Hit-Ratio. Hit-Ratio < 95 % → Buffer Pool zu klein, viele Lesezugriffe gehen auf die Disk (langsam). |
| Connections | Aktive und maximale Verbindungen. Nähert sich Max → Connection-Limit erhöhen oder Abfragen optimieren. |
| Table Locks / Threads running | Sperrkonflikte und parallele Abfragen. Viele wartende Locks = Transaktionsproblem. |
| Aborted Connections | Unterbrochene Verbindungen (Timeout, Netzwerkfehler). Dauerhaft erhöht → Netzwerk oder App-Config prüfen. |
PHP-FPM (ID 4912) – PHP-Prozesspool
Zeigt wie gut PHP-FPM die eingehenden Nextcloud-Anfragen verarbeitet (K3s-Pod, NodePort 30254).
| Panel | Was man abliest |
|---|---|
| Active Processes | Gleichzeitig verarbeitende PHP-Worker. Erreicht dieses Panel dauerhaft
pm.max_children → Pool vergrößern oder Server aufrüsten. |
| Idle Processes | Freie Worker. Immer 0 + Queue > 0 = PHP-FPM ist überlastet. |
| Request Queue | Wartende Requests die keinen freien Worker bekommen. Jede Zahl > 0 bedeutet Latenz für den Nutzer. |
| Requests per Second | Durchsatz des PHP-Backends. Korreliert mit Nutzerzahlen und Sync-Clients. |
| Slow Requests | PHP-Requests die länger als request_slowlog_timeout (5 s) dauern.
Hinweis auf ineffiziente Nextcloud-Operationen (z.B. große Datei-Scans). |
| Max Active (Peak) | Höchster Wert seit FPM-Start. Hilft bei der Dimensionierung von pm.max_children. |
__inputs-Sektion.
Die Variablen ds_prometheus, job, nodename und node
werden deshalb nach jedem Import via Python-Skript durch den common_grafana-Task gesetzt.
Ohne diesen Schritt zeigen alle Panels „No data". Falls ein Dashboard leer ist:
Playbook erneut ausführen oder manuell in Grafana die Dropdown-Variablen oben im Dashboard auswählen.
mysqld_exporter – Status prüfen
# Service-Status
systemctl status mysqld_exporter
# Logs
journalctl -u mysqld_exporter -n 50
# Metriken direkt abrufen (Test)
curl -s http://127.0.0.1:9104/metrics | grep mysql_up
grafana_proxy_hostname in host_vars/<host>/vars.yml
auf den Blog-Hostnamen geändert werden – sonst zeigt der /grafana/-Pfad
auf den falschen Ingress.
Updates
Betriebssystem (AlmaLinux 9)
Security-Updates werden durch dnf-automatic täglich automatisch
eingespielt. Für ein vollständiges System-Update:
-
ssh -p 10022 root@server dnf upgrade -y -
Prüfen ob ein Reboot nötig ist:
needs-restarting -r -
Bei Bedarf: Server rebooten. K3s startet automatisch nach dem Reboot,
Pods kommen selbst wieder hoch.
reboot # Danach prüfen: k3s kubectl get pods -n nextcloud
Container-Update: Patch innerhalb desselben Tags
Wenn Docker Hub z.B. nextcloud:33-fpm von 33.0.2 auf 33.0.3 aktualisiert
und der Tag im Ansible unverändert bleibt:
- Neues Image manuell pullen:
crictl pull docker.io/library/nextcloud:33-fpm - Pod neu starten (nutzt jetzt das frisch gepullte Image):
k3s kubectl rollout restart deployment/nextcloud -n nextcloud k3s kubectl rollout status deployment/nextcloud -n nextcloud --timeout=300s - DB-Migrationen sicherstellen:
ansible-playbook nextcloud-k3s.yml --limit test --tags next_config --ask-vault-pass
Container-Update: Major-Versionssprung (z.B. 33 → 34)
nextcloud-backup.sh ausführen.
- Tag in
inventory/group_vars/all.ymländern:nextcloud_image_fpm: "nextcloud:34-fpm" # ggf. auch: nextcloud_image_collabora: "collabora/code:25.08" - Playbook ausführen – K3s zieht das neue Image automatisch und rollt den Pod durch:
ansible-playbook nextcloud-k3s.yml --limit test --ask-vault-pass -
Der Nextcloud-Container führt
occ upgradebeim Start automatisch aus. Der Playbook-Lauf führt anschließend dienext_config-Rolle aus (DB-Indices, WOPI-Config, App-Einstellungen). - Admin-Panel auf Warnungen prüfen:
k3s kubectl exec -n nextcloud deployment/nextcloud -c nextcloud-fpm -- \ runuser -u www-data -- php /var/www/html/occ setupchecks
Wo neue Versionen nachschauen
| Komponente | URL |
|---|---|
| Nextcloud | https://hub.docker.com/_/nextcloud/tags (Filter: *-fpm) |
| Collabora | https://hub.docker.com/r/collabora/code/tags |
| nginx | https://hub.docker.com/_/nginx/tags (Filter: *-alpine) |
| nginx-ingress (F5) Helm | https://artifacthub.io/packages/helm/nginx-stable/nginx-ingress |
| cert-manager Helm | https://artifacthub.io/packages/helm/cert-manager/cert-manager |
Backup & Restore
Was gesichert wird
| Was | Pfad (Server) | Ziel (lokal) | Methode |
|---|---|---|---|
| Benutzerdaten | /data |
/data/nextcloud/<env>/data/ |
rsync |
| App-Dateien | /srv/nextcloud-www |
/data/nextcloud/<env>/nextcloud/ |
rsync (data/ ausgeschlossen) |
| Datenbank | MariaDB <env>_nextcloud (Host) |
/data/nextcloud/<env>/db.sql |
mysqldump --single-transaction |
'nextcloud'@'10.42.%' eingeschränkt
(nur Pod-IPs). mysqldump läuft daher als root via Unix-Socket.
Die Credentials stehen in /root/.my.cnf (von Ansible angelegt).
Das Backup-Skript nutzt diese Verbindung automatisch.
Backup ausführen
Das Skript erwartet den Instanz-Namen als Argument. Die Instanz steuert welcher Server kontaktiert wird und wohin lokal gesichert wird.
| Instanz | Befehl | Lokales Ziel |
|---|---|---|
| Test-Server | ./scripts/nextcloud-backup.sh test |
/data/nextcloud/test/ |
| Sofie | ./scripts/nextcloud-backup.sh sofie |
/data/nextcloud/sofie/ |
| CVJM | ./scripts/nextcloud-backup.sh cvjm |
/data/nextcloud/cvjm/ |
# Beispiel: Test-Instanz sichern
cd ~/www_k3s
./scripts/nextcloud-backup.sh test
# Ablauf (gilt für alle Instanzen):
# 1. Maintenance-Modus an (kubectl exec occ maintenance:mode --on)
# 2. mysqldump --single-transaction → /data/nextcloud/<env>/db.sql
# 3. rsync /srv/nextcloud-www/ → /data/nextcloud/<env>/nextcloud/
# 4. rsync /data/ → /data/nextcloud/<env>/data/
# 5. Maintenance-Modus aus
Das Skript liegt im Repository-Verzeichnis scripts/nextcloud-backup.sh.
Sofie und CVJM sind erst nutzbar sobald IP und Hostname in
inventory/host_vars/<env>/vars.yml eingetragen sind –
das Skript prüft das und bricht mit einer Fehlermeldung ab falls noch changeme drin steht.
Restore-Workflow
Auch das Restore-Skript erwartet den Instanz-Namen als Argument:
| Instanz | Befehl | Quelle |
|---|---|---|
| Test-Server | ./scripts/nextcloud-restore.sh test |
/data/nextcloud/test/ |
| Sofie | ./scripts/nextcloud-restore.sh sofie |
/data/nextcloud/sofie/ |
| CVJM | ./scripts/nextcloud-restore.sh cvjm |
/data/nextcloud/cvjm/ |
-
Server neu aufsetzen (Infrastructure) –
testdurch Instanz-Namen ersetzen:ansible-playbook nextcloud-k3s.yml --limit test --ask-vault-pass -
Restore-Skript ausführen (überschreibt die frische Installation):
cd ~/www_k3s ./scripts/nextcloud-restore.sh testAblauf: Maintenance an → Pod stoppen → rsync App-Dateien → rsync User-Daten → SQL-Import → Ownership fix → Pod starten → files:scan → Maintenance aus
-
Ansible erneut ausführen (stellt occ-Config sicher: trusted_domains, WOPI, Apps):
ansible-playbook nextcloud-k3s.yml --limit test --ask-vault-passDieser Schritt ist bei einem Hostname-Wechsel zwingend, sonst empfohlen.
Manuelle Einzel-Befehle (Referenz)
# Maintenance-Modus
k3s kubectl exec -n nextcloud deployment/nextcloud -c nextcloud-fpm -- \
runuser -u www-data -- php /var/www/html/occ maintenance:mode --on
# Datenbank-Dump (auf dem Server)
mysqldump --single-transaction <db_name> > /tmp/nc.sql
# Ownership nach manuellem rsync
chown -R 33:33 /srv/nextcloud-www /data
chmod 0755 /srv/nextcloud-www && chmod 0770 /data
# File-Index neu aufbauen
k3s kubectl exec -n nextcloud deployment/nextcloud -c nextcloud-fpm -- \
runuser -u www-data -- php /var/www/html/occ files:scan --all
instanceid in config.php wird von Nextcloud selbst beim
ersten Start gesetzt und verknüpft die App-Dateien mit dem Appdata-Verzeichnis
(/data/appdata_<instanceid>/). Nach einem Restore aus dem Backup
ist sie automatisch korrekt – niemals manuell überschreiben.
Troubleshooting
Pod startet nicht / bleibt in CrashLoopBackOff
k3s kubectl describe pod -l app=nextcloud -n nextcloud
k3s kubectl logs deployment/nextcloud -c nextcloud-fpm -n nextcloud --previous
Typische Ursachen:
- DB nicht erreichbar: MariaDB-Service läuft nicht oder iptables-Regeln fehlen (nach Reboot?)
- config.php leer (0 Byte): Erster Start konnte
config/nicht beschreiben wegen falscher Ownership. Lösung:chown 33:33 /srv/nextcloud-www/config, leere Datei löschen, Pod neu starten. - Redis nicht erreichbar:
systemctl status redisauf dem Host prüfen
Nextcloud-Seite lädt, aber CSS/JS fehlt (Seite sieht defekt aus)
# MIME-Typen der Assets prüfen:
curl -sI "https://nextcloud.example.com/core/css/server.css" | grep content-type
# Muss: text/css sein
curl -sI "https://nextcloud.example.com/dist/core-main.js" | grep content-type
# Muss: application/javascript sein
Ursache war in der Vergangenheit ein types {}-Block im nginx-Server-Kontext,
der alle geerbten MIME-Typen überschrieben hat. Die aktuelle Konfiguration
nutzt für .mjs einen dedizierten Location-Block.
Admin-Warnung: "Reverse-Proxy-Header ist falsch"
trusted_proxies fehlt in custom.config.php oder
enthält nicht den K3s Pod-CIDR (10.42.0.0/16).
k3s kubectl exec -n nextcloud deployment/nextcloud -c nextcloud-fpm -- \
runuser -u www-data -- php /var/www/html/occ config:system:get trusted_proxies
Collabora: "Unautorisierter WOPI-Host"
Die wopi_allowlist enthält nicht den Pod-CIDR (10.42.x.x).
Collabora-Pods verbinden sich zum Nextcloud-WOPI von ihrer Pod-IP aus.
k3s kubectl exec -n nextcloud deployment/nextcloud -c nextcloud-fpm -- \
runuser -u www-data -- php /var/www/html/occ \
config:app:get richdocuments wopi_allowlist
# Korrektur:
k3s kubectl exec -n nextcloud deployment/nextcloud -c nextcloud-fpm -- \
runuser -u www-data -- php /var/www/html/occ \
config:app:set richdocuments wopi_allowlist \
--value="10.42.0.0/16,10.43.0.0/16,collabora.example.de,82.165.165.230"
Zertifikat wird nicht ausgestellt
# cert-manager Logs prüfen:
k3s kubectl logs -n cert-manager deployment/cert-manager --tail=50
# Certificate-Objekt prüfen:
k3s kubectl describe certificate nextcloud-tls -n nextcloud
# ClusterIssuer-Status:
k3s kubectl describe clusterissuer letsencrypt-prod
Häufige Ursache: Port 80 ist durch iptables oder den Provider blockiert. cert-manager benötigt Port 80 für den HTTP-01-Challenge.
CronJob läuft nicht
# Status der letzten Jobs:
k3s kubectl get cronjob nextcloud-cron -n nextcloud
k3s kubectl get jobs -n nextcloud --sort-by=.metadata.creationTimestamp | tail -5
# Logs des letzten Job-Pods:
k3s kubectl logs -n nextcloud -l job-name=nextcloud-cron-<id>
Sicherheit
Mehrschichtige Schutzstrategie
| Ebene | Maßnahme | Rolle |
|---|---|---|
| Host-Netzwerk | iptables IPv4/IPv6 – Default-Policy DROP, Portscan-Blocker, ICMP-Rate-Limit, nur 80/443/10022 offen | common_firewall |
| SSH | Port 10022, Key-only, gehärtete sshd_config, Fail2Ban | common_ssh, common_fail2ban |
| HTTP | Rate-Limiting Login (5r/s), HSTS, Security-Header | common_k3s (nginx-ingress (F5) Helm-Values + Ingress-Annotations) |
| Datenbank | User nur aus Pod-CIDR (10.42.%), MariaDB bind 0.0.0.0 + iptables |
next_mariadb, common_firewall |
| Redis | Bind auf localhost + Host-IP, iptables auf Pod-CIDR | next_redis, common_firewall |
| SELinux | Enforcing, container_file_t für HostPath-Volumes |
next_selinux |
| Audit | auditd mit Hardening-Regeln (sudo, SSH, Cron, Kernelmodule) | common_auditd |
| Rootkits | rkhunter täglicher Scan um 03:15 | common_rkhunter |
| Patches | dnf-automatic: Security-Updates täglich automatisch | common_dnf_automatic |
| Secrets | Ansible Vault, K8s Secrets (Opaque), no_log auf sensible Tasks | alle Rollen |
Firewall im Detail
Das Skript /root/fw/fw_ipv4.sh (aus common_firewall) wird per
@reboot-Cronjob nach jedem Neustart angewendet. K3s fügt danach seine eigenen
KUBE-*-Chains oben in die INPUT/OUTPUT-Chains ein.
| Feature | Detail |
|---|---|
| Default-Policy DROP | INPUT, OUTPUT und FORWARD werden sofort nach dem Flush auf DROP gesetzt – kein Fenster, in dem ungeschützter Traffic durchkommt |
| Portscan-Blocker | Jedes Paket, das keine ACCEPT-Regel trifft, trägt die Quell-IP in
/proc/net/xt_recent/portscan ein. Beim nächsten Paket dieser IP
greift --rcheck ganz oben → 24h Sperre.
Prüfen: cat /proc/net/xt_recent/portscan |
| ICMP Rate-Limit | Echo-Request max. 5/s, Burst 10 – schützt gegen ICMP-Flood von vielen Quellen. Bestehende Ping-Sessions laufen über ESTABLISHED weiter. |
| Kein server_ipv4-Whitelist | Die frühere Regel -s server_ipv4 -j ACCEPT wurde entfernt –
externe Pakete mit gespoofter Server-IP wurden dadurch akzeptiert.
Lokaler Traffic läuft sicher über Loopback (-i lo -j ACCEPT). |
| K3s OUTPUT-CIDR | OUTPUT erlaubt explizit Traffic zu Pod-CIDR (10.42.0.0/16) und Service-CIDR (10.43.0.0/16) – nötig für Kubelet-Probes, kube-proxy, Metrics-Server. Ohne diese Regeln bricht K3s-intern Port 10250. |
| IPv6 DROP-Policy | ip6tables startet direkt mit Policy DROP, ICMPv6 rate-limited,
echo-reply nur für ESTABLISHED/RELATED |
# Geblockte Pakete live verfolgen
journalctl -k -f --grep "IPTables-Dropped"
# Portscan-Blockliste anzeigen
cat /proc/net/xt_recent/portscan | awk '{print $1}'
# Firewall-Regeln neu laden (nach Reboot passiert das automatisch)
/root/fw/fw_ipv4.sh && /root/fw/fw_ipv6.sh
Wichtige Sicherheitshinweise
git commit prüfen:
ansible-vault encrypt inventory/host_vars/test/vault.yml
'nextcloud'@'10.42.%'). Niemals Port 3306
öffentlich freigeben.
StrictHostKeyChecking=accept-new setzen.