我如何更新 docker-compose 服务中的 celery worker,但保持长时间的 运行 任务处于活动状态直到他们完成
How can I update a celery worker in a docker-compose service but keep long running task active until they finish
我有一个 Flask 应用程序,允许用户通过 celery 作业队列启动长 运行ning 任务(有时 > 1d)。 Flask 应用程序及其所有依赖项(包括 celery worker)都通过 docker 容器化,并以 docker-compose 文件开始。
我的问题是,当我使用新版本的应用程序软件更新容器映像时,我需要使用以下命令重新启动容器:
docker-compose down
docker-compose up -d
这将取消所有长 运行ning 作业,因为在 docker-compose 中默认只有一个很短的超时值。按照 docker-compose and graceful Celery shutdown 中的建议,通过 docker-compose 为正常停止设置更长的超时值对我不起作用,因为无法预测作业需要多长时间,更新可能需要很长时间直到所有任务完成。
我的想法是以某种方式从 docker-compose
控件中分离 运行ning 容器,然后在分离的容器内正常关闭 celery,然后允许作业完成,但是不接受新工作。然后我可以通过 docker-compose up -d
.
启动普通容器堆栈
因此我想这样做:
- remove/rename 来自 docker 的芹菜容器构成
- 通知容器中的 celery 任务正常停止并让作业完成但不接受新作业
- 然后启动将接受新作业的新容器
我尝试使用 docker rename
重命名由 docker-compose 启动的容器,但它们仍然对 docker-compose down
有反应。
我的问题是这种方法是否是处理此问题的正确方法,使用 docker-compose 是否可行?在 docker-compose 环境中处理具有长 运行ning 任务的 celery worker 的优雅更新的最佳实践是什么?
我发现的其他相关但不能完全解决问题的问题:
docker-compose and graceful Celery shutdown :答案显示了如何优雅地停止容器,但我想立即启动一个新的芹菜工人,以免停机。
How do I restart celery workers gracefully?:这适用于本地安装,但我必须重新启动容器才能获取新的应用程序代码。
编辑:解决方案的新提示:
本期我发现了类似的情况。这里 docker-compose --scale
用于复制服务,然后可以找到旧服务和新服务的 ID。一旦新服务启动,就应该能够告诉 celery 关闭并完成旧容器中的执行任务。如果这是解决方案,我稍后会添加它作为答案。
https://github.com/docker/compose/issues/1786#
编辑:更多地考虑带有缩放的变体。我又遇到了 运行ning 长任务的问题。在我可以缩减到 1 个实例之前,观察垂死的容器会很麻烦。在 link 中的示例中,唯一重要的是在停止旧服务之前检查新服务是否真的启动,以便脚本可以立即缩减为单个实例。我宁愿复制该服务,但从 docker-compose 的控制中删除新服务,这样当我缩减到 1 个容器时它就不会被杀死。这必须通过删除 运行ning 容器的 docker-compose 标签来实现:
"Labels": {
"com.docker.compose.config-hash": "44e0bbd2a10e28bcad071a42315e65ed4d89f2d815a08aed4f3133b05b9d9f71",
"com.docker.compose.container-number": "1",
"com.docker.compose.oneoff": "False",
"com.docker.compose.project": "karmada_docker_upgreat",
"com.docker.compose.project.config_files": "docker-compose_test.yml",
"com.docker.compose.project.working_dir": "/home/USERNAME/git/karmada_docker_upgreat",
"com.docker.compose.service": "karmada_celery_kalibrate_worker",
"com.docker.compose.version": "1.25.0"
}
还是走错路了?重命名服务对 docker-compose.
没有影响
** 编辑 ** 无法更改 运行ning 容器的标签:https://github.com/moby/moby/issues/15496
我想得越多,我想我将不得不使用正常的 docker 命令来 运行 芹菜容器。使用 docker 命令和 shell 脚本很容易实现我需要做的事情。我仍然希望在 docker-compose 中看到解决方案。
经过更多的研究,我找到了解决这个问题的方法。但我不得不放弃使用 docker-compose
的限制。
目前,我认为使用 docker-compose
无法完成我需要做的事情,因为一旦使用 docker-compose
启动的容器将始终由 docker-compose
命令控制,只要它在线。原因是无法在 运行ning 容器上更改标签,并且 docker-compose
通过标签找到它控制的容器(有关详细信息,请参见问题)。
所以尽管可以使用:
docker-compose up -d --no-deps --scale $SERVICE_NAME=2 --no-recreate $SERVICE_NAME
启动更新的容器并离开当前容器 运行ning,如这里所建议:
https://github.com/docker/compose/issues/1786#
在漫长的 运行ning 工作完成后,我无法缩减服务规模。因为作业可能 运行 很长(> 1 天),所以我可以完成多个容器。因此,我将不得不实施大量开销来计算当前正在完成的容器,并在其中一个完成时重新缩放回适当的数量。总是有意外的危险 docker-compose down
会把他们全部倒下。
但是 https://github.com/docker/compose/issues/1786# 末尾的 shell 脚本促使我放弃 docker-compose
约束并使用正常的 docker
命令控制所有芹菜容器。有了这个,很容易管理我想做的事情。我想出了以下 shell 脚本:
startup () {
SERVICE_NAME=${1?"Usage: docker_update <SERVICE_NAME> <COMMAND>"}
COMMAND=${2?"Usage: docker_update <SERVICE_NAME> <COMMAND>"}
docker run \
-d \
--name $SERVICE_NAME \
SOME_DOCKER_IMAGE \
$COMMAND
}
update () {
SERVICE_NAME=${1?"Usage: docker_update <SERVICE_NAME> <COMMAND>"}
COMMAND=${2?"Usage: docker_update <SERVICE_NAME> <COMMAND>"}
echo "[INFO] Updating docker service $SERVICE_NAME"
OLD_CONTAINER_ID=$(docker ps --format "table {{.ID}} {{.Names}} {{.CreatedAt}}" | grep $SERVICE_NAME | tail -n 1 | awk -F " " '{print }')
OLD_CONTAINER_NAME=$(docker ps --format "table {{.ID}} {{.Names}} {{.CreatedAt}}" | grep $SERVICE_NAME | tail -n 1 | awk -F " " '{print }')
TEMP_UUID=`uuidgen`
TEMP_CONTAINER_NAME="celery_worker_${TEMP_UUID}"
echo "[INFO] rename $OLD_CONTAINER_NAME to $TEMP_CONTAINER_NAME"
docker rename $OLD_CONTAINER_NAME $TEMP_CONTAINER_NAME
echo "[INFO] start new/updated celery queue"
startup $SERVICE_NAME $COMMAND
echo "[INFO] send SIGTERM to $TEMP_CONTAINER_NAME for warm shutdown"
docker kill --signal=SIGTERM $TEMP_CONTAINER_NAME
# Optional waiting for the container to finish
echo "[INIT] waiting for old docker container to finish"
docker wait $TEMP_CONTAINER_NAME
}
SERVICE_NAME=${1?"Usage: docker_update <SERVICE_NAME>"}
COMMAND=${2?"Usage: docker_update <SERVICE_NAME> <COMMAND>"}
echo "[INFO] checking if this service already runs"
docker ps --format "table {{.ID}} {{.Names}} {{.CreatedAt}}" | grep $SERVICE_NAME
if [ $? -eq 0 ]
then
echo "[INFO] CONTAINER with name $SERVICE_NAME is online -> update"
update $SERVICE_NAME $COMMAND
else
echo "[INFO] CONTAINER with name $SERVICE_NAME is **not** online -> starting"
startup $SERVICE_NAME $COMMAND
fi
脚本检查给定名称的服务是否为 运行ning。如果不是,则启动它。如果是 运行ning,它会重命名当前的 运行ning 容器,然后启动一个新的(可能已更新)容器,并向旧容器发送一个 SIGTERM。对于芹菜来说,这是执行 warm shutdown
的信号,这意味着它不再接受新任务,而是完成当前正在执行的任务,然后退出。如果没有任务 运行ning 它会立即退出。新的芹菜工人接管了所有新任务。
我有一个 Flask 应用程序,允许用户通过 celery 作业队列启动长 运行ning 任务(有时 > 1d)。 Flask 应用程序及其所有依赖项(包括 celery worker)都通过 docker 容器化,并以 docker-compose 文件开始。
我的问题是,当我使用新版本的应用程序软件更新容器映像时,我需要使用以下命令重新启动容器:
docker-compose down
docker-compose up -d
这将取消所有长 运行ning 作业,因为在 docker-compose 中默认只有一个很短的超时值。按照 docker-compose and graceful Celery shutdown 中的建议,通过 docker-compose 为正常停止设置更长的超时值对我不起作用,因为无法预测作业需要多长时间,更新可能需要很长时间直到所有任务完成。
我的想法是以某种方式从 docker-compose
控件中分离 运行ning 容器,然后在分离的容器内正常关闭 celery,然后允许作业完成,但是不接受新工作。然后我可以通过 docker-compose up -d
.
因此我想这样做:
- remove/rename 来自 docker 的芹菜容器构成
- 通知容器中的 celery 任务正常停止并让作业完成但不接受新作业
- 然后启动将接受新作业的新容器
我尝试使用 docker rename
重命名由 docker-compose 启动的容器,但它们仍然对 docker-compose down
有反应。
我的问题是这种方法是否是处理此问题的正确方法,使用 docker-compose 是否可行?在 docker-compose 环境中处理具有长 运行ning 任务的 celery worker 的优雅更新的最佳实践是什么?
我发现的其他相关但不能完全解决问题的问题:
docker-compose and graceful Celery shutdown :答案显示了如何优雅地停止容器,但我想立即启动一个新的芹菜工人,以免停机。
How do I restart celery workers gracefully?:这适用于本地安装,但我必须重新启动容器才能获取新的应用程序代码。
编辑:解决方案的新提示:
本期我发现了类似的情况。这里 docker-compose --scale
用于复制服务,然后可以找到旧服务和新服务的 ID。一旦新服务启动,就应该能够告诉 celery 关闭并完成旧容器中的执行任务。如果这是解决方案,我稍后会添加它作为答案。
https://github.com/docker/compose/issues/1786#
编辑:更多地考虑带有缩放的变体。我又遇到了 运行ning 长任务的问题。在我可以缩减到 1 个实例之前,观察垂死的容器会很麻烦。在 link 中的示例中,唯一重要的是在停止旧服务之前检查新服务是否真的启动,以便脚本可以立即缩减为单个实例。我宁愿复制该服务,但从 docker-compose 的控制中删除新服务,这样当我缩减到 1 个容器时它就不会被杀死。这必须通过删除 运行ning 容器的 docker-compose 标签来实现:
"Labels": {
"com.docker.compose.config-hash": "44e0bbd2a10e28bcad071a42315e65ed4d89f2d815a08aed4f3133b05b9d9f71",
"com.docker.compose.container-number": "1",
"com.docker.compose.oneoff": "False",
"com.docker.compose.project": "karmada_docker_upgreat",
"com.docker.compose.project.config_files": "docker-compose_test.yml",
"com.docker.compose.project.working_dir": "/home/USERNAME/git/karmada_docker_upgreat",
"com.docker.compose.service": "karmada_celery_kalibrate_worker",
"com.docker.compose.version": "1.25.0"
}
还是走错路了?重命名服务对 docker-compose.
没有影响** 编辑 ** 无法更改 运行ning 容器的标签:https://github.com/moby/moby/issues/15496 我想得越多,我想我将不得不使用正常的 docker 命令来 运行 芹菜容器。使用 docker 命令和 shell 脚本很容易实现我需要做的事情。我仍然希望在 docker-compose 中看到解决方案。
经过更多的研究,我找到了解决这个问题的方法。但我不得不放弃使用 docker-compose
的限制。
目前,我认为使用 docker-compose
无法完成我需要做的事情,因为一旦使用 docker-compose
启动的容器将始终由 docker-compose
命令控制,只要它在线。原因是无法在 运行ning 容器上更改标签,并且 docker-compose
通过标签找到它控制的容器(有关详细信息,请参见问题)。
所以尽管可以使用:
docker-compose up -d --no-deps --scale $SERVICE_NAME=2 --no-recreate $SERVICE_NAME
启动更新的容器并离开当前容器 运行ning,如这里所建议:
https://github.com/docker/compose/issues/1786#
在漫长的 运行ning 工作完成后,我无法缩减服务规模。因为作业可能 运行 很长(> 1 天),所以我可以完成多个容器。因此,我将不得不实施大量开销来计算当前正在完成的容器,并在其中一个完成时重新缩放回适当的数量。总是有意外的危险 docker-compose down
会把他们全部倒下。
但是 https://github.com/docker/compose/issues/1786# 末尾的 shell 脚本促使我放弃 docker-compose
约束并使用正常的 docker
命令控制所有芹菜容器。有了这个,很容易管理我想做的事情。我想出了以下 shell 脚本:
startup () {
SERVICE_NAME=${1?"Usage: docker_update <SERVICE_NAME> <COMMAND>"}
COMMAND=${2?"Usage: docker_update <SERVICE_NAME> <COMMAND>"}
docker run \
-d \
--name $SERVICE_NAME \
SOME_DOCKER_IMAGE \
$COMMAND
}
update () {
SERVICE_NAME=${1?"Usage: docker_update <SERVICE_NAME> <COMMAND>"}
COMMAND=${2?"Usage: docker_update <SERVICE_NAME> <COMMAND>"}
echo "[INFO] Updating docker service $SERVICE_NAME"
OLD_CONTAINER_ID=$(docker ps --format "table {{.ID}} {{.Names}} {{.CreatedAt}}" | grep $SERVICE_NAME | tail -n 1 | awk -F " " '{print }')
OLD_CONTAINER_NAME=$(docker ps --format "table {{.ID}} {{.Names}} {{.CreatedAt}}" | grep $SERVICE_NAME | tail -n 1 | awk -F " " '{print }')
TEMP_UUID=`uuidgen`
TEMP_CONTAINER_NAME="celery_worker_${TEMP_UUID}"
echo "[INFO] rename $OLD_CONTAINER_NAME to $TEMP_CONTAINER_NAME"
docker rename $OLD_CONTAINER_NAME $TEMP_CONTAINER_NAME
echo "[INFO] start new/updated celery queue"
startup $SERVICE_NAME $COMMAND
echo "[INFO] send SIGTERM to $TEMP_CONTAINER_NAME for warm shutdown"
docker kill --signal=SIGTERM $TEMP_CONTAINER_NAME
# Optional waiting for the container to finish
echo "[INIT] waiting for old docker container to finish"
docker wait $TEMP_CONTAINER_NAME
}
SERVICE_NAME=${1?"Usage: docker_update <SERVICE_NAME>"}
COMMAND=${2?"Usage: docker_update <SERVICE_NAME> <COMMAND>"}
echo "[INFO] checking if this service already runs"
docker ps --format "table {{.ID}} {{.Names}} {{.CreatedAt}}" | grep $SERVICE_NAME
if [ $? -eq 0 ]
then
echo "[INFO] CONTAINER with name $SERVICE_NAME is online -> update"
update $SERVICE_NAME $COMMAND
else
echo "[INFO] CONTAINER with name $SERVICE_NAME is **not** online -> starting"
startup $SERVICE_NAME $COMMAND
fi
脚本检查给定名称的服务是否为 运行ning。如果不是,则启动它。如果是 运行ning,它会重命名当前的 运行ning 容器,然后启动一个新的(可能已更新)容器,并向旧容器发送一个 SIGTERM。对于芹菜来说,这是执行 warm shutdown
的信号,这意味着它不再接受新任务,而是完成当前正在执行的任务,然后退出。如果没有任务 运行ning 它会立即退出。新的芹菜工人接管了所有新任务。