更新 AWS ECS 服务任务的最佳实践

Posted

技术标签:

【中文标题】更新 AWS ECS 服务任务的最佳实践【英文标题】:Best Practice for Updating AWS ECS Service Tasks 【发布时间】:2018-02-11 14:51:51 【问题描述】:

我目前正在尝试设置一个简单的 CI,它将重建我的项目、创建新的 docker 映像、将新映像推送到 amazon ecr 存储库、使用最新的 docker 映像创建现有任务定义的新修订版,用任务定义的新版本更新正在运行的服务,最后停止运行旧版本的现有任务并启动运行新版本的任务。

除了开始任务的新版本之外,一切都运行良好。

在 bash 脚本中,我调用的最终命令是:

aws ecs update-service --cluster "$CLUSTER" --service "$SERVICE" --task-definition "$TASK_DEFINITION":"$REVISION"

这会导致以下事件错误:

(service rj-api-service) was unable to place a task because no container instance met all of its requirements. The closest matching (container-instance bbbc23d5-1a09-45e7-b344-e68cc408e683) is already using a port required by your task.

这是有道理的,因为我要替换的容器与新容器完全相同,并且将在同一个端口上运行,它只包含我的应用程序的最新版本。

我的印象是update-service 命令会停止现有任务并启动新任务,但看起来它首先启动新任务,如果成功则停止旧任务。

处理此问题的最佳做法是什么?我应该先停止旧任务吗?我是否应该先删除脚本中的服务并在每次更新时重新创建整个服务?

目前我只需要运行 1 个任务实例,但如果我需要它能够自动扩展到多个实例,我不想将自己装箱。对解决此问题的最佳方法有何建议?

【问题讨论】:

"如果未指定修订,则使用最新的 ACTIVE 修订。"根据Docs 【参考方案1】:

您收到的消息是因为 ECS 正在尝试进行蓝绿部署。这意味着它试图在不停止当前任务的情况下分配您的新任务修订,以避免您的服务停机。一旦最新的任务准备就绪(稳定状态),旧的任务将最终被删除。

这种部署的问题在于,您需要在集群中拥有足够的可用资源,以便在一段时间内维持和运行 2 个任务(旧的和新的)。例如,如果您正在部署一个具有 2GB 内存和 2 个 CPU 的任务,那么您的集群将需要拥有该数量的可用资源才能使用新的任务修订来更新服务。

你有两个选择:

    通过添加新的 EC2 实例来扩展您的集群,这样您就可以拥有足够的可用资源并执行部署。 更改您的服务配置,以免执行蓝绿部署(在您的集群中同时只允许 1 个任务)。

为了执行选项 2,您只需要设置以下值:

最低健康百分比:0 最大百分比:100

例子

这意味着您只想让所需任务的 100% 运行(仅此而已!),并且您愿意在部署新版本时停机(0% 的健康服务)。

在示例中,我假设您只需要 1 个所需任务,但 最小健康百分比最大百分比 值适用于任何数量的所需任务你想要的任务。

希望对您有所帮助!如果您还有其他疑问,请告诉我。

【讨论】:

这是一个完美的描述。太感谢了。我将健康百分比设置为默认值 50/200。我可以不进行蓝绿部署,因此将健康百分比设置为 0/100 可以按照我最初的预期工作,我所要做的就是更新,而不需要停止正在运行的任务的额外步骤。 因此,如果最小 0 和最大 100 并且我有 3 个容器正在运行,更新是否会进行滚动升级,或者它将杀死我的所有 3 个容器并启动一组 3 个。 它应该首先杀死正在运行的容器,然后启动最新的容器(执行更新)。 我有上述设置,最小健康百分比和最大百分比,但是,我仍然收到错误无法放置任务,因为没有容器实例满足其所有要求。关闭匹配 ( xxx) 已在使用您的任务所需的端口。 @fuzzi 嗨,我也遇到了这个问题,你能解决吗?【参考方案2】:

您可以在构建环境中使用 shell 脚本通过以下步骤启动新版本的任务。

    将构建环境中的tasks definition json template 存储在一个文件中(例如,模板文件为web-server.json,任务定义系列为web-server)。

    将文件目录作为当前目录,执行注册任务定义(不存在时首次运行)

    aws ecs register-task-definition --cli-input-json file://web-server.json

    将正在运行的任务 id(TASK_ID) 获取到 shell 脚本中的变量。

    TASK_ID=`aws ecs list-tasks --cluster default --desired-status RUNNING --family web-server | egrep "task" | tr "/" " " | tr "[" " " | awk 'print $2' | sed 's/"$//'`

    获取任务修订版(TASK_REVISION)到 shell 脚本中的变量。

    TASK_REVISION=`aws ecs describe-task-definition --task-definition web-server | egrep "revision" | tr "/" " " | awk 'print $2' | sed 's/"$//'`

    停止当前任务运行

    aws ecs stop-task --cluster default --task $TASK_ID

    立即开始新任务

    aws ecs update-service --cluster default --service web-server --task-definition web-server:$TASK_REVISION --desired-count 1

作为最佳实践,您可以为 2 个任务(在服务内运行的两个任务)保持期望计数的最小值,并使用以下脚本进行滚动更新(一次更新一个任务)(对多个容器的上述步骤的扩展) 停机时间为零(确保在第一次容器更新后保持足够的时间,例如睡眠 30 以便它准备好接受新请求)。

cd /<directory-containing-web-server.json>
aws ecs register-task-definition --cli-input-json file://web-server.json
OLD_TASK_ID=`aws ecs list-tasks --cluster default --desired-status RUNNING --family web-server | egrep "task" | tr "/" " " | tr "[" " " |  awk 'print $2' | sed 's/"$//'`

TASK_REVISION=`aws ecs describe-task-definition --task-definition web-server | egrep "revision" | tr "/" " " | awk 'print $2' | sed 's/"$//'`
aws ecs stop-task --cluster default --task $OLD_TASK_ID

OLD_TASK_ID=`aws ecs list-tasks --cluster default --desired-status RUNNING --family web-server | egrep "task" | tr "/" " " | tr "[" " " |  awk 'print $2' | sed 's/"$//'`
aws ecs update-service --cluster default --service web-server --task-definition web-server:$TASK_REVISION --desired-count 1

sleep 30
aws ecs stop-task --task $OLD_TASK_ID
aws ecs update-service --cluster default --service web-server --task-definition web-server:$TASK_REVISION --desired-count 2

注意:您需要相应地配置任务定义族、期望的实例数和任务定义模板。

【讨论】:

你可以使用 "jq" 来简化 OLD_TASK_ID=... OLD_TASK_ID=aws ecs list-tasks --cluster default --desired-status RUNNING --family web-server | jq --raw-output ".taskArns | .[]" 当然你必须安装 JQ 'apt-get install jq' @Ashan 如何从现有任务定义中获取 file://web-server.json ?【参考方案3】:

使用 -> AWS CLI

获取OLD_TASK_ID

aws ecs list-tasks --cluster $ecsClusterName --desired-status RUNNING --family $nameTaskDefinition | egrep "task/" | sed -E "s/.*task\/(.*)\"/\1/"

停止任务

aws ecs stop-task --cluster $ecsClusterName --task $OLD_TASK_ID

更新 ECS 服务

aws ecs update-service --cluster $ecsClusterName --service $nameService --task-definition $nameTaskDefinition:$version --desired-count 1 --force-new-deployment

【讨论】:

解析JSON时,jq规则:aws ecs list-tasks ... | jq ".taskArns[0]"【参考方案4】:

所以我现在确实有这个工作。

在使用新任务定义调用aws ecs update service 后,我调用aws ecs list-tasks,然后对服务的每个正在运行的任务运行“aws stop task”。因为服务的期望计数是 1,所以它会立即尝试启动任务备份并使用新的服务定义。

这不是很漂亮,但它现在似乎工作得很好。

【讨论】:

【参考方案5】:

要更新服务中运行的“任务”中的任务定义,您需要删除任务并启动新任务。

这样,我解决了更新tasks中task-definition的问题

我写了以下代码:

    # Register a new Task definition 
    aws ecs register-task-definition --family testing-cluster --cli-input-json file://scripts/taskdefinition/testingtaskdef.json --region $AWS_REGION

    # Update Service in the Cluster
    aws ecs update-service --cluster $CLUSTER_NAME --service $SERVICE --task-definition testing-cluster --desired-count 1 --region $AWS_REGION 



    DECRIBED_SERVICE=$(aws ecs describe-services --region $AWS_REGION --cluster $CLUSTER_NAME --services $SERVICE);
    CURRENT_DESIRED_COUNT=$(echo $DECRIBED_SERVICE | jq --raw-output ".services[0].desiredCount")
    #    - echo $CURRENT_DESIRED_COUNT

    CURRENT_TASK_REVISION=$(echo $DECRIBED_SERVICE | jq -r ".services[0].taskDefinition")
    echo "Current Task definition in Service" + $CURRENT_TASK_REVISION

    CURRENT_RUNNING_TASK=$(echo $DECRIBED_SERVICE | jq -r ".services[0].runningCount")
    echo $CURRENT_RUNNING_TASK

    CURRENT_STALE_TASK=$(echo $DECRIBED_SERVICE | jq -r ".services[0].deployments | .[] | select(.taskDefinition != \"$CURRENT_TASK_REVISION\") | .taskDefinition")
    echo "Task defn apart from current service Taskdefn" +  $CURRENT_STALE_TASK
    #   - echo $CURRENT_STALE_TASK

    tasks=$(aws ecs --region $AWS_REGION list-tasks --cluster $CLUSTER_NAME | jq -r '.taskArns | map(.[40:]) | reduce .[] as $item (""; . + $item + " ")')
    echo "Tasks are as follows" 
    echo $tasks
    TASKS=$(aws ecs --region $AWS_REGION describe-tasks --cluster $CLUSTER_NAME --task $tasks);
    #    - echo $TASKS
    OLDER_TASK=$(echo $TASKS | jq -r ".tasks[] | select(.taskDefinitionArn!= \"$CURRENT_TASK_REVISION\") | .taskArn | split(\"/\") | .[1] ")
    echo "Older Task running  " + $OLDER_TASK
    for old_task in $OLDER_TASK; do
        aws ecs --region us-east-1 stop-task --cluster $CLUSTER_NAME --task $old_task
    done    

    # Run new tasks with the updated new Task-definition
    aws ecs --region $AWS_REGION run-task --cluster $CLUSTER_NAME --task-definition $CURRENT_TASK_REVISION

【讨论】:

以上是关于更新 AWS ECS 服务任务的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

在 AWS ECS 上使用 docker-compose 进行持续部署的最佳实践

AWS ECS - 部署全栈项目的最佳方式(任务定义、集群和服务)

阿里云SLB最佳实践

模拟 AWS 服务和 Lambda 最佳实践

AWS 中的 cloudformation 最佳实践

AWS 架构最佳实践概述