我有一个flask应用程序,该应用程序允许用户通过celery作业队列启动长时间运行的任务(有时> 1d)。 Flask应用程序及其所有依赖项(包括celery worker)都通过docker进行了容器化,并以docker-compose文件开头。
我的问题是,当我使用新版本的应用程序软件更新容器映像时,需要使用以下命令重新启动容器:
docker-compose down
docker-compose up -d
这将取消所有长时间运行的作业,因为docker-compose中每个默认值只有一个短超时值。按照docker-compose and graceful Celery shutdown中的建议,通过docker-compose设置较长的超时值以进行平稳停止对我不起作用,因为无法预测作业将花费多长时间,并且更新可能需要很长时间才能完成所有任务完成。
我的想法是以某种方式将运行中的容器与docker-compose
控件分离,然后在分离的容器内正常关闭芹菜,然后允许作业完成,但不接受新作业。然后,我可以通过docker-compose up -d
启动普通容器堆栈。
我想这样做:
我尝试使用docker rename
重命名由docker-compose启动的容器,但它们仍然对docker-compose down
做出反应。
我的问题是这种方法是否是处理此问题的正确方法,甚至可以通过docker-compose实现?在docker-compose环境中处理长时间运行的芹菜工人的优雅更新的最佳实践是什么?
我发现的其他相关问题,但不能完全解决问题:
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#
编辑:更多地考虑具有缩放比例的变体。在这里,我又遇到了长时间运行的任务的问题。观察即将死去的容器将很麻烦,直到我可以还原到1个实例为止。在链接的示例中,仅重要的是在停止旧服务之前检查新服务是否确实已启动,以便脚本可以立即扩展到单个实例。我宁愿复制该服务,但从docker-compose的控制中删除新服务,以便在我缩放回1个容器时不会被杀死。这必须通过删除正在运行的容器的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无关。
**编辑**无法更改正在运行的容器的标签:https://github.com/moby/moby/issues/15496 我考虑得越多,我将不得不使用普通的docker命令来运行celery容器。使用docker命令和shell脚本,很容易实现我需要做的事情。我仍然希望在docker-compose中看到一个解决方案。
答案 0 :(得分:1)
经过更多研究,我找到了解决此问题的方法。但是我不得不放弃使用docker-compose
的约束。
当前,我认为我无法用docker-compose
做任何事情,因为一旦容器以在线docker-compose
开始,它将始终受docker-compose
命令的控制。原因是无法在运行的容器上更改标签,docker-compose
通过标签找到了它控制的容器(有关详细信息,请参阅问题)。
所以尽管可以使用:
docker-compose up -d --no-deps --scale $SERVICE_NAME=2 --no-recreate $SERVICE_NAME
要启动更新的容器,请保留当前容器的运行状态,如此处建议:
https://github.com/docker/compose/issues/1786#
长期运行的工作结束后,我无法缩减服务规模。因为作业可能会运行很长时间(> 1d),所以我可能要完成多个容器。因此,我将不得不实施大量的开销来计算当前正在完成的容器,并在完成其中一个容器后将其缩放到适当的数量。总是有偶然的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 $1}')
OLD_CONTAINER_NAME=$(docker ps --format "table {{.ID}} {{.Names}} {{.CreatedAt}}" | grep $SERVICE_NAME | tail -n 1 | awk -F " " '{print $2}')
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
该脚本检查具有给定名称的服务是否正在运行。如果不是,它将启动它。如果它正在运行,它将重命名当前正在运行的容器,然后启动一个新的(可能已更新)容器,然后将SIGTERM发送到旧的容器。对于芹菜,这是执行warm shutdown
的信号,这意味着它不再接受新任务,而是完成当前正在执行的任务,然后退出。如果没有任务在运行,它将立即退出。新芹菜工人接管所有新任务。