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

问题描述 投票:65回答:8

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

docker docker-registry amazon-ecs
8个回答
51
投票

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

更新:现在可以通过ECS代理上设置的ECS_IMAGE_PULL_BEHAVIOR环境变量调整此行为。有关详细信息,请参阅the documentation。截至撰写本文时,支持以下设置:

用于为容器实例自定义拉图像过程的行为。以下描述了可选行为:

  • 如果指定了default,则远程拉动图像。如果图像拉出失败,则容器使用实例上的缓存图像。
  • 如果指定了always,则始终远程拉动图像。如果图像拉出失败,则任务失败。此选项可确保始终提取最新版本的图像。任何缓存的图像都将被忽略,并受自动图像清理过程的影响。
  • 如果指定了once,则只有在相同容器实例上的先前任务未提取图像或者自动图像清理过程删除了缓存图像时,才会远程提取图像。否则,使用实例上的缓存图像。这确保不会尝试不必要的图像拉取。
  • 如果指定了prefer-cached,则在没有缓存图像的情况下远程拉取图像。否则,使用实例上的缓存图像。对容器禁用自动图像清理,以确保不删除缓存的图像。

50
投票

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

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

20
投票

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

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

This tutorial有更多细节,并描述了上述步骤如何适应端到端产品开发过程。

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


3
投票

我创建了a script,用于将更新的Docker映像部署到ECS上的登台服务,以便相应的任务定义引用Docker映像的当前版本。我不确定我是否遵循最佳做法,因此欢迎提供反馈。

要使脚本生效,您需要备用ECS实例或deploymentConfiguration.minimumHealthyPercent值,以便ECS可以窃取实例以将更新的任务定义部署到。

我的算法是这样的:

  1. 使用Git修订版标记与任务定义中的容器对应的Docker图像。
  2. 将Docker镜像标记推送到相应的注册表。
  3. 取消注册任务定义系列中的旧任务定义。
  4. 注册新的任务定义,现在引用标记有当前Git修订版的Docker镜像。
  5. 更新服务以使用新任务定义。

我的代码贴在下面:

部署-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)

_common.朋友

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

1
投票

使用AWS cli我尝试了如上所述的aws ecs update-service。没有从ECR拿起最新的码头工人。最后,我重新运行创建ECS群集的Ansible playbook。当ecs_taskdefinition运行时,任务定义的版本会受到影响。一切都很好。拾取新的泊坞窗图像。

实际上不确定任务版本更改是否强制重新部署,或者使用ecs_service的playbook是否导致任务重新加载。

如果有人有兴趣,我将获得发布我的剧本的消毒版本的许可。


1
投票

AWS CodePipeline。

您可以将ECR设置为源,将ECS设置为要部署的目标。


0
投票

以下命令对我有用

docker build -t <repo> . 
docker push <repo>
ecs-cli compose stop
ecs-cli compose start

0
投票

好吧,我也试图找到一种自动化的方法,即将更改推送到ECR,然后最新的标签应该由服务选择。您可以通过从群集中停止服务任务来手动执行此操作。新任务将拉出更新的ECR容器。

© www.soinside.com 2019 - 2024. All rights reserved.