如何将更新的Docker镜像部署到Amazon ECS任务?

akn*_*ds1 78 amazon-ecs docker docker-registry

一旦所述图像在相应的注册表中更新后,使我的Amazon ECS任务更新其Docker镜像的正确方法是什么?

Dim*_*ima 59

如果您的任务在服务下运行,则可以强制执行新部署.这会强制重新评估任务定义并拉出新的容器图像.

aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
Run Code Online (Sandbox Code Playgroud)

  • 我认为要实现此目的,您需要确保 ECS 实例上有足够的资源来部署相同大小的附加任务。我假设 AWS 尝试本质上执行热交换,等待新任务实例预启动,然后终止旧任务实例。如果您不这样做,它只会不断添加具有 0 个正在运行的实例的“部署”条目。 (7认同)
  • @AlexFedulov,是的,我认为你是对的.为了在创建新部署时不会导致停机,您可以1)提供足够的实例来部署新版本以及旧版本.这可以通过自动缩放来实现.2)使用Fargate部署类型.您可以通过将服务的"最低健康百分比"参数设置为0来避免分配额外资源,以允许ECS在部署新服务之前删除旧服务.但这会导致一些停机时间. (3认同)
  • 未知选项: - force-new-deployment (3认同)
  • 您可能还需要添加 `--region &lt;region&gt;` 标志 (3认同)
  • 我尝试了这个命令,它不会使用新映像更新容器,而是使用相同的旧映像启动另一个容器。所以我最终有两个容器在运行,即使在服务中我指定了所需的计数=1 (2认同)
  • 当我有“AWS::ECS::Service”和“DeploymentConfiguration:MinimumHealthyPercent:50 MaximumPercent:200”时,对我有用 (2认同)

Sam*_*arp 56

每次启动任务(通过StartTask和RunTaskAPI调用或自动启动作为服务的一部分),在ECS代理将执行docker pull的image你设置的任务定义.如果每次推送到注册表时都使用相同的映像名称(包括标记),则应该能够通过运行新任务来运行新映像.请注意,如果Docker因任何原因(例如,网络问题或身份验证问题)无法访问注册表,则ECS代理将尝试使用缓存的映像; 如果要在更新映像时避免使用缓存图像,则每次都要将不同的标记推送到注册表,并在运行新任务之前相应地更新任务定义.

  • 你确定吗?我已经看到过,即使在我将新图像推送到Dockerhub(使用相同的标记名称)之后,旧的docker图像也会运行.我想也许我应该在每次构建新图像时碰到标记名称.但是,根据我的经验,这种情况非常罕见,因此可能只是暂时的网络问题.(我知道你在ECS上工作,所以你是回答这个问题的最佳人选,但这并不是我所经历过的.请道歉,如果这是粗鲁的,而不是我的意图!) (4认同)
  • 我同意@Ibrahim,在许多情况下,当从 Lambda 调用 run_task() 时,新图像(即使正确加载到 ECR 中)将不会被拉取和使用。CloudWatch 日志未显示任何错误;它只是坚持使用旧图像。确实非常令人沮丧! (3认同)
  • 这是我很难学到的东西。 (2认同)
  • 是的,当前的行为是它每次都会尝试拉动。如果拉取失败(网络问题、缺乏权限等),它将尝试使用缓存的图像。您可以在代理日志文件中找到更多详细信息,这些文件通常位于“/var/log/ecs”中。 (2认同)
  • 值得注意的是,Fargate 根本不缓存图像,并且始终从注册表中提取图像,因此不需要配置:[AWS 文档](https://docs.aws.amazon.com/AmazonECS/latest/bestpracticesguide/pull -behavior.html) (2认同)

Nea*_*eal 21

注册新任务定义并更新服务以使用新任务定义是AWS推荐的方法.最简单的方法是:

  1. 导航到任务定义
  2. 选择正确的任务
  3. 选择创建新修订
  4. 如果您已经使用类似:latest标签的方式提取最新版本的容器图像,则只需单击"创建".否则,请更新容器映像的版本号,然后单击"创建".
  5. 展开操作
  6. 选择更新服务(两次)
  7. 然后等待服务重新启动

本教程提供了更多详细信息,并描述了上述步骤如何适用于端到端产品开发过程.

完全披露:本教程以Bitnami的容器为特色,我为Bitnami工作.然而,这里表达的想法是我自己的,而不是Bitnami的意见.

  • 这可行,但您可能必须更改服务最小/最大值.如果您只有一个EC2实例,则必须将最小健康百分比设置为零,否则它将永远不会终止任务(使您的服务暂时脱机)以部署更新的容器. (3认同)
  • @Malvineous好点!在[教程的ECS设置部分](https://docs.bitnami.com/aws/how-to/ecs-rds-tutorial/#step-23-set-up-amazon-ecs)中,我完全描述那.以下是该部分的推荐配置:任务数量 - 1,最低健康百分比 - 0,最大百分比 - 200. (3认同)

Z.W*_*Wei 12

有两种方法可以做到这一点。

首先,使用 AWS CodeDeploy。您可以在 ECS 服务定义中配置蓝/绿部署部分。这包括一个 CodeDeployRoleForECS、另一个用于交换机的 TargetGroup 和一个测试侦听器(可选)。AWS ECS 将创建 CodeDeploy 应用程序和部署组,并将这些 CodeDeploy 资源与您的 ECS 集群/服务和您的 ELB/目标组链接起来。然后就可以使用 CodeDeploy 来发起部署,在部署中需要输入一个 AppSpec,指定使用什么任务/容器来更新什么服务。您可以在此处指定新任务/容器。然后,您会看到新的实例在新的 TargetGroup 中启动,旧的 TargetGroup 与 ELB 断开连接,很快注册到旧 TargetGroup 的旧实例将被终止。

这听起来非常复杂。实际上,由于/如果您在 ECS 服务上启用了自动缩放,那么一个简单的方法是使用控制台或 cli 强制进行新部署,就像这里的一位绅士指出的那样:

aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment

通过这种方式,您仍然可以使用“滚动更新”部署类型,如果一切正常,ECS 将简单地启动新实例并耗尽旧实例,而不会导致您的服务停机。不好的一面是你失去了对部署的良好控制,如果出现错误,你无法回滚到以前的版本,这会破坏正在进行的服务。但这是一个非常简单的方法。

顺便说一句,不要忘记为最小健康百分比和最大百分比设置适当的数字,例如 100 和 200。


小智 7

遇到同样的问题。花费数小时后,完成了这些用于自动部署更新映像的简化步骤:

1.ECS 任务定义更改:为了更好地理解,假设您已经创建了一个包含以下详细信息的任务定义(注意:这些数字会根据您的任务定义相应地更改):

launch_type = EC2

desired_count = 1
Run Code Online (Sandbox Code Playgroud)

然后您需要进行以下更改:

deployment_minimum_healthy_percent = 0  //this does the trick, if not set to zero the force deployment wont happen as ECS won't allow to stop the current running task

deployment_maximum_percent = 200  //for allowing rolling update
Run Code Online (Sandbox Code Playgroud)

2.将您的图像标记为 < your-image-name>:latest。最新的密钥负责被相应的 ECS 任务拉取。

sudo docker build -t imageX:master .   //build your image with some tag
sudo -s eval $(aws ecr get-login --no-include-email --region us-east-1)  //login to ECR
sudo docker tag imageX:master <your_account_id>.dkr.ecr.us-east-1.amazonaws.com/<your-image-name>:latest    //tag your image with latest tag
Run Code Online (Sandbox Code Playgroud)

3.Push到图片到ECR

sudo docker push  <your_account_id>.dkr.ecr.us-east-1.amazonaws.com/<your-image-name>:latest
Run Code Online (Sandbox Code Playgroud)

4.apply force-部署

sudo aws ecs update-service --cluster <your-cluster-name> --service <your-service-name> --force-new-deployment --region us-east-1
Run Code Online (Sandbox Code Playgroud)

注意:我已经编写了所有命令,假设区域是us-east-1。只需在实施时将其替换为您各自的区域即可。


akn*_*ds1 6

I created a script for deploying updated Docker images to a staging service on ECS, so that the corresponding task definition refers to the current versions of the Docker images. I don't know for sure if I'm following best practices, so feedback would be welcome.

For the script to work, you need either a spare ECS instance or a deploymentConfiguration.minimumHealthyPercent value so that ECS can steal an instance to deploy the updated task definition to.

My algorithm is like this:

  1. Tag Docker images corresponding to containers in the task definition with the Git revision.
  2. Push the Docker image tags to the corresponding registries.
  3. Deregister old task definitions in the task definition family.
  4. Register new task definition, now referring to Docker images tagged with current Git revisions.
  5. Update service to use new task definition.

我的代码粘贴在下面:

部署-ecs

#!/usr/bin/env python3
import subprocess
import sys
import os.path
import json
import re
import argparse
import tempfile

_root_dir = os.path.abspath(os.path.normpath(os.path.dirname(__file__)))
sys.path.insert(0, _root_dir)
from _common import *


def _run_ecs_command(args):
    run_command(['aws', 'ecs', ] + args)


def _get_ecs_output(args):
    return json.loads(run_command(['aws', 'ecs', ] + args, return_stdout=True))


def _tag_image(tag, qualified_image_name, purge):
    log_info('Tagging image \'{}\' as \'{}\'...'.format(
        qualified_image_name, tag))
    log_info('Pulling image from registry in order to tag...')
    run_command(
        ['docker', 'pull', qualified_image_name], capture_stdout=False)
    run_command(['docker', 'tag', '-f', qualified_image_name, '{}:{}'.format(
        qualified_image_name, tag), ])
    log_info('Pushing image tag to registry...')
    run_command(['docker', 'push', '{}:{}'.format(
        qualified_image_name, tag), ], capture_stdout=False)
    if purge:
        log_info('Deleting pulled image...')
        run_command(
            ['docker', 'rmi', '{}:latest'.format(qualified_image_name), ])
        run_command(
            ['docker', 'rmi', '{}:{}'.format(qualified_image_name, tag), ])


def _register_task_definition(task_definition_fpath, purge):
    with open(task_definition_fpath, 'rt') as f:
        task_definition = json.loads(f.read())

    task_family = task_definition['family']

    tag = run_command([
        'git', 'rev-parse', '--short', 'HEAD', ], return_stdout=True).strip()
    for container_def in task_definition['containerDefinitions']:
        image_name = container_def['image']
        _tag_image(tag, image_name, purge)
        container_def['image'] = '{}:{}'.format(image_name, tag)

    log_info('Finding existing task definitions of family \'{}\'...'.format(
        task_family
    ))
    existing_task_definitions = _get_ecs_output(['list-task-definitions', ])[
        'taskDefinitionArns']
    for existing_task_definition in [
        td for td in existing_task_definitions if re.match(
            r'arn:aws:ecs+:[^:]+:[^:]+:task-definition/{}:\d+'.format(
                task_family),
            td)]:
        log_info('Deregistering task definition \'{}\'...'.format(
            existing_task_definition))
        _run_ecs_command([
            'deregister-task-definition', '--task-definition',
            existing_task_definition, ])

    with tempfile.NamedTemporaryFile(mode='wt', suffix='.json') as f:
        task_def_str = json.dumps(task_definition)
        f.write(task_def_str)
        f.flush()
        log_info('Registering task definition...')
        result = _get_ecs_output([
            'register-task-definition',
            '--cli-input-json', 'file://{}'.format(f.name),
        ])

    return '{}:{}'.format(task_family, result['taskDefinition']['revision'])


def _update_service(service_fpath, task_def_name):
    with open(service_fpath, 'rt') as f:
        service_config = json.loads(f.read())
    services = _get_ecs_output(['list-services', ])[
        'serviceArns']
    for service in [s for s in services if re.match(
        r'arn:aws:ecs:[^:]+:[^:]+:service/{}'.format(
            service_config['serviceName']),
        s
    )]:
        log_info('Updating service with new task definition...')
        _run_ecs_command([
            'update-service', '--service', service,
            '--task-definition', task_def_name,
        ])


parser = argparse.ArgumentParser(
    description="""Deploy latest Docker image to staging server.
The task definition file is used as the task definition, whereas
the service file is used to configure the service.
""")
parser.add_argument(
    'task_definition_file', help='Your task definition JSON file')
parser.add_argument('service_file', help='Your service JSON file')
parser.add_argument(
    '--purge_image', action='store_true', default=False,
    help='Purge Docker image after tagging?')
args = parser.parse_args()

task_definition_file = os.path.abspath(args.task_definition_file)
service_file = os.path.abspath(args.service_file)

os.chdir(_root_dir)

task_def_name = _register_task_definition(
    task_definition_file, args.purge_image)
_update_service(service_file, task_def_name)
Run Code Online (Sandbox Code Playgroud)

_common.py

import sys
import subprocess


__all__ = ['log_info', 'handle_error', 'run_command', ]


def log_info(msg):
    sys.stdout.write('* {}\n'.format(msg))
    sys.stdout.flush()


def handle_error(msg):
    sys.stderr.write('* {}\n'.format(msg))
    sys.exit(1)


def run_command(
        command, ignore_error=False, return_stdout=False, capture_stdout=True):
    if not isinstance(command, (list, tuple)):
        command = [command, ]
    command_str = ' '.join(command)
    log_info('Running command {}'.format(command_str))
    try:
        if capture_stdout:
            stdout = subprocess.check_output(command)
        else:
            subprocess.check_call(command)
            stdout = None
    except subprocess.CalledProcessError as err:
        if not ignore_error:
            handle_error('Command failed: {}'.format(err))
    else:
        return stdout.decode() if return_stdout else None
Run Code Online (Sandbox Code Playgroud)

  • 这是矫枉过正。应该可以通过 terraform 或仅单个 ecs-cli 行进行部署。 (7认同)

Sai*_*r S 5

如果 docker 图像标签相同,以下对我有用:

  1. 转到集群和​​服务。
  2. 选择服务并单击更新。
  3. 将任务数设置为 0 并更新。
  4. 部署完成后,将任务数重新调整为 1。

以下 api 也有效:

aws ecs update-service --cluster <cluster_name> --service <service_name> --force-new-deployment
Run Code Online (Sandbox Code Playgroud)