Moodle Dev Fleet Deployment

This bundle deploys the development Moodle environment with the local gem-moodle Helm chart. The chart renders the Moodle web pods, MariaDB, Redis, cron, PVCs, service accounts, service, and ingress.

Table Of Contents

[[TOC]]

Fleet Layout

Fleet applies the bundle from moodle-dev/fleet.yaml into the moodle-dev namespace. The bundle depends on the namespace and secret bundles first:

  • moodle-dev/namespace: creates the namespace.
  • moodle-dev/secrets: creates the Moodle, MariaDB, and registry secrets.
  • moodle-dev: deploys the local Helm chart from this directory.
  • moodle-dev/websocket: deploys the websocket service as a separate Fleet bundle.

The Helm release name is moodle, so the main resources are named from that release, for example moodle, moodle-mariadb, moodle-redis, and moodle-cron.

Runtime Shape

The main moodle Deployment contains two containers in the same pod:

  • moodle: nginx on port 8080. It serves HTTP and proxies PHP requests to the local FPM container.
  • fpm: Moodle PHP-FPM on port 9000. It runs the Moodle install or upgrade bootstrap before starting PHP-FPM.

External traffic flows through the ingress to the moodle service, then to the nginx container. Nginx forwards PHP requests to 127.0.0.1:9000 inside the same pod.

Supporting resources:

  • moodle-mariadb: MariaDB StatefulSet for the Moodle database.
  • moodle-redis: Redis StatefulSet for Moodle sessions.
  • moodle-cron: Kubernetes CronJob that runs Moodle cron.
  • moodle-moodle: shared Moodle data PVC, mounted at /var/www/moodledata.
  • data-moodle-mariadb-0: MariaDB StatefulSet PVC.
  • data-moodle-redis-0: Redis StatefulSet PVC.

Images and Hostname

The development values use the custom GEM images:

  • PHP-FPM image: registry.gem.mintfit.hamburg/development/moodle:latest-dev
  • nginx image: registry.gem.mintfit.hamburg/development/moodle/nginx:latest-dev

The public Moodle URL is configured as:

https://moodle-development.gem.mintfit.hamburg

Change these settings in moodle-dev/values.yaml, commit the change, and let Fleet reconcile the bundle.

Startup and Upgrade Handling

The chart mounts bootstrap scripts from a ConfigMap into the FPM and cron containers.

During FPM startup the chart:

  1. Configures PHP-FPM.
  2. Prepares Moodle data and local cache directories.
  3. Waits for MariaDB.
  4. Acquires a MariaDB advisory lock.
  5. Creates or updates config.php.
  6. Injects chart-managed Moodle config, including local cache and Redis session settings.
  7. Runs Moodle install or upgrade unless moodle.skipUpgrade is true.
  8. Waits for Redis when Redis is enabled.
  9. Starts PHP-FPM.

The MariaDB advisory lock serializes install and upgrade work, so multiple web replicas do not run Moodle maintenance at the same time.

Persistence and Data Safety

The chart uses stable PVC names and does not pin a cluster-specific volumeName. That keeps the chart portable across clusters and storage setups.

The Moodle data PVC is annotated with helm.sh/resource-policy: keep, so Helm does not delete it during normal release removal. MariaDB and Redis use StatefulSet volume claim templates.

Changing the chart and redeploying should not delete Moodle data or the Moodle database PVCs. Still create a snapshot or backup before destructive operations, storage class changes, namespace deletion, or manual PVC/PV cleanup. For important production-like data, the backing PV reclaim policy should be Retain.

Multiple Replicas

The Moodle web Deployment intentionally does not render spec.replicas. This allows Rancher to manage the replica count from the UI without Fleet resetting it on every reconciliation.

The setup supports multiple Moodle web replicas because:

  • Moodle data uses a ReadWriteMany PVC.
  • Moodle sessions are stored in Redis.
  • Local cache is kept per pod in /tmp/moodle-localcache.
  • FPM has its own readiness probe, so a pod is not marked ready while nginx is up but PHP-FPM is still starting.
  • Install and upgrade work is protected by a MariaDB advisory lock.

Only scale the moodle Deployment. Do not scale moodle-mariadb or moodle-redis; both are single-replica StatefulSets in this chart.

To scale from the command line:

kubectl scale deployment/moodle -n moodle-dev --replicas=2

The same change can be made in Rancher by scaling the moodle Deployment in the moodle-dev namespace.

Redis

Redis is enabled by default and is used for Moodle sessions. The bootstrap script injects the Redis session settings into config.php.

This chart currently configures Redis for sessions only. Moodle MUC cache stores and locking can still be tuned separately in Moodle if needed.

Cron

Moodle cron is run by a Kubernetes CronJob named moodle-cron.

Current defaults:

cron:
  enabled: true
  schedule: "*/1 * * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  startingDeadlineSeconds: 300

The CronJob runs:

php admin/cli/cron.php --keep-alive=0

--keep-alive=0 makes each Kubernetes CronJob execution run once and exit. concurrencyPolicy: Forbid prevents Kubernetes from starting a new cron pod while the previous one is still running.

The Moodle administration UI controls Moodle's internal scheduled tasks. It does not change the Kubernetes CronJob schedule, resource limits, history limits, or concurrency policy. Change those settings in moodle-dev/values.yaml.

Local Validation

Run these commands before committing chart changes:

cd moodle-dev
helm lint . -f values.yaml
helm template moodle . -n moodle-dev -f values.yaml
helm template moodle . -n moodle-dev -f values.yaml > /tmp/moodle-dev-rendered.yaml
kubectl apply --dry-run=server -n moodle-dev -f /tmp/moodle-dev-rendered.yaml

The server-side dry run requires access to the target cluster.

Inspecting the Deployment

Useful status commands:

kubectl get deploy,statefulset,cronjob,pod,pvc -n moodle-dev
kubectl get ingress -n moodle-dev
kubectl get job,pod -n moodle-dev -l app.kubernetes.io/name=moodle-cron

Useful logs:

kubectl logs -n moodle-dev deploy/moodle -c fpm --tail=100
kubectl logs -n moodle-dev deploy/moodle -c moodle --tail=100
kubectl logs -n moodle-dev job/<cron-job-name> --tail=100

For a specific pod:

kubectl describe pod -n moodle-dev <pod-name>
kubectl logs -n moodle-dev <pod-name> -c fpm --tail=100
kubectl logs -n moodle-dev <pod-name> -c moodle --tail=100

Operational Notes

Edit moodle-dev/values.yaml for normal configuration changes, then commit and push so Fleet reconciles the desired state. Avoid long-lived manual edits to rendered Kubernetes resources because Fleet will continue reconciling from Git.

The current FPM and cron image startup still runs as root because the image prepares files and ownership at container startup. That can trigger warnings in namespaces enforcing the Kubernetes restricted Pod Security profile. A full fix requires changing the image and startup flow to run without root privileges.

If Moodle cron fails on an old ad-hoc task from a removed plugin or failed copy operation, fix the stale task in Moodle or the Moodle database. That is application data, not a chart scheduling problem.