haproxy Archives - credativ®

Moodle ist eine beliebte Open Source Online-Lernplattform. Gerade in Zeiten von COVID19 und Distanzunterricht hat sich die Bedeutung von Moodle für Schulen und Universität weiter verstärkt, da ganze Bundesländer ihren Schulunterricht innerhalb weniger Tage auf Moodle und andere Plattformen wie BigBlueButton umgestellt haben. Dies führt unweigerlich zu Skalierungs-Problemen, wenn plötzlich mehrere zehntausend Schüler auf Moodle zugreifen müssen. Neben der Skalierung der Moodle Applikation selber muss auch die Datenbank bedacht werden, hier kann unter anderem PostgreSQL verwendet werden. Im Folgenden werden Möglichkeiten zum Load-Balancing für Moodle unter PostgreSQL vorgestellt.

Hochverfügbarkeit durch Patroni

Eine Online-Lernplattform ist aus Sicht des Bildungssystems kritische Infrastruktur und sollte dementsprechend Hochverfügbar ausgelegt sein, was insbesondere die Datenbank betrifft. Eine gute Lösung für PostgreSQL ist hier Patroni, über dessen Debian-Integration wir bereits früher berichtet haben. Kurz zusammengefasst verwendet Patroni einen “Distributed Consensus Store” (DCS) um aus einem typischerweise 3-Knoten Cluster einen sogenannten Leader zu wählen bzw. bei Ausfalls eines Leaders ein Failover zu initiieren und einen neuen Leader zu wählen, ohne dabei in ein Split-Brain Szenario zu gelangen. Zusätzlich bietet Patroni eine REST-API, die für die Kommunikation der Knoten untereinander und von dem patronictl Programm verwendet wird, z.B. um die Postgres-Konfiguration Online auf allen Knoten zu ändern oder einen Switchover einzuleiten.

Client-Lösungen für Hochverfügbarkeit

Aus Sicht von Moodle muss allerdings zusätzlich gewährleistet sein, dass es mit dem Leader verbunden ist, da sonst keine schreibenden Transaktionen möglich sind. Herkömmliche Hochverfügbarkeits-Lösungen wie Pacemaker verwenden hier virtuelle IPs (VIPs), die im Failover-Fall zum neuen Primary-Knoten geschwenkt werden. Für Patroni gibt es stattdessen das vip-manager Projekt, welches den Leader-Key im DCS überwacht und Cluster-VIP lokal setzt oder entfernt. Dieses ist ebenfalls in Debian integriert.

Eine Alternative ist die Verwendung von Client-seitigem Failover basierend auf der libpq-Bibliothek von PostgreSQL. Hierfür werden alle Cluster-Mitglieder im Verbindungs-String aufgelistet und die Verbindungs-Option target_session_attrs=read-write gewählt. So konfiguriert versucht der Client bei einem Verbindungs-Abbruch die anderen Knoten zu erreichen, bis ein neuer Primary gefunden wird.

Eine andere Möglichkeit ist HAProxy, ein hoch-skalierender TCP/HTTP Load-Balancer. Durch die Möglichkeit periodische Health-Checks auf die Patroni REST-API der einzelnen Knoten durchzuführen kann es den aktuellen Leader ermitteln und Client-Anfragen dorthin weiterleiten.

Datenbank-Konfiguration von Moodle

Die Verbindung von Moodle zu einer PostgreSQL-Datenbank wird in config.php konfiguriert, z.B. für eine einfache Stand-Alone Datenbank:

$CFG->dbtype    = 'pgsql';
$CFG->dblibrary = 'native';
$CFG->dbhost    = '192.168.1.1';
$CFG->dbname    = 'moodle';
$CFG->dbuser    = 'moodle';
$CFG->dbpass    = 'moodle';
$CFG->prefix    = 'mdl_';
$CFG->dboptions = array (
  'dbport' => '',
  'dbsocket' => ''
);

Hier wird der Standard-Port 5432 verwendet.

Falls Streaming-Replication verwendet wird, können die Standbys zusätzlich als readonly definiert werden und einen eigenen Datenbank-Benutzer (der lediglich Leseberechtigungen benötigt) zugeordnet werden:

$CFG->dboptions = array (
[...]
  'readonly' => [
    'instance' => [
      [
      'dbhost' => '192.168.1.2',
      'dbport' =>  '',
      'dbuser' => 'moodle_safereads',
      'dbpass' => 'moodle'
      ],
      [
      'dbhost' => '192.168.1.3',
      'dbport' =>  '',
      'dbuser' => 'moodle_safereads',
      'dbpass' => 'moodle'
      ]
    ]
  ]
);

Failover/Load-Balancing mit libpq

Wenn allerdings ein hochverfügbarer Postgres-Cluster mit Patroni verwendet wird kann wie oben beschrieben der Primary im Failover- oder Switchover-Fall wechseln. Moodle bietet hier keine Möglichkeit generische Datenbank-Optionen zu setzen und damit ist die Einstellung von target_session_attrs=read-write direkt nicht möglich. Wir haben hierfür einen Patch entwickelt und in den Moodle Tracker eingestellt. Dieser erlaubt die zusätzliche Option 'dbfailover' => 1, im $CFG->dboptions-Array, was die nötige Verbindungs-Option target_session_attrs=read-write hinzufügt. Eine angepasste config.php sähe damit z.B. so aus:

$CFG->dbtype    = 'pgsql';
$CFG->dblibrary = 'native';
$CFG->dbhost    = '192.168.1.1,192.168.1.2,192.168.1.3';
$CFG->dbname    = 'moodle';
$CFG->dbuser    = 'moodle';
$CFG->dbpass    = 'moodle';
$CFG->prefix    = 'mdl_';
$CFG->dboptions = array (
  'dbfailover' => 1,
  'dbport' => '',
  'dbsocket' => '',
  'readonly' => [
    'instance' => [
      [
      'dbhost' => '192.168.1.1',
      'dbport' =>  '',
      'dbuser' => 'moodle_safereads',
      'dbpass' => 'moodle'
      ],
      [
      'dbhost' => '192.168.1.2',
      'dbport' =>  '',
      'dbuser' => 'moodle_safereads',
      'dbpass' => 'moodle'
      ],
      [
      'dbhost' => '192.168.1.3',
      'dbport' =>  '',
      'dbuser' => 'moodle_safereads',
      'dbpass' => 'moodle'
      ]
    ]
  ]
);

Failover/Load-Balancing mit HAProxy

Wenn stattdessen HAProxy verwendet werden soll, dann muss entsprechend der HAProyx-Host als $CFG->dbhost eingetragen werden, z.B. 127.0.0.1 wenn HAProxy lokal auf dem/den Moodle Server(n) läuft. Zusätzlich kann ein zweiter Port (z.B. 65432) für lesende Anfragen definiert werden, der analog zu einem Streaming-Replication Standby als readonly in $CFG->dboptions konfiguriert wird. Die config.php sähe dann z.B. so aus:

$CFG->dbtype    = 'pgsql';
$CFG->dblibrary = 'native';
$CFG->dbhost    = '127.0.0.1';
$CFG->dbname    = 'moodle';
$CFG->dbuser    = 'moodle';
$CFG->dbpass    = 'moodle';
$CFG->prefix    = 'mdl_';
$CFG->dboptions = array (
  'dbport' => '',
  'dbsocket' => '',
  'readonly' => [
    'instance' => [
      'dbhost' => '127.0.0.1',
      'dbport' =>  '65432',
      'dbuser' => 'moodle_safereads',
      'dbpass' => 'moodle'
    ]
  ]
);

Die HAProxy Konfigurations-Datei haproxy.cfg kann beispielhaft folgendermaßen aussehen:

global
    maxconn 100

defaults
    log global
    mode tcp
    retries 2
    timeout client 30m
    timeout connect 4s
    timeout server 30m
    timeout check 5s

listen stats
    mode http
    bind *:7000
    stats enable
    stats uri /

listen postgres_write
    bind *:5432
    mode tcp
    option httpchk
    http-check expect status 200
    default-server inter 3s fall 3 rise 3 on-marked-down shutdown-sessions
    server pg1 192.168.1.1:5432 maxconn 100 check port 8008
    server pg2 192.168.1.2:5432 maxconn 100 check port 8008
    server pg3 192.168.1.3:5432 maxconn 100 check port 8008

HAProxy erwartet eingehende schreibende Verbindungen (postgres_write) auf Port 5432 und sendet sie an Port 5432 der Cluster-Mitglieder weiter. Der Primary wird durch einen HTTP-Check auf Port 8008 (dem Standard Patroni REST-API Port) ermittelt; Patroni gibt hier für den Primary den Status 200 und für Standbys den Status 503 zurück.

Für die lesenden Abfragen (postgres_read) muss entschieden werden, ob der Primary auch read-only Anfragen beantworten soll oder nicht. Wenn dies der Fall ist kann ein einfacher Postgres-Check verwendet werden; allerdings kann dies zu Einträgen im Postgres-Log bzgl. fehlerhafter oder unvollständiger Logins führen:

listen postgres_read
    bind *:65432
    mode tcp
    balance leastconn
    option pgsql-check user haproxy
    default-server inter 3s fall 3 rise 3 on-marked-down shutdown-sessions
    server pg1 192.168.1.1:5432 check
    server pg2 192.168.1.2:5432 check
    server pg3 192.168.1.3:5432 check

Wenn der Primary nicht am Read-Scaling teilnehmen soll kann einfach der gleiche HTTP-Check wie in der postgres_write Sektion verwendet werden, wobei diesmal der HTTP-Status 503 erwartet wird:

listen postgres_read
    bind *:65432
    mode tcp
    balance leastconn
    option httpchk
    http-check expect status 503
    default-server inter 3s fall 3 rise 3 on-marked-down shutdown-sessions
    server pg1 192.168.1.1:5432 check port 8008
    server pg2 192.168.1.2:5432 check port 8008
    server pg3 192.168.1.3:5432 check port 8008

Überarbeitetes Ansible Playbook

HAProxy-Unterstützung wurde auch in Version 0.3 unseres Ansible-Playbooks für die automatisierte Einrichtung eines Drei-Knoten-PostgreSQL-Patroni-Clusters unter Debian implementiert. Mit der neuen Variable haproxy_primary_read_scale kann entschieden werden, ob HAProxy Anfragen auf den Read-Only Port auch an den Primary-Knoten oder nur an die Follower geben soll.

Unterstützung

Falls Sie Unterstützung bei PostgreSQL, Patroni, HAProxy, Moodle oder anderer Software benötigen, steht Ihnen unser Open Source Support Center zur Verfügung – Falls gewünscht auch 24 Stunden am Tag, an 365 Tagen im Jahr.

Wir freuen uns über Ihre Kontaktaufnahme.