根据您的应用程序需求和设计,您实际上可能希望将不同容器中的工作人员分开以执行不同的任务。
但是,如果资源使用率较低并且将多个工作器组合在一个容器中是有意义的,则可以通过入口点脚本来实现。
Edit 2019-12-05:运行一段时间后。对于生产用途来说,这不是一个好主意。 2 个警告:
存在后台工作人员静默退出但未在前台捕获的风险。 tail -f 将继续运行,但 docker 不会知道后台工作人员已停止。根据您的 celery 调试级别设置,日志可能会显示一些指示,但是当您执行 docker ps 时,docker 不知道。为了可靠,工人需要在失败时重新启动,这给我们带来了使用supervisord的建议。
-
随着容器的启动和停止(但未删除),docker 容器状态将保持不变。这意味着,如果您的 celery worker 确实依赖 pidfile 进行识别,但存在不正常的关闭,则 pidfile 有可能被保留,并且 worker 不会干净地重新启动,即使使用docker stop; docker start。这是由于 celery 启动检测到之前不干净关闭的剩余 PIDfile 的存在。为了防止出现多个实例,重新启动的工作人员会通过“找到 PIDfile,celery 已经在运行?”来停止自己。必须使用docker rm 或docker-compose down; docker-compose up 删除整个容器。几种处理方法:
a.容器必须是 run 并带有 --rm 标志才能在容器停止后删除容器。
b.也许在celery multi 或celery worker 命令中不包括--pidfile 参数会更好。
总结建议:使用supervisord 可能会更好。
现在,进入细节:
Docker 容器需要一个前台任务才能运行,否则容器将退出。这将进一步解决。
此外,celery worker 可能会运行长时间运行的任务,需要响应 docker 的关闭 (SIGTERM) 信号到gracefully shutdown,即在关闭或重启之前完成长时间运行的任务。
要实现docker信号的传播和处理,最好在docker的exec form的dockerfile中声明entrypoint,你也可以在docker-compose file中这样做
另外,由于 celery multi 在后台工作,docker 看不到任何日志。您需要能够在前台显示日志,让docker logs 能够看到正在发生的事情。我们将通过为 celery 多工作程序设置日志文件并在控制台前台显示 tail -f <logfile_pattern> 来无限期运行。
我们需要实现三个目标:
- 使用前台任务运行 docker 容器
- 接收、
trap 并处理 docker 关闭信号
- 优雅地关闭工作器
对于#1,我们将运行tail -f &,然后运行wait作为前台任务。
对于#2,这是通过设置trap 函数并捕获信号来实现的。要使用 trap 函数接收和处理信号,wait 必须是正在运行的前台任务,在 #1 中实现。
对于#3,我们将在celery multi start的启动过程中运行celery multi stop <number_of_workers_in_start_command>和其他自变量参数。
这是我写的gist,复制到这里:
#!/bin/sh
# safety switch, exit script if there's error. Full command of shortcut `set -e`
set -o errexit
# safety switch, uninitialized variables will stop script. Full command of shortcut `set -u`
set -o nounset
# tear down function
teardown()
{
echo " Signal caught..."
echo "Stopping celery multi gracefully..."
# send shutdown signal to celery workser via `celery multi`
# command must mirror some of `celery multi start` arguments
celery -A config.celery_app multi stop 3 --pidfile=./celery-%n.pid --logfile=./celery-%n%I.log
echo "Stopped celery multi..."
echo "Stopping last waited process"
kill -s TERM "$child" 2> /dev/null
echo "Stopped last waited process. Exiting..."
exit 1
}
# start 3 celery worker via `celery multi` with declared logfile for `tail -f`
celery -A config.celery_app multi start 3 -l INFO -Q:1 queue1 -Q:2 queue1 -Q:3 queue3,celery -c:1-2 1 \
--pidfile=./celery-%n.pid \
--logfile=./celery-%n%I.log
# start trapping signals (docker sends `SIGTERM` for shudown)
trap teardown SIGINT SIGTERM
# tail all the logs continuously to console for `docker logs` to see
tail -f ./celery*.log &
# capture process id of `tail` for tear down
child=$!
# waits for `tail -f` indefinitely and allows external signals,
# including docker stop signals, to be captured by `trap`
wait "$child"
使用上面的代码作为入口点脚本文件的内容,并根据需要进行相应的修改。
在dockerfile或docker-compose文件中以exec形式声明:
ENTRYPOINT ["entrypoint_file"]
celery worker 之后可以在 docker 容器中运行,也可以优雅地停止。