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

50

我目前正在试图设置一个简单的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个任务实例,但如果我需要自动扩展到多个实例,我不想让自己陷入困境。 有关如何解决此问题的最佳建议吗?


如果未指定修订版本,则使用最新的活动修订版本。根据文档 - Taku
5个回答

70
你收到的信息是因为ECS正在进行蓝绿部署。这意味着它正在尝试分配新的任务版本而不停止当前任务,以避免服务中断。一旦最新的任务准备就绪(稳定状态),旧任务将被删除。
这种类型的部署问题在于,你需要在集群中拥有足够的空闲资源来维持运行两个任务(旧的和新的)一段时间。例如,如果你正在部署一个具有2GB内存和2个CPU的任务,则你的集群需要具有相应数量的空闲资源才能使用新任务版本更新服务。
你有两个选择:
1. 通过添加新的EC2实例来扩展集群,以便你可以拥有足够的空闲资源并执行部署。 2. 更改服务配置,以便不执行蓝绿部署(在集群中仅允许同时运行一个任务)。
要执行第二个选项,你只需要设置以下值:
- 最小健康百分比:0 - 最大百分比:100
示例

Example

这意味着您只希望运行100%所需的任务(没有更多!),并且您愿意在部署新版本时出现停机时间(0%的健康服务)。
在这个例子中,我假设您只想要1个所需的任务,但是“最小健康百分比”和“最大百分比”值适用于任何所需的任务数量。
希望能有所帮助!如果您还有其他疑问,请告诉我。

6
这是一个完美的描述。非常感谢。我将健康百分比设置为默认值50/200。我不打算进行蓝绿部署,因此将健康百分比设置为0/100可以按照我最初的预期工作,而且我只需要调用更新而无需停止正在运行的任务的额外步骤。 - on3al
2
如果最小值是0,最大值是100,并且我有3个容器正在运行,那么更新会进行滚动升级还是会杀死我的所有3个容器并启动新的3个容器集合? - lakshayk
2
首先应该停止你正在运行的容器,然后再启动最新的容器(执行更新)。 - Fabian Rivera
我已经按照最小健康百分比和最大百分比进行了设置,但是仍然出现错误:“由于没有容器实例满足其所有要求,因此无法放置任务。”与您的任务所需端口相匹配的最接近的(xxx)已被占用。 - fuzzi
这个错误确实与资源耗尽有关,但是在当前版本的ECS和可用工具中,您根本不需要扩展集群节点。如果您的任务定义同时设置了容器端口和主机端口,则不会为容器动态分配端口,如果您的集群只有一个节点,则会看到此错误,因为只有一个东西可以使用相同的端口。但将主机端口设置为0,它将是动态的,并且您可以在单节点集群中生成多个不同版本而不会出现端口冲突,前提是节点上有足够的可用资源供两者使用。 - SandWyrm
显示剩余3条评论

8
你可以使用构建环境中的shell脚本按照以下步骤开始任务的新版本。
  1. 在您的构建环境中将任务定义json模板存储在一个文件中(例如,模板文件为web-server.json,任务定义族为web-server)。

  2. 使用文件目录作为当前目录,并执行注册任务定义(如果不存在,则第一次运行时发生)

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

  3. 将正在运行的任务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/"$//'`

  4. 将任务修订版(TASK_REVISION)保存到shell脚本变量中。

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

  5. 停止当前正在运行的任务

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

  6. 立即启动新任务

    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

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

2
你可以使用"jq"来简化这行代码: ... OLD_TASK_ID=aws ecs list-tasks --cluster default --desired-status RUNNING --family web-server | jq --raw-output ".taskArns | .[]" 当然,你需要安装JQ:"apt-get install jq" - AlexS
@Ashan 如何从现有任务定义中获取 file://web-server.json 文件? - vickey99

4

使用 -> 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

1
在解析JSON时,jq规则为:aws ecs list-tasks ... | jq ".taskArns[0]" - johndodo

1

现在我已经把它搞定了。

在使用新的任务定义调用aws ecs update service之后,我调用aws ecs list-tasks,然后对该服务中每个正在运行的任务运行'aws stop task`。因为服务的所需数量为1,它立即尝试重新启动任务并使用新的服务定义。

虽然不太美观,但目前似乎足够好用。


1

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

通过这种方式,我解决了在任务中更新任务定义的问题

我编写了以下代码:

    # 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

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