docker - 如何在docker-compose服务中更新celery worker ,但保持长时间运行的任务处于事件状态,直到完成

标签 docker deployment docker-compose celery

我有一个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控件分离,然后在分离的容器内正常关闭celery,这将允许作业完成,但不接受新作业。然后,我可以通过docker-compose up -d启动普通容器堆栈。

因此,我想这样做:
  • 从docker删除/重命名celery容器compose
  • 指示容器中的celery任务正常停止并让作业完成但不接受新作业
  • 然后启动将接受新作业的新容器

  • 我尝试使用docker rename重命名由docker-compose启动的容器,但它们仍然对docker-compose down使用react。

    我的问题是这种方法是否是处理此问题的正确方法,甚至可以通过docker-compose实现?在docker-compose环境中处理长时间运行的 celery worker 的优雅更新的最佳实践是什么?

    我发现的其他相关问题,但不能完全解决问题:

    docker-compose and graceful Celery shutdown:答案显示了如何优雅地停止容器,但我想立即启动新的 celery worker 以减少停机时间。

    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中看到一个解决方案。

    最佳答案

    经过更多研究,我找到了解决该问题的方法。但是我不得不放弃使用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命令控制了所有 celery 容器。有了这个,很容易管理我想做的事情。我想出了以下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发送到旧的容器。对于 celery ,这是执行warm shutdown的信号,这意味着它不再接受新任务,而是完成当前正在执行的任务,然后退出。如果没有任务在运行,它将立即退出。新 celery worker 接管所有新任务。

    关于docker - 如何在docker-compose服务中更新celery worker ,但保持长时间运行的任务处于事件状态,直到完成,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61338556/

    相关文章:

    vagrant - Docker 和 shell 配置程序的奇怪 Vagrant 错误

    bash - & 与 docker exec 一起使用时返回不正确的 pid

    docker - 为什么我的 docker 卷用完了磁盘空间?

    google-app-engine - 部署 App Engine 应用程序时出现错误消息 "service cloudbuilt.googleapis.com is not for consumer..."

    perl - Docker [for mac] 文件系统变为只读,这几乎破坏了 docker 的所有功能

    Go 应用程序(在 Docker 容器中)没有反射(reflect)页面上的更改?

    amazon-ec2 - 如何从私有(private) Dockerhub 存储库自动化 docker 运行?

    apache - 启动部署架构-在没有Apache的情况下运行Glassfish V3前奏

    java - 在 Websphere 中部署 war 有没有更好的方法?

    php - PHP-FPM不会从Dockerfile开始