Как развернуть обновленные образы Docker в задачах Amazon ECS?


110

Как правильно заставить мои задачи Amazon ECS обновлять свои образы Docker после обновления этих образов в соответствующем реестре?


Я бы порекомендовал запустить автоматическую / запланированную функцию лямбда. Таким образом, это вне экземпляра. Вы пробовали это? Вы также можете использовать SWF для выполнения действий за раз
iSkore

Мне не нужно автоматизировать @iSkore. В конце концов, я хотел бы написать для него сценарий, но выбираю сам, когда его запускать.
aknuds1

Ага, попался. Не был в этом уверен. Не могли бы вы предоставить немного дополнительной информации?
iSkore

1
@iSkore Я не знаю, как описать это лучше, чем я уже описал. Процедура: 1. Загрузите новую версию образа Docker в реестр. 2. Разверните новую версию образа в ECS. Вопрос в том, как реализовать последнее.
aknuds1

это тоже непросто или очевидно с EKS .. как F является наиболее распространенной задачей использования кластера, развертывания нового образа, столь непонятного в документации?

Ответы:


89

Если ваша задача выполняется в рамках службы, вы можете принудительно выполнить новое развертывание. Это приводит к повторной оценке определения задачи и извлечению нового образа контейнера.

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

1
Я думаю, чтобы это сработало, вам нужно убедиться, что на ваших экземплярах ECS достаточно ресурсов для развертывания дополнительной задачи того же размера. Я предполагаю, что AWS пытается, по сути, выполнить горячую замену, ожидая предварительной загрузки нового экземпляра задачи, прежде чем завершить работу старого. Он просто продолжает добавлять записи «развертывания» с 0 запущенными экземплярами, если вы этого не сделаете.
Alex Fedulov

3
@AlexFedulov, ага, я думаю, вы правы. Чтобы избежать простоев при создании нового развертывания, вы можете: 1) Предоставить достаточно экземпляров для развертывания новой версии вместе со старой. Этого можно добиться с помощью автомасштабирования. 2) Используйте тип развертывания Fargate. Вы можете избежать выделения дополнительных ресурсов, установив для параметра службы «минимальный процент работоспособности» значение 0, чтобы позволить ECS удалить вашу старую службу перед развертыванием новой. Однако это вызовет некоторое время простоя.
Дима

3
Неизвестные параметры: --force-new-deployment
user4674453

1
Неизвестные параметры: --force-new-deployment: upgrade awscli
Kyle Parisi

1
Я пробовал эту команду, она не обновляет контейнер с новым изображением, она запускает другой контейнер с тем же старым изображением. Итак, у меня работают два контейнера, хотя в сервисе я указал желаемое количество = 1
математика

61

Каждый раз, когда вы запускаете задачу (либо через вызовы API StartTaskи, RunTaskлибо запускается автоматически как часть службы), агент ECS будет выполнять одну docker pullиз imageзадач, указанных в определении задачи. Если вы используете одно и то же имя образа (включая тег) каждый раз, когда отправляете в реестр, вы сможете запустить новый образ, запустив новую задачу. Обратите внимание: если Docker не может получить доступ к реестру по какой-либо причине (например, проблемы с сетью или проблемы с аутентификацией), агент ECS попытается использовать кэшированный образ; Если вы хотите избежать использования кэшированных изображений при обновлении образа, вам нужно каждый раз вставлять в реестр другой тег и соответственно обновлять определение задачи перед запуском новой задачи.

Обновление: теперь это поведение можно настроить с помощью ECS_IMAGE_PULL_BEHAVIORпеременной среды, заданной в агенте ECS. Подробности смотрите в документации . На момент написания поддерживаются следующие настройки:

Поведение, используемое для настройки процесса извлечения изображения для ваших экземпляров контейнера. Ниже описаны дополнительные варианты поведения:

  • Если defaultуказано, изображение извлекается удаленно. Если получение изображения не удается, то контейнер использует кэшированное изображение в экземпляре.

  • Если alwaysуказано, изображение всегда извлекается удаленно. Если получение изображения не удается, задача не выполняется. Этот параметр гарантирует, что всегда будет загружаться последняя версия образа. Любые кэшированные изображения игнорируются и подлежат автоматической очистке.

  • Если onceуказано, изображение извлекается удаленно, только если оно не было извлечено предыдущей задачей в том же экземпляре контейнера или если кэшированное изображение было удалено в процессе автоматической очистки образа. В противном случае используется кэшированное изображение в экземпляре. Это гарантирует, что не будут предприниматься попытки ненужного извлечения изображения.

  • Если prefer-cachedуказано, изображение извлекается удаленно, если кэшированного изображения нет. В противном случае используется кэшированное изображение в экземпляре. Автоматическая очистка образа отключена для контейнера, чтобы гарантировать, что кешированный образ не будет удален.


5
Ты уверен? Я видел случаи, когда старые образы докеров запускались даже после того, как я отправил новый образ в Dockerhub (с тем же именем тега). Думаю, возможно, мне следует просто нажимать имя тега каждый раз, когда создается новое изображение. Однако, по моему опыту, такое случается довольно редко, так что, возможно, это были временные проблемы с сетью. (Я знаю, что вы работаете над ECS, так что вы лучший человек, чтобы ответить на этот вопрос, но это не совсем то, что я испытал. Прошу прощения, если это покажется грубым, это не мое намерение!)
Ибрагим

1
Да, текущее поведение таково, что он будет каждый раз пытаться извлечь. Если извлечение не удалось (проблемы с сетью, отсутствие разрешений и т. Д.), Он попытается использовать кэшированное изображение. Вы можете найти более подробную информацию в файлах журнала агента, которые обычно находятся в /var/log/ecs.
Сэмюэл Карп

26

AWS рекомендует зарегистрировать новое определение задачи и обновить сервис для использования нового определения задачи. Самый простой способ сделать это:

  1. Перейти к определениям задач
  2. Выберите правильную задачу
  3. Выберите создать новую ревизию
  4. Если вы уже загружаете последнюю версию образа контейнера с чем-то вроде тега: latest, просто нажмите Create. В противном случае обновите номер версии образа контейнера и нажмите «Создать».
  5. Развернуть Действия
  6. Выберите службу обновления (дважды)
  7. Затем дождитесь перезапуска службы

Это руководство содержит более подробную информацию и описывает, как вышеуказанные шаги вписываются в непрерывный процесс разработки продукта.

Полное раскрытие информации: в этом руководстве представлены контейнеры от Bitnami, и я работаю на Bitnami. Однако высказанные здесь мысли являются моими собственными, а не мнением Bitnami.


3
Это работает, но вам, возможно, придется изменить минимальные / максимальные значения вашего сервиса. Если у вас есть только один экземпляр EC2, вы должны установить минимальный процент работоспособности равным нулю, иначе он никогда не завершит задачу (временно отключит вашу службу), чтобы развернуть обновленный контейнер.
Malvineous 04

3
@Malvineous Хороший вопрос! В разделе руководства по настройке ECS я описываю именно это. Вот рекомендуемая конфигурация из этого раздела: Количество задач - 1, Минимальный процент работоспособности - 0, Максимальный процент - 200.
Нил

@Neal Я попробовал ваш подход, как указано здесь ... все равно без радости
Хафиз

@Hafiz Если вам нужна помощь, чтобы разобраться в этом, вы должны описать, как далеко вы зашли и какую ошибку вы совершили.
Нил

Это работает только для служб, а не для задач без служб.
зайцман

9

Есть два способа сделать это.

Сначала используйте AWS CodeDeploy. Вы можете настроить сине-зеленые разделы развертывания в определении службы ECS. Сюда входят CodeDeployRoleForECS, еще одна TargetGroup для переключателя и тестовый прослушиватель (необязательно). AWS ECS создаст приложение и группу развертывания CodeDeploy и свяжет эти ресурсы CodeDeploy с вашим кластером / службой ECS и вашими ELB / TargetGroups. Затем вы можете использовать CodeDeploy для запуска развертывания, в котором вам нужно ввести AppSpec, который указывает, с помощью какой задачи / контейнера обновлять какую службу. Здесь вы указываете свою новую задачу / контейнер. Затем вы увидите, что новые экземпляры запускаются в новой TargetGroup, а старая TargetGroup отключена от ELB, и вскоре старые экземпляры, зарегистрированные в старой TargetGroup, будут прекращены.

Звучит очень сложно. Фактически, поскольку / если вы включили автоматическое масштабирование для своей службы ECS, простой способ сделать это - просто принудительно выполнить новое развертывание с помощью консоли или cli, как здесь указал джентльмен:

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

Таким образом, вы по-прежнему можете использовать тип развертывания «скользящее обновление», и ECS просто запустит новые экземпляры и опустошит старые без простоя вашей службы, если все в порядке. Плохая сторона заключается в том, что вы теряете точный контроль над развертыванием и не можете вернуться к предыдущей версии, если произойдет ошибка, и это нарушит текущую службу. Но это действительно простой способ.

Кстати, не забудьте установить правильные числа для Минимального процента работоспособности и Максимального процента, например 100 и 200.


Есть ли способ сделать это, не меняя IP? У меня, когда я запускал это, он работал, но он изменил частный IP, который у меня был
Migdotcom

@Migdotcom У меня была аналогичная проблема при необходимости прокси-сервера NLB. Короче говоря, единственный способ сохранить одинаковый IP-адрес экземпляра EC2 - это использовать либо эластичные IP-адреса, либо другой подход. Я не знаю вашего варианта использования, но привязка Global Accelerator к связанному с ECS ALB предоставила мне статические IP-адреса, и это решило мой вариант использования. Если вы хотите узнать динамические внутренние IP-адреса, вам нужно будет запросить ALB с помощью лямбда. Это потребовало больших усилий. Ссылка ниже: aws.amazon.com/blogs/networking-and-content-delivery/…
Маркус

aws ecs update-service --cluster <имя кластера> --service <имя службы> --force-new-deployment сработал для меня!
Гваскес

3

Я создал сценарий для развертывания обновленных образов Docker в промежуточной службе на ECS, чтобы соответствующее определение задачи относилось к текущим версиям образов Docker. Я не знаю наверняка, следую ли я лучшим практикам, поэтому обратная связь будет приветствоваться.

Чтобы скрипт работал, вам нужен либо запасной экземпляр ECS, либо deploymentConfiguration.minimumHealthyPercentзначение, чтобы ECS могла украсть экземпляр для развертывания обновленного определения задачи.

Мой алгоритм такой:

  1. Пометьте образы Docker, соответствующие контейнерам в определении задачи, с ревизией Git.
  2. Отправьте теги изображений Docker в соответствующие реестры.
  3. Отмените регистрацию старых определений задач в семействе определений задач.
  4. Зарегистрируйте новое определение задачи, теперь ссылающееся на образы Docker, помеченные текущими версиями Git.
  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.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

@Andris Спасибо, исправлено.
aknuds1

5
Это перебор. Должна быть возможность развертывания через terraform или только одну линию ecs-cli.
Holms

@holms Я использую Terraform для обновления образа задачи ECS. Это такое же излишество, как и приведенный выше код Python. Требуемые шаги столь же сложны.
Яри ​​Туркиа,

3

AWS CodePipeline.

Вы можете установить ECR в качестве источника и ECS в качестве цели для развертывания.


2
можно ссылку на любую документацию по этому поводу?
BenDog 09

2

Попался в ту же проблему. Потратив часы, вы завершили эти упрощенные шаги для автоматического развертывания обновленного образа:

1. Изменения в определении задачи ECS: для лучшего понимания предположим, что вы создали определение задачи с приведенными ниже сведениями (примечание: эти числа будут меняться соответственно в соответствии с определением вашей задачи):

launch_type = EC2

desired_count = 1

Затем вам необходимо внести следующие изменения:

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

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

3. нажать на изображение в ECR

sudo docker push  <your_account_id>.dkr.ecr.us-east-1.amazonaws.com/<your-image-name>:latest

4. применить силовое развертывание

sudo aws ecs update-service --cluster <your-cluster-name> --service <your-service-name> --force-new-deployment --region us-east-1

Примечание. Я написал все команды, предполагая, что регион является us-east-1 . Просто замените его на свой регион при внедрении.


Я заметил, что параметры являются параметрами терраформирования; Любые идеи, как добиться того же для CloudFormation: у меня есть мои AutoScalingGroup MinSize: 0 и MaxSize: 1; что еще нужно установить?
Уэйн

1

Следующее сработало для меня, если тег изображения докера такой же:

  1. Заходим в кластер и сервис.
  2. Выберите услугу и нажмите «Обновить».
  3. Установите количество задач как 0 и обновите.
  4. После завершения развертывания масштабируйте количество задач до 1.

0

Используя AWS cli, я попробовал aws ecs update-service, как было предложено выше. Не забирал последний докер из ECR. В конце концов, я перезапускаю свой сценарий Ansible, в котором был создан кластер ECS. Версия определения задачи изменяется при запуске ecs_taskdefinition. Тогда все хорошо. Подбирается новый образ докера.

По правде говоря, не уверен, вызывает ли изменение версии задачи повторное развертывание или playbook, использующий ecs_service, вызывает перезагрузку задачи.

Если кому-то интересно, я получу разрешение на публикацию очищенной версии моей пьесы.


Я считаю, что пересмотр определения задачи требуется только при обновлении фактической конфигурации определения задачи. в этом случае, если вы используете изображение с последним тегом, нет необходимости изменять конфигурацию? Конечно, иметь идентификатор фиксации в качестве тега - это хорошо, и иметь отдельную ревизию определения задачи, чтобы вы могли откатиться, но тогда ваш CI увидит все учетные данные, которые вы используете для контейнера, что я не хочу реализовывать.
Holms

0

ну, я также пытаюсь найти автоматический способ сделать это, то есть нажать изменения в ECR, а затем последний тег должен быть получен службой. Правильно, вы можете сделать это вручную, остановив задачу для своей службы из кластера. Новые задачи потянут обновленные контейнеры ECR.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.