From d58d5af0df7950b8a6512c8d9fc5a009818ca9f1 Mon Sep 17 00:00:00 2001 From: Daniel Quintero Date: Fri, 7 Aug 2026 14:35:33 -0500 Subject: [PATCH] fix(chart): gate the maintenance cronjob on actual GLPI/DB readiness, not just file existence ## Problem The maintenance CronJob (front/cron.php, schedule every N minutes) is a normal resource created in the same Sync phase as nginx/php-fpm, without waiting for the post-install glpi-db-install/glpi-db-configure/ glpi-cache-configure Jobs (which run as Helm hooks, post-install hook-weight 10/20/30) to finish. On a fresh install, this lets the first scheduled run(s) fire before GLPI is actually usable, failing with "Unable to load the GLPI configuration from the database" - self-heals via Kubernetes' OnFailure retry, but noisy (BackOff events, wasted retries) on every fresh install. ## First attempt (insufficient - documented here for the record) Initially added an initContainer that waits for `/etc/glpi/config/config_db.php` to exist before running cron.php. Verified empirically on a live kind cluster that this REDUCES but does NOT eliminate the race: `glpi-db-install.sh` runs `db:install --reconfigure`, which writes that same config_db.php file as soon as the *schema* is created (hook-weight 10) - well before db:configure/ cache:configure (weight 20/30) finish. Across 4 live re-installs with just the file-existence check, saw BackOff on roughly half of them, confirmed via captured pod state: the initContainer completed (file existed) but the "base" container still failed immediately with "Unable to load the GLPI configuration from the database". ## Actual fix Replaced the file-existence check with a loop of the exact read-only command glpi-db-install.sh itself already uses to decide whether the schema is fully installed: `php bin/console database:check_schema_integrity` (exit 0 once GLPI is genuinely ready to serve requests). This requires switching the initContainer from busybox to the real php-fpm image (needed for the php binary + GLPI codebase) and giving it the same glpi-config/glpi-secret/MARIADB_PASSWORD env wiring as the main container, since the check needs a working DB connection to run. ## Testing - helm lint --strict: 0 failures - helm template: initContainer renders with the php-fpm image and the correct env/secretKeyRef wiring - Live kind cluster (kindest/node:v1.35.0), schedule accelerated to every minute to stress-test the install-time race: 5 consecutive fresh install cycles (helm uninstall + delete pvc + fresh helm install each time), zero BackOff/failed cronjob events across all 5 - compared to a ~50% failure rate observed with the file-existence-only check across 4 separate live runs beforehand - Confirmed GLPI still serves its real login page (HTTP 200) after each install --- helm/templates/glpi-cronjob.yaml | 56 ++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/helm/templates/glpi-cronjob.yaml b/helm/templates/glpi-cronjob.yaml index 68aa3b9..0d95245 100644 --- a/helm/templates/glpi-cronjob.yaml +++ b/helm/templates/glpi-cronjob.yaml @@ -38,6 +38,62 @@ spec: affinity: {{- toYaml . | nindent 12 }} {{- end }} + initContainers: + # On a fresh install, this CronJob (schedule every N minutes) is a normal + # resource created in the same Sync phase as nginx/php-fpm, without waiting for + # the post-install glpi-db-install/glpi-db-configure/glpi-cache-configure Jobs + # (which run as Helm hooks, post-install hook-weight 10/20/30) to finish. Without + # this guard, front/cron.php can fire before GLPI is actually ready, failing with + # "Unable to load the GLPI configuration from the database". + # + # A naive `[ -f config_db.php ]` file-existence check is NOT sufficient: db:install + # (hook-weight 10) already runs with --reconfigure and writes that same file as + # soon as the schema is created, well before db:configure/cache:configure (weight + # 20/30) finish - verified empirically this still let cron.php race ~1 in 2 runs on + # a fresh install. Instead, this loops the exact read-only command + # glpi-db-install.sh itself already uses to decide whether the schema is fully + # installed (`database:check_schema_integrity`), using the real php-fpm image + # (needed for the php binary + GLPI codebase, unlike the lighter busybox image + # used by wait-for-mariadb elsewhere) - the actual precondition front/cron.php + # needs, not an indirect proxy for it. + - name: wait-for-glpi-ready + image: {{ include "glpi.phpfpm.image" . }} + imagePullPolicy: {{ .Values.glpi.phpfpm.image.pullPolicy }} + {{- with .Values.glpi.securityContext }} + securityContext: + {{- toYaml . | nindent 16 }} + {{- end }} + envFrom: + - configMapRef: + name: glpi-config + - secretRef: + name: glpi-secret + {{- if .Values.mariadb.enabled }} + env: + - name: MARIADB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "glpi.mariadb.secretName" . }} + key: {{ include "glpi.mariadb.secretPasswordKey" . }} + {{- end }} + command: + - sh + - -c + - until php bin/console database:check_schema_integrity > /dev/null 2>&1; do sleep 3; done + volumeMounts: + - name: etc + mountPath: /etc/glpi + {{- if .Values.glpi.securityContext.readOnlyRootFilesystem }} + - name: tmp + mountPath: /tmp + {{- end }} + resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi containers: - image: {{ include "glpi.phpfpm.image" . }} imagePullPolicy: {{ .Values.glpi.phpfpm.image.pullPolicy }}