Script runit Sidekiq trop fragile : **discourse:www-data** **+ forcé** **-L log/sidekiq.log** **provoque un crash d'une seconde**

Bonjour l’équipe,

je signale un mode de défaillance dans la configuration officielle Docker/runit qui peut tuer silencieusement Sidekiq (et donc les tâches d’IA/de fond) sans aucune reconstruction ni mise à jour.

Environnement

  • Installation officielle de Discourse Docker (conteneur standard + services runit).
  • Aucune reconstruction/mise à jour juste avant le début du problème.
  • Plugin Discourse AI activé, mais l’IA a cessé de répondre.

Symptômes

  • L’IA semble activée dans l’interface d’administration, mais aucune réponse de l’IA n’apparaît.
  • Les tâches de fond (IA/embeddings/réponse automatique) semblent bloquées.
  • sv status sidekiq montre que Sidekiq meurt de manière répétée juste après le démarrage :
down: sidekiq: 1s, normally up, want up
  • Le démarrage manuel de Sidekiq fonctionne bien, donc l’application elle-même est OK :
bundle exec sidekiq -C config/sidekiq.yml
# reste actif, se connecte à Redis, traite les tâches

Ce que nous avons trouvé

Le script runit par défaut était :

exec chpst -u discourse:www-data \
  bash -lc 'cd /var/www/discourse && ... bundle exec sidekiq -e production -L log/sidekiq.log'

Deux points de fragilité :

  1. Groupe principal www-data Dans mon conteneur, les chemins inscriptibles typiques appartiennent à discourse:discourse. Tout décalage dans tmp/pids ou les chemins partagés peut faire que Sidekiq se termine au démarrage lorsqu’il est exécuté sous www-data, même si le démarrage manuel en tant que discourse fonctionne.
  2. Écriture forcée -L log/sidekiq.log dans les journaux partagés Le chemin du journal est un lien symbolique vers /shared/log/rails/sidekiq.log. Si ce fichier/répertoire est recréé avec des propriétaires/permissions différents, Sidekiq peut se terminer immédiatement avant de produire des journaux utiles.

Déclencheur connexe : logrotate échoue quotidiennement

Séparément, logrotate échouait tous les jours avec :

error: skipping "..."log" because parent directory has insecure permissions
Set "su" directive in config file ...

La cause était les permissions Debian/Ubuntu standard :

  • /var/log est root:adm avec 0775 (groupe inscriptible).
  • logrotate refuse la rotation à moins qu’une directive su globale ne soit définie. C’est le comportement attendu en amont.

Au moment où la tâche quotidienne de logrotate a échoué, elle a également recréé des fichiers sous /shared/log/rails/ (y compris sidekiq.log), ce qui a probablement interagi avec la journalisation forcée via -L et a contribué à la boucle de crash de Sidekiq « 1s ».

Correction (aucune reconstruction nécessaire)

  1. Corriger logrotate pour qu’il arrête de toucher aux journaux partagés dans un état d’échec Ajouter une directive su globale :
# /etc/logrotate.conf (en haut)
su root adm

Après cela, logrotate -v sort avec 0 et ne signale plus de permissions de répertoire parent non sécurisées.

  1. Remplacer le script runit de Sidekiq par un défaut plus robuste Passer à discourse:discourse et au sidekiq.yml standard, et ne pas forcer -L log/sidekiq.log, rend Sidekiq stable :
#!/bin/bash
exec 2>&1
cd /var/www/discourse

mkdir -p tmp/pids
chown discourse:discourse tmp/pids || true

exec chpst -u discourse:discourse \
  bash -lc 'cd /var/www/discourse && rm -f tmp/pids/sidekiq*.pid; exec bundle exec sidekiq -C config/sidekiq.yml'

Après cela :

  • sv status sidekiq reste run :
  • Les tâches IA/de fond reprennent.

Demande / suggestion

Pourrions-nous envisager de rendre le service Sidekiq officiel Docker/runit plus robuste par défaut ?

Par exemple :

  • Exécuter Sidekiq sous discourse:discourse (correspondant à la propriété typique à l’intérieur du conteneur).
  • Préférer bundle exec sidekiq -C config/sidekiq.yml.
  • Éviter de forcer un fichier journal partagé via -L log/sidekiq.log, ou le rendre résilient à la dérive des permissions du volume partagé/logrotate.

Même une note de documentation (« si Sidekiq affiche down: 1s mais que le démarrage manuel fonctionne, vérifiez /etc/service/sidekiq/run et évitez la journalisation forcée partagée ») aiderait grandement les auto-hébergeurs.

Heureux de fournir plus de journaux si nécessaire. Merci !

1 « J'aime »

Où trouvez-vous cela ? Sidekiq est lancé via le maître unicorn pour économiser de la mémoire. Je ne vois pas ce code du tout dans discourse_docker. On dirait que vous utilisez peut-être une très ancienne configuration ?

2 « J'aime »

Bonjour, permettez-moi de reformuler ceci strictement sur la base des faits d’exécution à partir du conteneur Docker officiel.

Ce que je vois dans le conteneur en cours d’exécution (faits)

Il s’agit d’une installation Docker officielle avec runit (flux de travail standard du lanceur /var/discourse ; pas de reconstruction juste avant l’incident). À l’intérieur du conteneur :

  1. Un service Sidekiq runit existe et est celui qui est supervisé
ls -l /etc/service/sidekiq/run
sv status sidekiq

Sortie pendant l’incident :

down: sidekiq: 1s, normally up, want up
  1. Le démarrage manuel de Sidekiq fonctionne
cd /var/www/discourse
sudo -u discourse bundle exec sidekiq -C config/sidekiq.yml

Ceci reste actif, se connecte à Redis et traite les tâches.

  1. La correction (patching) uniquement de /etc/service/sidekiq/run (sans reconstruction) corrige immédiatement la boucle de crash Remplacement de /etc/service/sidekiq/run par :
#!/bin/bash
exec 2>&1
cd /var/www/discourse
mkdir -p tmp/pids
chown discourse:discourse tmp/pids || true
exec chpst -u discourse:discourse \
  bash -lc 'cd /var/www/discourse && rm -f tmp/pids/sidekiq*.pid; exec bundle exec sidekiq -C config/sidekiq.yml'

Après cela :

sv status sidekiq
run: sidekiq: (pid <PID>) <SECONDS>s

Donc, Sidekiq n’est pas lancé via le maître Unicorn dans cette image ; c’est un service runit dont le script d’exécution peut entrer en boucle de crash.

Pourquoi vous pourriez ne pas voir le code exact dans

discourse_docker

Je suis d’accord, le texte littéral pourrait ne pas se trouver dans le dépôt car /etc/service/sidekiq/run est un artefact d’exécution généré/injecté pendant la construction/le démarrage de l’image, et non nécessairement un fichier verbatim dans discourse_docker. Mais c’est le service supervisé actif dans cette image officielle, comme montré ci-dessus.

Ce qui a déclenché la fragilité (faits + inférence minimale)

  • Nous avons également observé des échecs quotidiens de logrotate en raison des permissions Debian standard : /var/log = root:adm 0775, donc logrotate a refusé la rotation jusqu’à l’ajout de global su root adm.
  • Lorsque logrotate échouait, il recréait des fichiers sous /shared/log/rails/, y compris sidekiq.log.
  • Le script runit par défaut dans cette image utilisait discourse:www-data et forçait -L log/sidekiq.log dans /shared/log, ce qui rend Sidekiq très sensible à la dérive des permissions du volume partagé et peut provoquer une sortie immédiate avant que les journaux utiles ne soient écrits.

Demande / proposition

Compte tenu de ce qui précède, pourrions-nous envisager de renforcer le service Sidekiq Docker/runit par défaut ?

Défauts suggérés :

  • exécuter en tant que discourse:discourse (correspond à la propriété typique à l’intérieur du conteneur),
  • démarrer via bundle exec sidekiq -C config/sidekiq.yml,
  • éviter de forcer un -L log/sidekiq.log partagé (ou le rendre résilient).

Cela empêcherait la boucle de crash silencieuse down: 1s qui arrête toutes les tâches d’arrière-plan/IA.

Je suis disponible pour tester n’importe quelle branche/commit que vous me signalez.

Encore… je suis confus quant à l’origine de votre image :

image

Ceci est l’image officielle.

Ceci est une recherche du mot sidekiq dans le discourse docker officiel.

Il y a 3 résultats… rien concernant une unité runit. Elle est gérée via unicorn.

1 « J'aime »

Bonjour, merci, cette capture d’écran aide à clarifier la disposition.

Je suis d’accord que dans l’image officielle actuelle, Sidekiq n’est pas un service runit séparé (pas de /etc/service/sidekiq/). Il est lancé à partir de la chaîne de démarrage du service runit de unicorn, ce qui correspond à votre liste /etc/service.

Mon rapport concerne toujours un mode de défaillance à l’exécution de ce chemin de lancement de Sidekiq, qu’il réside dans une unité runit autonome ou à l’intérieur de unicorn/run :

Faits observés à l’exécution sur mon VPS (Docker officiel, pas de reconstruction/mise à jour juste avant l’incident) :

  1. Les tâches de fond se sont arrêtées et les réponses de l’IA ont cessé.

  2. Sidekiq est entré dans une boucle de crash immédiate (arrêt : 1s) lorsqu’il a été démarré par le superviseur/la chaîne de démarrage du conteneur.

  3. Le démarrage manuel en tant que discourse via bundle exec sidekiq -C config/sidekiq.yml est resté actif et a traité les tâches, donc l’application/redis fonctionnaient correctement.

  4. Simultanément, des échecs de logrotate ont provoqué la recréation de /shared/log/rails/sidekiq.log (et des chemins associés) avec des permissions différentes ; après avoir stabilisé la commande de lancement de Sidekiq (exécutée en tant que discourse:discourse, utilisation de sidekiq.yml, évitant de forcer le partage -L sidekiq.log), la boucle de crash s’est arrêtée immédiatement.

Donc, le fichier littéral /etc/service/sidekiq/run peut ne pas exister dans cette image — d’accord — mais l’étape de lancement de Sidekiq intégrée au service runit de unicorn est fragile face aux permissions du volume partagé/à la dérive de logrotate et peut tuer silencieusement Sidekiq sans reconstruction. C’est le problème fondamental.

Suggestion : veuillez envisager de renforcer le lancement de Sidekiq dans le script runit officiel de unicorn (ou là où il est généré) :

  • Exécuter Sidekiq sous discourse:discourse,
  • Préférer bundle exec sidekiq -C config/sidekiq.yml,
  • Éviter de forcer un -L log/sidekiq.log partagé (ou le rendre résilient).