Docker容器中的Celery多进程

15

我有一个 Python 应用程序,其中包含在 Docker 容器中的 Celery。我想要几个具有不同队列的工作进程。

celery worker -c 3 -Q queue1
celery worker -c 7 -Q queue2,queue3

但我不在Docker Compose中这样做。我发现了Celery Multi。我尝试使用它。

version: '3.2'
services:
  app:
    image: "app"
    build:
      context: .
    networks:
      - net
    ports:
      - 5004:5000
    stdin_open: true
    tty: true
    environment:
      FLASK_APP: app/app.py
      FLASK_DEBUG: 1
    volumes:
      - .:/home/app
  app__celery:
    image: "app"
    build:
      context: .
    command: sh -c 'celery multi start 2 -l INFO -c:1 3 -c:2 7 -Q:1 queue1 -Q:2 queue2,queue3'

但是我明白...

app__celery_1  |    > celery1@1ab37081acb9: OK
app__celery_1  |    > celery2@1ab37081acb9: OK
app__celery_1 exited with code 0

我的celery容器关闭了,如何防止它关闭并获取其日志?

更新:Celery multi创建了后台进程。如何在前台启动celery multi?

3个回答

14

我是这样完成这个任务的。我使用了supervisord而不是celery multi。Supervisord在前台启动,我的容器没有关闭。

command: supervisord -c supervisord.conf

我将所有队列添加到了supervisord.con中。

[program:celery]
command = celery worker -A app.celery.celery -l INFO -c 3 -Q q1
directory = %(here)s
startsecs = 5
autostart = true
autorestart = true
stopwaitsecs = 300
stderr_logfile = /dev/stderr
stderr_logfile_maxbytes = 0
stdout_logfile = /dev/stdout
stdout_logfile_maxbytes = 0

[program:beat]
command = celery -A app.celery.celery beat -l INFO --pidfile=/tmp/beat.pid
directory = %(here)s
startsecs = 5
autostart = true
autorestart = true
stopwaitsecs = 300
stderr_logfile = /dev/stderr
stderr_logfile_maxbytes = 0
stdout_logfile = /dev/stdout
stdout_logfile_maxbytes = 0

[supervisord]
loglevel = info
nodaemon = true
pidfile = /tmp/supervisord.pid
logfile = /dev/null
logfile_maxbytes = 0

13
根据您的应用需求和设计,您可能需要将不同任务的工作人员分离到不同的容器中。
但是,如果资源使用率较低,并且合并多个工作人员到单个容器中是有意义的,则可以通过入口脚本实现。 编辑2019-12-05: 运行一段时间后,这对于生产使用来说并不是一个好主意。有两个注意点:
  1. 存在后台工作进程默默退出但前台没有捕获的风险。 tail -f 将继续运行,但 docker 将不知道后台工作进程已停止。根据您 celery 的调试级别设置,日志可能会显示一些指示,但当您执行 docker ps 时,docker 是不知道的。为了可靠,工作进程需要在失败后重新启动,这带来了使用 supervisord 的建议。

  2. 当容器启动和停止(但未删除)时,docker 容器状态被保留。这意味着如果您的 celery 工作进程依赖于 pidfile 进行识别,但出现非正常关闭,则 pidfile 可能会被保留,并且即使使用 docker stop; docker start,工作进程也无法清洁地重启。这是由于 celery 启动检测到上一个不干净的关闭中剩余的 PIDfile 的存在。为了防止多个实例,重新启动的工作进程会用“PIDfile found,celery is already running?”自动停止。整个容器必须使用 docker rmdocker-compose down; docker-compose up 来删除。处理此问题的几种方法:

    a. 必须使用 --rm 标志运行容器,以便在容器停止后删除容器。

    b. 或许不包括 celery multicelery worker 命令中的 --pidfile 参数会更好。

总体建议:最好使用supervisord

现在,进入细节:

Docker容器需要运行前台任务,否则容器将退出。下面将进一步解决这个问题。

此外,celery工作者可能会运行长时间的任务,并需要响应docker的关闭(SIGTERM)信号以优雅地关闭即在关闭或重启之前完成长时间运行的任务。

为了实现docker信号传播和处理,最好在dockerfile中声明entrypoint,并使用docker的exec形式,也可以在docker-compose文件中进行此操作。

此外,由于celery multi在后台工作,docker看不到任何日志。您需要能够在前台显示日志,以便让docker logs能够查看发生了什么。我们将通过为celery多工作者设置logfile并在控制台前台显示tail -f <logfile_pattern>来无限期地运行来实现这一点。

我们需要实现三个目标:

  1. 使用前台任务运行docker容器
  2. 接收、trap和处理docker关闭信号
  3. 优雅地关闭工作者

对于 #1,我们将运行 tail -f &,然后将其作为前台任务 wait

对于 #2,这可以通过设置 trap 函数并捕获信号来实现。要使用 trap 函数接收和处理信号,必须在 #1 中实现正在运行的前台任务 wait

对于 #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工作者可以在Docker容器中运行,也可以优雅地停止。


基于您在 https://dev59.com/P6Dia4cB1Zd3GeqPH7bX#74629396 的回答,使用 supervisord 进行实现。 - Taras Mykhalchuk

3

首先,我不明白使用多个Docker容器的优势。

在我看来,您希望每个工作者都在单独的容器中。这样,您就可以拥有灵活性和微服务环境。

如果您仍然希望在同一个容器中拥有多个工作者,我可以建议一种解决方法,通过在命令的末尾添加while true; do sleep 2; done来保持容器的开启状态:

celery multi start 2 -l INFO -c:1 3 -c:2 7 -Q:1 queue1 -Q:2 queue2,queue3 && while true; do sleep 2; done

或者,将其包装在一个简短的脚本中:

#!/bin/bash
celery multi start 2 -l INFO -c:1 3 -c:2 7 -Q:1 queue1 -Q:2 queue2,queue3
while true; do sleep 2; done

我想启动celery并在一个容器中获取所有队列的日志。不同的队列,调度工作等。您的答案将有助于启动容器。它不会关闭。但是我没有得到日志。因此,我选择了supervisor。 - dluhhbiu
如果您使用docker-compose运行,您将在一个地方看到所有日志,这不够吗?当您扩展到多台机器时,监管者中也无法同时查看日志,对吧? - ItayB
5
芹菜多工只能在后台运行。while true; do sleep 2; done将使容器继续工作。但如果工作进程没有响应,容器不会停止和重启。在supervisord中重启工作进程并不是使用 Docker 的方式,但它可以正常工作。而且我还没有准备好使用多台机器。 - dluhhbiu
你可以在循环中检查Celery进程。 - gzerone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接