von Carsten Rieger|Aktualisiert
Um die Last auf mehrere Nextcloud Instanzen zu verteilen schalten wir einen Loadbalancer (HAProxy) vor die Nextcloud Webserver. Zudem verschlüsseln wir die Kommunikation mit SSL sowohl nach extern, als auch nach intern.
In diesem Beispiel nutzen wir folgende Ubuntu 20 Server:
- haproxy – 192.168.2.205
- nc1 – 192.168.2.206
- nc2 – 192.168.2.207
Zuerst passen wir die hosts-Datei auf allen Servern an:
sudo -s
nano /etc/hosts
# Server: haproxy 127.0.0.1 localhost 127.0.1.1 haproxy ... 192.168.2.205 haproxy 192.168.2.206 nc1 192.168.2.207 nc2
# Server: nc1 127.0.0.1 localhost 127.0.1.1 nc1 ... 192.168.2.205 haproxy 192.168.2.206 nc1 192.168.2.207 nc2
# Server: nc2 127.0.0.1 localhost 127.0.1.1 nc2 ... 192.168.2.205 haproxy 192.168.2.206 nc1 192.168.2.207 nc2
Nun aktualisieren wir den haproxy-Server und installieren den HAProxy-Loadbalancer:
apt update && apt upgrade -y
apt-get install --no-install-recommends software-properties-common add-apt-repository ppa:vbernat/haproxy-2.4 apt install haproxy=2.4.\*
Um sich den Status des Loabalancers „live“ ansehen zu können nutzen wir das Tool HATOP:
wget http://archive.ubuntu.com/ubuntu/pool/universe/h/hatop/hatop_0.8.0-1.1_all.deb
dpkg -i hatop*.deb
Ruft man nach Fertigstellung des HAProxys das HATop-Tool wie folgt auf,
hatop -s /run/haproxy/admin.sock
so erhält man eine „Live“-Sicht, auf den Loadbalancer.
Am Beispiel von self signed SSL-Zertifikaten verschlüsseln wir sowohl die Kommunikation zum Loadbalancer, als auch die Kommunikation zwischen dem Loadbalancer und den Nextcloud-Instanzen. Um das realisieren zu können installieren wir
apt install ssl-cert
und generieren uns neue self-signed SSL_Zertifikate
make-ssl-cert generate-default-snakeoil -y
Um diese Zertifikate im HAProxy nutzen zu können, fügen wir das Zertifikat und den PrivatKey in eine Datei zusammen:
cat /etc/ssl/private/ssl-cert-snakeoil.key /etc/ssl/certs/ssl-cert-snakeoil.pem > /etc/haproxy/server.pem
Zudem generieren wir uns ein dhparam.pem-File, um auch den Schlüsselaustausch selbst abzusichern:
openssl dhparam -dsaparam -out /etc/haproxy/dhparam.pem 4096
Die Vorbereitungen sind nun abgeschlossen, so dass wir den HAProxy (Layer6 / http-Mode) konfigurieren können. Beginnen wir mit einer Sicherungskopie der Standardkonfiguration.
cd /etc/haproxy
cp haproxy.cfg haproxy.cfg.bak
Öffnen Sie dann die Konfigurationsdatei und ersetzen den kompletten Inhalt mit dem nachfolgenden Block:
global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners stats timeout 30s user haproxy group haproxy daemon # Default SSL material locations ca-base /etc/ssl/certs crt-base /etc/ssl/private ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets ssl-dh-param-file /etc/ssl/certs/dhparam.pem defaults log global mode http option httplog option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http listen stats bind :8443 ssl crt /etc/haproxy/server.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2 maxconn 5 mode http stats enable stats show-legends stats hide-version stats refresh 30s stats show-node stats uri / frontend NEXTCLOUD mode http bind :80 bind :443 ssl crt /etc/haproxy/server.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2 maxconn 20000 acl url_discovery path /.well-known/caldav /.well-known/carddav http-request redirect location /remote.php/dav/ code 301 if url_discovery redirect scheme https code 301 if !{ ssl_fc } http-response set-header Strict-Transport-Security max-age=63072000 acl is_certbot path_beg /.well-known/acme-challenge/ use_backend LetsEncrypt if is_certbot default_backend NEXTCLOUD backend NEXTCLOUD balance leastconn cookie SERVERID insert indirect nocache option httpchk GET /login http-check expect rstatus [2-3][0-9][0-9] server server1 192.168.2.206:443 check maxconn 10000 ssl verify none ca-file /etc/haproxy/server.pem cookie nc1 server server2 192.168.2.207:443 check maxconn 10000 ssl verify none ca-file /etc/haproxy/server.pem cookie nc2 backend LetsEncrypt mode http server certbot 127.0.0.1:9080
Zwei Alternativen aus dem Enterprise-Umfeld sehen wie folgt aus:
Beispiel 1: Layer 6 / http-mode mit http-Healthcheck:
global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners stats timeout 30s user haproxy group haproxy daemon ca-base /etc/ssl/certs crt-base /etc/ssl/private ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets tune.ssl.cachesize 1000000 ssl-dh-param-file /etc/haproxy/dhparam.pem defaults log global mode http option httplog option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http frontend Statistiken bind *:8443 ssl crt /etc/haproxy/server.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2 mode http option httplog maxconn 5 stats enable stats show-legends stats hide-version stats refresh 60s stats show-node stats uri / frontend NEXTCLOUD mode http bind :80 bind :443 ssl crt /etc/haproxy/server.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2 acl url_discovery path /.well-known/caldav /.well-known/carddav http-request redirect location /remote.php/dav/ code 301 if url_discovery redirect scheme https code 301 if !{ ssl_fc } http-response set-header Strict-Transport-Security max-age=63072000 acl is_certbot path_beg /.well-known/acme-challenge/ use_backend LetsEncrypt if is_certbot default_backend NEXTCLOUD backend NEXTCLOUD mode http fullconn 20000 balance leastconn stick-table type ip size 128m expire 2h stick on src option forwardfor option httpchk GET /login http-check expect rstatus [2-3][0-9][0-9] server NC1 192.168.2.206:443 weight 1 inter 5s downinter 20s rise 4 fall 2 check ssl verify none ca-file /etc/haproxy/server.pem on-marked-down shutdown-sessions maxconn 10000 server NC2 192.168.2.207:443 weight 1 inter 5s downinter 20s rise 4 fall 2 check ssl verify none ca-file /etc/haproxy/server.pem on-marked-down shutdown-sessions maxconn 10000 backend LetsEncrypt mode http server certbot 127.0.0.1:9080 # (c) Carsten Rieger IT-Services, v. 220102
Beispiel 2: Layer 4 / tcp-mode mit http-Healthcheck:
global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners stats timeout 30s user haproxy group haproxy daemon ca-base /etc/ssl/certs crt-base /etc/ssl/private ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets tune.ssl.cachesize 1000000 ssl-dh-param-file /etc/haproxy/dhparam.pem defaults log global mode tcp log global option tcplog option dontlognull option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http frontend Statistiken bind *:8443 ssl crt /etc/haproxy/server.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2 mode http option httplog maxconn 5 stats enable stats show-legends stats hide-version stats refresh 60s stats show-node stats uri / frontend NEXTCLOUD bind *:443 maxconn 20000 mode tcp option tcplog tcp-request inspect-delay 5s tcp-request content accept if { req_ssl_hello_type 1 } default_backend NEXTCLOUD backend NEXTCLOUD mode tcp fullconn 20000 balance leastconn stick-table type ip size 100m expire 2h stick on src option httpchk GET /login http-check expect rstatus [2-3][0-9][0-9] server server1 192.168.2.206:443 weight 1 inter 5s downinter 20s rise 4 fall 2 check check-ssl verify none on-marked-down shutdown-sessions maxconn 10000 server server2 192.168.2.207:443 weight 1 inter 5s downinter 20s rise 4 fall 2 check check-ssl verify none on-marked-down shutdown-sessions maxconn 10000 # (c) Carsten Rieger IT-Services, v. 220102
Nach dem Speichern der Konfigurationsdatei, gefolgt von einem Neustart des HAProxys
service haproxy restart
können Sie die Status-Seite des Loadbalancers bereits aufrufen (https://192.168.2.205:8443):
Vorausgesetzt, Sie haben die Nextcloud-Instanzen (Backends) bereits aufgesetzt (Lesen Sie mehr in diesem Artikel), so können Sie diese „lastverteilt“/“gebalanced“ über https://192.168.2.205 erreichen.
Überprüfen Sie sowohl in der Nextcloud, als auch in der Browserkonsole das Balancingverhalten. Dazu sehen Sie sich zum Einen die System-Einstellungen der Nextcloud an und vollziehen dort visuell nach, auf welchem Node (Knoten/Server) Sie sich befinden. Zum Anderen sehen Sie sich das Pendant, also den vom HAProxy gesetzten Cookie, in der Browserkonsole an. Beide spiegeln im Beispiel den Server nc1 wider.
Möchten Sie Let’s Encrypt Zertifikate nutzen, so installieren Sie die Let’s Encrypt Software certbot (URL):
apt install snapd snap install core && snap refresh core apt remove certbot snap install --classic certbot ln -s /snap/bin/certbot /usr/bin/certbot
Mittels des Kommandozeilentools certbot requestieren wir die SSL Zertifikate
certbot certonly --standalone --preferred-challenges http --http-01-address 127.0.0.1 --http-01-port 9080 -d ihre.domain.de --email ihre@domain.de --agree-tos --non-interactive
und nutzen dann ein Skript um alle Zertifikate zu konsolidieren:
nano /etc/haproxy/le-certificates.sh
#!/bin/bash for CERTIFICATE in `find /etc/letsencrypt/live/* -type d`; do CERTIFICATE=`basename $CERTIFICATE` cat /etc/letsencrypt/live/$CERTIFICATE/fullchain.pem /etc/letsencrypt/live/$CERTIFICATE/privkey.pem > /etc/haproxy/server.pem done exit 0
Nun machen wir das Skript noch ausführbar
chmod +x /etc/haproxy/le-certificates.sh
und führen es aus.
/etc/haproxy/le-certificates.sh
Nach einem Neustart des HAProxy-Services
services haproxy restart
werden die neuen Let’s Encypt SSL-Zertifikate schon verwendet.
Um die Zertifikate automatisch zu erneuern benötigen wir ein weiteres Skript:
nano /etc/haproxy/le-renewal.sh
#!/bin/bash certbot renew --standalone --preferred-challenges http --http-01-address 127.0.0.1 --http-01-port 9080 --post-hook "/etc/haproxy/le-certificates.sh && systemctl reload haproxy.service" --quiet exit 0
Auch dieses Skript muss wieder ausführbar gemacht werden
chmod +x /etc/haproxy/le-renewal.sh
Nun legen wir noch den wöchentlichen Cronjob an
crontab -e
@weekly /etc/haproxy/le-renewal.sh > /dev/null
wodurch die Zertifikate wöchentlich auf Aktualisierungen geprüft werden und bei einer Aktualisierung auch der haproxy-Service neu gestartet wird.
Die Installation und Einrichtung des HAProxy-Loadbalancers wurde somit erfolgreich abgeschlossen. Über Ihre Unterstützung (diese wird ordnungsgemäß versteuert!) würden sich meine Frau, meine Zwillinge und ich sehr freuen. Vielen Dank!