hel_Sinki
(hel Sinki)
1
大家好,
报告一下官方 Docker/runit 设置中存在的一种故障模式,它可以在不进行任何重建或升级的情况下静默终止 Sidekiq(进而终止 AI/后台作业)。
环境
- 官方 Discourse Docker 安装(标准容器 + runit 服务)。
- 问题开始前没有进行重建/升级。
- Discourse AI 插件已启用,但 AI 停止回复。
症状
- 管理界面中 AI 显示为启用,但没有 AI 回复出现。
- 后台作业(AI/嵌入/自动回复)似乎卡住了。
sv status sidekiq 显示 Sidekiq 在启动后反复崩溃:
down: sidekiq: 1s, normally up, want up
- 手动启动 Sidekiq 运行正常,说明应用程序本身没问题:
bundle exec sidekiq -C config/sidekiq.yml
# 保持运行,连接到 Redis,处理作业
我们发现的情况
默认的 runit 脚本是:
exec chpst -u discourse:www-data \
bash -lc 'cd /var/www/discourse && ... bundle exec sidekiq -e production -L log/sidekiq.log'
两个脆弱点:
- 主用户组 www-data 在我的容器中,典型的可写路径归 discourse:discourse 所有。当 Sidekiq 在 www-data 权限下启动时,tmp/pids 或共享路径的任何权限漂移都可能导致 Sidekiq 在启动时退出,即使以 discourse 用户手动启动是正常的。
- 强制的 -L log/sidekiq.log 写入共享日志 该日志路径是指向
/shared/log/rails/sidekiq.log 的符号链接。如果该文件/目录以不同的所有权/权限重新创建,Sidekiq 可能会立即退出,而无法产生有用的日志。
相关触发因素:logrotate 每日失败
另外,logrotate 每天都会失败并报告:
error: skipping "...\log" because parent directory has insecure permissions
Set "su" directive in config file ...
原因是标准的 Debian/Ubuntu 权限设置:
/var/log 是 root:adm,权限为 0775(组可写)。
- 除非设置全局 su 指令,否则 logrotate 会拒绝轮换。这是预期的上游行为。
在每日 logrotate 作业失败的时刻,它也重新创建了 /shared/log/rails/ 下的文件(包括 sidekiq.log),这很可能与强制的 -L 日志记录相互作用,并导致了 Sidekiq 的“1秒崩溃”循环。
修复方法(无需重建)
- 修复 logrotate,使其停止在失败状态下触碰共享日志 添加一个全局 su 指令:
# /etc/logrotate.conf (顶部)
su root adm
之后,logrotate -v 退出代码为 0,并且不再报告父目录权限不安全。
- 用更健壮的默认值替换 Sidekiq runit 脚本 切换到 discourse:discourse 和标准的 sidekiq.yml,并且不强制 -L log/sidekiq.log,使 Sidekiq 稳定:
#!/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'
执行此操作后:
sv status sidekiq 保持运行状态:
- AI/后台作业恢复。
请求/建议
我们能否考虑默认情况下使官方 Docker/runit Sidekiq 服务更加健壮?
例如:
- 在 discourse:discourse 用户下运行 Sidekiq(与容器内典型所有权匹配)。
- 优先使用
bundle exec sidekiq -C config/sidekiq.yml。
- 避免通过
-L log/sidekiq.log 强制指定共享日志文件,或者使其能够抵御 logrotate/共享卷权限漂移。
即使是添加一个文档说明(“如果 Sidekiq 显示 down: 1s 但手动启动正常,请检查 /etc/service/sidekiq/run 并避免强制共享日志记录”)也会对自托管用户有很大帮助。
如果需要更多日志,我很乐意提供。谢谢!
1 个赞
sam
(Sam Saffron)
2
您在哪里找到这个的?Sidekiq 是通过 unicorn master 启动的,以节省内存。在 discourse_docker 中完全没有看到这段代码。看起来您可能使用的是非常旧的设置?
2 个赞
hel_Sinki
(hel Sinki)
3
您好——我将严格根据官方 Docker 容器的运行时事实重述此事。
我在运行的容器中看到的情况(事实)
这是一个官方 Docker 安装,带有 runit(标准的 /var/discourse 启动器工作流程;事件发生前没有重建)。在容器内:
- 存在一个 runit Sidekiq 服务,并且是正在被监控的那个
ls -l /etc/service/sidekiq/run
sv status sidekiq
事件期间的输出:
down: sidekiq: 1s, normally up, want up
- 手动启动 Sidekiq 成功
cd /var/www/discourse
sudo -u discourse bundle exec sidekiq -C config/sidekiq.yml
这会保持运行,连接到 Redis,并处理作业。
- 仅修补 /etc/service/sidekiq/run (不重建)立即修复了崩溃循环 将 /etc/service/sidekiq/run 替换为:
#!/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'
之后:
sv status sidekiq
run: sidekiq: (pid <PID>) <SECONDS>s
因此,在这个官方镜像中,Sidekiq 不是通过 Unicorn master 启动的;它是一个 runit 服务,其运行时脚本可能会陷入崩溃循环。
为什么您可能看不到确切的代码在
discourse_docker 中
我同意确切的文本可能不在仓库中,因为 /etc/service/sidekiq/run 是在镜像构建/启动期间生成/注入的运行时工件,不一定是 discourse_docker 中逐字对应的文件。但如上所示,它是这个官方镜像中活动的受监控服务。
触发脆弱性的原因(事实 + 最少推断)
- 我们还观察到由于标准的 Debian 权限设置(/var/log = root:adm 0775),logrotate 每天都会失败,因此在添加全局 su root adm 之前,logrotate 拒绝轮换。
- 当 logrotate 失败时,它会在 /shared/log/rails/ 下重新创建文件,包括 sidekiq.log。
- 此镜像中默认的 runit 脚本使用 discourse:www-data 并强制将 -L log/sidekiq.log 写入 /shared/log,这使得 Sidekiq 对共享卷权限漂移非常敏感,并可能在有有用日志之前立即退出。
请求/建议
鉴于以上情况,我们能否考虑加强默认的 Docker/runit Sidekiq 服务?
建议的默认设置:
- 以 discourse:discourse 身份运行(与容器内的典型所有权匹配),
- 通过 bundle exec sidekiq -C config/sidekiq.yml 启动,
- 避免强制共享的 -L log/sidekiq.log(或使其具有弹性)。
这将防止导致所有后台/AI 作业停止的静默的 down: 1s 崩溃循环。
我很乐意测试您指向我的任何分支/提交。
sam
(Sam Saffron)
4
我又…对你从哪里获取图片感到困惑:

这是官方图片。
这是在官方 discourse docker 中搜索单词 sidekiq 的结果。
https://github.com/search?q=repo%3Adiscourse%2Fdiscourse_docker%20sidekiq&type=code
有 3 个匹配项…但没有关于 runit 单元的内容。它由 unicorn 管理。
1 个赞
hel_Sinki
(hel Sinki)
5
您好,感谢您的截图,它有助于阐明布局。
我同意,在当前的官方镜像中,Sidekiq 不是一个独立的 runit 服务(没有 /etc/service/sidekiq/)。它是从 unicorn runit 服务的启动链中启动的,这与您的 /etc/service 列表相符。
我的报告仍然是关于该 Sidekiq 启动路径的运行时故障模式,无论它存在于独立的 runit 单元中还是在 unicorn/run 内部:
我的 VPS 上的运行时事实(官方 Docker,事件发生前没有重建/升级):
- 后台作业停止,AI 回复停止。
- 当由容器的 Supervisor/启动链启动时,Sidekiq 进入立即崩溃循环(停机时间:1 秒)。
- 以
discourse 身份手动启动 bundle exec sidekiq -C config/sidekiq.yml 保持运行并处理作业,因此应用程序/redis 没问题。
- 与此同时,
logrotate 失败导致 /shared/log/rails/sidekiq.log(及相关路径)以不同的权限重新创建;在稳定 Sidekiq 启动命令(以 discourse:discourse 身份运行,使用 sidekiq.yml,避免强制使用共享的 -L sidekiq.log)后,崩溃循环立即停止。
因此,字面上的文件 /etc/service/sidekiq/run 在此镜像中可能不存在——同意——但嵌入在 unicorn runit 服务中的 Sidekiq 启动步骤对共享卷权限/logrotate 漂移很脆弱,并且可以在没有重建的情况下静默地杀死 Sidekiq。这是核心问题。
建议: 请考虑在官方 unicorn runit 脚本(或生成它的位置)中加固 Sidekiq 启动:
-
以 discourse:discourse 身份运行 Sidekiq,
-
优先使用 bundle exec sidekiq -C config/sidekiq.yml,
-
避免强制使用共享的 -L log/sidekiq.log(或使其具有弹性)。