cf-moodle/readme.md
2025-07-08 08:01:03 +00:00

14 KiB

Moodle Deployment auf Cloud Foundry

Diese Anleitung beschreibt die Schritte für ein robustes und automatisch skalierendes Deployment der Lernplattform Moodle auf einer Cloud Foundry-Umgebung. Dies umfasst die Konfiguration für eine externe Datenbank, S3-Dateispeicher, Session-Handling für mehrere Instanzen und Autoskalierung.

Voraussetzungen

  • Zugang zur STACKIT Cloud Foundry Umgebung.
  • Das cf Command Line Interface (CLI) ist installiert.
  • Zugangsdaten den STACKIT S3-kompatiblen Object Storage sind vorhanden.
  • DNS CNAME Eintrag auf "console.apps.01.cf.eu01.stackit.cloud." in passender Domäne

In diesem Beispiel wurde es im STACKIT DNS auf die folgende Route/URL durchgeführt: moodle.cf.demo.stackit.rocks

Architektur

Die folgende Abbildung zeigt die Architektur der Moodle-Umgebung auf STACKIT als textbasiertes Flussdiagramm.

graph TD
    A[Anwender] --> B(STACKIT DNS);
    B --> C(STACKIT CDN);
    C --> D{Cloud Foundry Router};
    
    subgraph Cloud Foundry Plattform
        D --> E1[Moodle Instanz 1];
        D --> E2[Moodle Instanz ...n];
    end
    
    subgraph Backing Services
        F[(MariaDB<br/>Datenbank)];
        G[(Redis<br/>Sessions & Locks)];
        H[(S3 Object Storage<br/>Dateien)];
    end

    E1 --> F;
    E1 --> G;
    E1 --> H;
    
    E2 --> F;
    E2 --> G;
    E2 --> H;

Verzeichnisstruktur

Bevor du am Ende die Anwendung pushst, stelle sicher, dass deine lokale Verzeichnisstruktur wie folgt aussieht. Dies ist entscheidend für den Erfolg.

.
├── manifest.yml
└── moodle/
    ├── .bp-config/
    │   ├── options.json
    │   └── php/
    │       └── php.ini.d/
    │           ├── moodle.ini
    │           └── mysqli.ini
    ├── httpd/
    │   └── logs/
    │       └── httpd.pid -> /tmp/httpd.pid
    ├── lib/
    ├── logs/
    │   ├── proc-man.log -> /dev/stdout
    │   └── rewrite.log -> /dev/stdout
    ├── php/
    │   └── var/
    │       └── run/
    │           └── php-fpm.pid -> /tmp/php-fpm.pid
    ├── config.php
    ├── index.php
    └── ... (alle anderen Moodle-Dateien und Ordner)

Schritt-für-Schritt Anleitung

1. Moodle herunterladen

Erstelle ein Projektverzeichnis. Lade den Quellcode der Moodle-Version 5.0.1 mit wget herunter und entpacke ihn in einen Unterordner namens moodle.

wget https://packaging.moodle.org/stable500/moodle-5.0.1.tgz
tar -xzf moodle-5.0.1.tgz

2. Cloud Foundry Services erstellen

Erstelle die benötigten Service-Instanzen in deiner Cloud Foundry-Umgebung.

# Datenbank-Service erstellen
cf create-service <dein-mariadb-service> <plan> moodle-demo-mariadb

# Autoscaler-Service erstellen
cf create-service autoscaler autoscaler-free-plan moodle-autoscaler

3. Konfigurationsdateien anlegen und anpassen

Dies ist der wichtigste Schritt, bei dem alle spezifischen Konfigurationen für den Betrieb auf Cloud Foundry vorgenommen werden.

a) manifest.yml anlegen

Erstelle im Hauptverzeichnis deines Projekts die manifest.yml-Datei mit folgendem Inhalt. Passe die Routen sowie deine S3-Zugangsdaten an.

---
applications:
- name: moodle
  memory: 2G
  disk_quota: 2G
  buildpack: php_buildpack
  path: moodle
  routes:
  - route: deine-moodle-route.deine-domain.de
  env:
    # Buildpack anweisen, weniger Debug-Logs in Dateien zu schreiben
    BP_PHP_LOG_LEVEL: WARNING

    # S3-Zugangsdaten für den Object Storage
    S3_ACCESS_KEY: DEIN_S3_ACCESS_KEY
    S3_SECRET_KEY: DEIN_S3_SECRET_KEY
    S3_BUCKET: DEIN_S3_BUCKET_NAME
    S3_ENDPOINT: DEIN_S3_ENDPOINT_URL

  services:
  - moodle-demo-mariadb
  - moodle-autoscaler

  # Konfiguration für den Autoscaler-Service
  autoscaling:
    min_instances: 2
    max_instances: 4
    rules:
    - metric_type: cpu
      threshold: 50
      operator: ">"
      adjustment: "+1"
      breach_duration_secs: 60
      cool_down_secs: 60
    - metric_type: cpu
      threshold: 10
      operator: "<"
      adjustment: "-1"
      breach_duration_secs: 60
      cool_down_secs: 60

b) Buildpack-Konfiguration (.bp-config)

Diese Dateien steuern das Verhalten des PHP Buildpacks.

  1. Web-Verzeichnis festlegen:

    echo '{"WEBDIR": "."}' > moodle/.bp-config/options.json
    
  2. PHP-Erweiterungen und -Einstellungen konfigurieren:

    mkdir -p moodle/.bp-config/php/php.ini.d/
    echo "extension=mysqli" > moodle/.bp-config/php/php.ini.d/mysqli.ini
    echo "max_input_vars = 5000" > moodle/.bp-config/php/php.ini.d/moodle.ini
    

c) Workarounds für das schreibgeschützte Dateisystem

Erstelle die Verzeichnisse und symbolischen Links für Log- und PID-Dateien.

# Für Log-Dateien (Umleitung auf die Standardausgabe)
mkdir -p moodle/logs
ln -s /dev/stdout moodle/logs/rewrite.log
ln -s /dev/stdout moodle/logs/proc-man.log

# Für Prozess-ID-Dateien (Umleitung in das beschreibbare /tmp-Verzeichnis)
mkdir -p moodle/httpd/logs
mkdir -p moodle/php/var/run
ln -s /tmp/httpd.pid moodle/httpd/logs/httpd.pid
ln -s /tmp/php-fpm.pid moodle/php/var/run/php-fpm.pid

d) config.php erstellen und anpassen

Kopiere moodle/config-dist.php zu moodle/config.php und ersetze den gesamten Inhalt mit der folgenden Vorlage. Passe $CFG->wwwroot an deine Route an.

<?php  // Moodle configuration file

unset($CFG);
global $CFG;
$CFG = new stdClass();

//=========================================================================
// 1. WEB SITE LOCATION
//=========================================================================
$CFG->wwwroot   = 'https://deine-moodle-route.deine-domain.de';


//=========================================================================
// 2. DATABASE SETUP (wird automatisch aus VCAP_SERVICES gelesen)
//=========================================================================
$CFG->dbtype    = 'mariadb';
$CFG->dblibrary = 'native';
$CFG->prefix    = 'mdl_';
$CFG->dboptions = array (
  'dbpersist' => 0,
  'dbport' => '',
  'dbsocket' => '',
  'dbcollation' => 'utf8mb4_unicode_ci',
);

// --- Lese Datenbank-Konfiguration aus VCAP_SERVICES (inkl. SSL) ---
$vcap_services_json = getenv('VCAP_SERVICES');
if ($vcap_services_json) {
    $vcap_services = json_decode($vcap_services_json, true);
    $db_service_key = null;
    if (isset($vcap_services['appcloud-mariadb1011'])) { // Passe diesen Service-Namen ggf. an
        $db_service_key = 'appcloud-mariadb1011';
    } 

    if ($db_service_key && isset($vcap_services[$db_service_key][0]['credentials'])) {
        $db_creds = $vcap_services[$db_service_key][0]['credentials'];

        $CFG->dbhost    = $db_creds['host'];
        $CFG->dbname    = $db_creds['name'];
        $CFG->dbuser    = $db_creds['username'];
        $CFG->dbpass    = $db_creds['password'];

        if (isset($db_creds['cacrt']) && !empty($db_creds['cacrt'])) {
            $ca_cert_path = '/tmp/db-ca.crt';
            file_put_contents($ca_cert_path, $db_creds['cacrt']);
            $CFG->dboptions['ssl_ca'] = $ca_cert_path;
            $CFG->dboptions['ssl_verify_server_cert'] = true;
        }
    }
}
// --- Ende der VCAP_SERVICES-Datenbank-Konfiguration ---


//=========================================================================
// 3. DATAROOT (verweist auf das beschreibbare /tmp-Verzeichnis)
//=========================================================================
$CFG->dataroot  = '/tmp/moodledata';
$CFG->directorypermissions = 0777;


//=========================================================================
// 4. S3 FILE STORAGE (wird automatisch aus Umgebungsvariablen gelesen)
//=========================================================================
if (getenv('S3_BUCKET')) {
    $CFG->alternative_file_system_class = '\core_files\files\s3_file_system';
    $CFG->s3_bucket = getenv('S3_BUCKET');
    $CFG->s3_accesskey = getenv('S3_ACCESS_KEY');
    $CFG->s3_secretkey = getenv('S3_SECRET_KEY');
    $CFG->s3_endpoint = getenv('S3_ENDPOINT');
}


//=========================================================================
// 5. SESSION HANDLING (für mehrere Instanzen)
//=========================================================================
$CFG->session_handler_class = '\core\session\database';


//=========================================================================
// DO NOT ADD ANYTHING BELOW THIS LINE
//=========================================================================
require_once(__DIR__ . '/lib/setup.php');

4. Anwendung deployen

Führe abschließend den Push-Befehl aus dem Hauptverzeichnis deines Projekts aus.

cf push

Nach Abschluss des Deployments ist deine Moodle-Instanz unter der konfigurierten Route erreichbar und wird gemäß deiner Policy automatisch skaliert.

Einsatz im Produktiven Umfeld

Um die Anwendung in einem produktiven Umfeld laufen zu lassen, sind die folgenden weiteren Anpassungen möglich

Datenbank

Es empfielt sich für den produktiven Einsatz für die MariaDB anstelle einer Single Instanz eine Datenbank mit Replicas zu wählen:

Siehe dazu: https://docs.stackit.cloud/stackit/en/service-plans-for-mariadb-79071839.html

Neben der reinen Hochverfügbarkeit ist auch die zu erwartetende Last zu beachten.

Session Persistenz

Im aktuellen Setup wird die Persistenz der Sitzungen über die Datenbank (MariaDB) sichergestellt. Besser geeignet ist hier der Einsatz eines Redis Caches, den es ebenfalls im Marketplace der STACKIT gibt und die auf ähnliche Weise wie die Datenank an die Moodle App gebunden werden kann.

cf create-service appcloud-redis7 redis-1.4.10-replica moodle-redis

Dazu ist dann auch eine Anpassung der config.php notwendig:

// --- Redis-Konfiguration für Sessions und Locks ---
$vcap_services = json_decode(getenv('VCAP_SERVICES'), true);
// Finde den Redis-Service (der Name kann variieren, z.B. 'redis')
$redis_service = null;
if (isset($vcap_services['redis'])) {
    $redis_service = $vcap_services['redis'][0];
}

if ($redis_service) {
    $redis_creds = $redis_service['credentials'];

    // Redis für Sessions verwenden
    $CFG->session_handler_class = '\cachestore_redis\session_handler';
    $CFG->session_redis_host = $redis_creds['host'];
    $CFG->session_redis_port = $redis_creds['port'];
    $CFG->session_redis_password = $redis_creds['password'];
    $CFG->session_redis_database = 0;  // Normalerweise 0
    $CFG->session_redis_prefix = 'moodlesess_';

    // Redis auch für Application Locks verwenden (Best Practice)
    $CFG->lock_factory = '\cachestore_redis\lock_factory';
}
// --- Ende der Redis-Konfiguration ---

Die Einstellung

$CFG->session_handler_class = '\core\session\database';

ist auszukommentieren.

Nun noch die angepasste manifest Datei:

---
applications:
- name: moodle
  memory: 2G
  disk_quota: 2G
  buildpack: php_buildpack
  path: moodle
  routes:
  - route: deine-moodle-route.deine-domain.de
  env:
    # Buildpack anweisen, weniger Debug-Logs in Dateien zu schreiben
    BP_PHP_LOG_LEVEL: WARNING

    # S3-Zugangsdaten für den Object Storage
    S3_ACCESS_KEY: DEIN_S3_ACCESS_KEY
    S3_SECRET_KEY: DEIN_S3_SECRET_KEY
    S3_BUCKET: DEIN_S3_BUCKET_NAME
    S3_ENDPOINT: DEIN_S3_ENDPOINT_URL

  services:
  - moodle-demo-mariadb
  - moodle-autoscaler
  - moodle-redis  # <-- Neuer Service hinzugefügt

  # Konfiguration für den Autoscaler-Service
  autoscaling:
    min_instances: 2
    max_instances: 10
    rules:
    - metric_type: cpu
      threshold: 50
      operator: ">"
      adjustment: "+1"
      breach_duration_secs: 60
      cool_down_secs: 60
    - metric_type: cpu
      threshold: 10
      operator: "<"
      adjustment: "-1"
      breach_duration_secs: 60
      cool_down_secs: 60

CDN (Content Delivey Network)

STACKIT bietet ein eigenes CDN an, welches nun im produktiven Einsatz noch zusätzlich die Performance erhöhen kann und die überregionale Erreichbarkeit erleichtert.

Dazu legt man bei STACKIT einen CDN Eintrag auf die Orginale Route an (In diesem Beispiel: https://moodle.cf.demo.stackit.rocks )

https://docs.stackit.cloud/stackit/en/create-a-distribution-332824982.html

Das CDN erzeugt damit nun eine eigene URL, in unserem Fall v22sf2s1td84d5hz6rsf2715mw.aa.cdn.onstackit.cloud. Um diese wieder "schön" zu bekommen, legt man in STACKIT noch einen weiteren DNS Eintrag an. In unserem Fall ein CNAME Eintrag von moodle.demo.stackit.rocks auf die vom CDN Service vorgeschlagene URL (hier v22sf2s1td84d5hz6rsf2715mw.aa.cdn.onstackit.cloud.) und legt damit im CDN eine Custom Domain an:

https://docs.stackit.cloud/stackit/en/add-a-custom-domain-to-your-distribution-332824866.html

Um moodle, die neue URL mitzuteilen, braucht es noch eine Anpassung der Datei config.php. Dazu den alten Eintrag von:

$CFG->wwwroot   = 'https://moodle.cf.demo.stackit.rocks';

Durch die neuen Einstellungen:

$CFG->wwwroot   = 'https://moodle.demo.stackit.rocks';
$CFG->sslproxy  = true;

ersetzen.

Zusätzlich muss an den Anfang der config.php (nach dem <?php und vor dem unset($CFG);) noch folgendes eingetragen werden:

// --- START: Proxy-Fix für HTTPS und Host-Header ---

// 1. SSL-Problem beheben (SSL-Terminierung)
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'httpss') {
    $_SERVER['HTTPS'] = 'on';
}

// 2. Host-Header-Problem beheben (wwwroot-Redirect-Schleife)
// Überschreibt den Host, den PHP sieht, mit dem originalen Host vom CDN.
if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
    $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
    $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
}

// --- ENDE: Proxy-Fix ---

Nun die Anwendung erneut deployen mittels:

cf push --strategy rolling

Die Option "--strategy rolling" sorgt für ein Update der Anwendung ohne Downtime.

Nun kann der weltweite Zugriff auf die URL https://moodle.demo.stackit.rocks erfolgen.