Як розгорнути оновлені зображення Docker для завдань Amazon ECS?


110

Який правильний підхід, щоб змусити мої завдання Амазонки ECS оновлювати свої зображення Docker після того, як зазначені зображення були оновлені у відповідному реєстрі?


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

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

Ах готча. Не був у цьому впевнений. Чи можете ви надати трохи більше інформації?
iSkore

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

це непросто або очевидно і з EKS. Яким чином F є найпоширенішим завданням використання кластера, розгортання нового зображення, тому незрозуміле в документації?

Відповіді:


88

Якщо ваше завдання працює під службою, ви можете примусити нове розгортання. Це змушує повторно оцінити визначення завдання та витягнути нове зображення контейнера.

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

1
Я думаю, що для цього вам потрібно переконатися, що у ваших екземплярах ECS є достатньо ресурсів для розгортання додаткового завдання такого ж розміру. Я припускаю, що AWS намагається фактично виконати гарячу заміну, очікуючи попереднього завантаження нового екземпляра завдання, перш ніж припинити старий. Він просто додає записи "розгортання" з 0 запущеними екземплярами, якщо ви цього не зробите.
Олексій Федулов

3
@ АлексФедулов, так, я думаю, ти прав. Щоб не виникати простоїв під час створення нового розгортання, ви можете: 1) Надати достатньо екземплярів для розгортання нової версії поряд зі старою версією. Цього можна досягти за допомогою автоматичного масштабування. 2) Використовуйте тип розгортання Fargate. Ви можете уникнути розподілу додаткових ресурсів, встановивши параметр служби "мінімально здоровий відсоток" на 0, щоб ECS змогла видалити стару службу перед розгортанням нової. Однак це спричинить певний час простою.
Діма

3
Невідомі параметри: --force-new-розгортання
user4674453

1
Невідомі варіанти: --force-new-розгортання: оновлення awscli
Kyle Parisi

1
Я спробував цю команду, він не оновлює контейнер з новим зображенням, він закручує інший контейнер з таким же старим зображенням. Отже, у мене працює два контейнери, хоча в сервісі я вказав потрібну кількість = 1
математика

61

Кожен раз , коли ви запускаєте завдання (або через StartTaskта RunTaskвиклики API або що автоматично запускається як частина служби), то ECS Агент виконає docker pullз imageвас вказати у визначенні завдань. Якщо ви використовуєте одне й те саме ім’я зображення (включаючи тег) кожного разу, коли ви натискаєте на свій реєстр, вам слід мати можливість запускати нове зображення, виконуючи нове завдання. Зверніть увагу, що якщо Docker не може отримати доступ до реєстру з будь-якої причини (наприклад, проблеми з мережею або проблеми автентифікації), агент ECS спробує використати кешоване зображення; якщо ви хочете уникнути використання кешованих зображень при використанні під час оновлення зображення, вам потрібно буде щоразу натискати на ваш реєстр інший тег і відповідно оновити визначення завдання перед запуском нового завдання.

Оновлення: тепер ця поведінка може бути налаштована за допомогою ECS_IMAGE_PULL_BEHAVIORзмінної середовища, встановленої на агенті ECS. Детальну інформацію див. У документації . На час написання підтримуються наступні налаштування:

Поведінка, яка використовується для налаштування процесу витягування зображень для екземплярів контейнера. Далі описано необов'язкове поведінку:

  • Якщо defaultвказано, зображення витягується віддалено. Якщо витяг зображення не вдається, контейнер використовує кешоване зображення на екземплярі.

  • Якщо alwaysвказано, зображення завжди витягується віддалено. Якщо витягнути зображення не вдалося, то завдання не вдасться. Ця опція гарантує, що остання версія зображення завжди буде витягнута. Будь-які кешовані зображення ігноруються та підлягають автоматичному очищенню зображення.

  • Якщо onceвказано, зображення витягується віддалено, лише якщо воно не було витягнуто попереднім завданням у тому ж екземплярі контейнера або якщо кешоване зображення було видалено автоматичним процесом очищення зображення. В іншому випадку використовується кешоване зображення в екземплярі. Це гарантує, що ніяких зайвих знімків зображень не буде здійснено.

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


4
Ти впевнений? Я бачив випадки, коли старі зображення докера запускаються навіть після того, як я натиснув нове зображення на Dockerhub (використовуючи те саме ім’я тега). Я думаю, що, можливо, я повинен просто зіткнути ім'я тегу кожного разу, коли створюється нове зображення. Однак це було досить рідко в моєму досвіді, тому, можливо, це були лише миттєві проблеми з мережею. (Я знаю, що ви працюєте над системою ECS, тому ви найкраща людина, яка відповість на це, але це не саме те, що я пережив. Вибачте, якщо це виходить як грубий, а не мій намір!)
Ібрагім

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

26

Реєстрація нового визначення завдання та оновлення служби для використання нового визначення завдання - це підхід, рекомендований AWS. Найпростіший спосіб зробити це:

  1. Перейдіть до визначення завдань
  2. Виберіть правильне завдання
  3. Виберіть створення нової версії
  4. Якщо ви вже витягуєте останню версію зображення контейнера з чимось на зразок: остання тег, просто натисніть кнопку Створити. В іншому випадку оновіть номер версії зображення контейнера і натисніть кнопку Створити.
  5. Розгортайте дії
  6. Оберіть службу оновлення (двічі)
  7. Потім зачекайте, коли послуга буде перезапущена

У цьому підручнику є більш докладно і описано, як вищезазначені кроки вписуються в процес розробки продукту, що завершується.

Повне розкриття: У цьому підручнику представлені контейнери з Bitnami, і я працюю в Bitnami. Однак думки, висловлені тут, є моєю власною, а не думкою Бітнамі.


3
Це працює, але, можливо, вам доведеться змінити значення служби min / max. Якщо у вас є лише один екземпляр EC2, вам потрібно встановити мінімально здоровий відсоток до нуля, інакше це ніколи не знищить завдання (зробити вашу службу тимчасово офлайн) для розгортання оновленого контейнера.
Malvineous

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

@Neal Я спробував ваш підхід, як зазначено тут ... все ще немає радості
Хафіз

@Hafiz Якщо вам потрібна допомога, щоб розібратися в цьому, слід описати, як далеко ви потрапили і яку помилку ви потрапили.
Ніл

Це працює лише для сервісів, а не для завдань без послуг.
zaitsman

8

Є два способи зробити це.

По-перше, використовуйте AWS CodeDeploy. Ви можете налаштувати розділи синього / зеленого розгортання у визначенні служби ECS. Сюди входить CodeDeployRoleForECS, ще одна TargetGroup для комутатора та тестовий слухач (необов’язково). AWS ECS створить групу додатків та розгортань CodeDeploy та зв’яже ці ресурси CodeDeploy із вашим кластером / службою ECS та вашими ELB / TargetGroups. Тоді ви можете використовувати CodeDeploy для ініціювання розгортання, в якому потрібно ввести AppSpec, який вказує використання завдання / контейнера для оновлення тієї служби. Тут ви вказуєте нове завдання / контейнер. Потім ви побачите, що нові екземпляри з'являються в новій TargetGroup, а стара TargetGroup відключається до ELB, і незабаром старі екземпляри, зареєстровані в старій TargetGroup, будуть припинені.

Це звучить дуже складно. Насправді, оскільки / якщо ви ввімкнули автоматичне масштабування на вашій службі ECS, простий спосіб зробити це просто змусити нове розгортання за допомогою консолі чи кліпу, як тут зазначив джентльмен:

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

Таким чином, ви все ще можете використовувати тип розгортання "прокатного оновлення", і ECS просто розкручує нові екземпляри та зливає старі без простою вашої служби, якщо все добре. Погана сторона полягає в тому, що ви втрачаєте тонкий контроль над розгортанням, і не можете повернутися до попередньої версії, якщо сталася помилка, і це порушить поточну послугу. Але це дійсно простий шлях.

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


Чи є спосіб це зробити без зміни IP-адреси? У моєму, коли я запускав це, це працювало, але це змінило приватний IP, яким я працював
Migdotcom

@Migdotcom У мене була подібна проблема, коли мені потрібен проксі-сервер NLB. Коротше кажучи, єдиний спосіб зберегти IP екземпляра EC2 однаковий - використовувати або еластичні IP-адреси, або використовувати інший підхід. Я не знаю вашого випадку використання, але підключення глобального акселератора до АЛБ, пов'язаного з ECS, надало мені статичні IP-адреси, це вирішило мій випадок використання. Якщо ви хочете знати динамічні внутрішні IP-адреси, вам потрібно буде запитати ALB за допомогою лямбда. Це було багато зусиль. Посилання нижче: aws.amazon.com/blogs/networking-and-content-delivery/…
Маркус

aws ecs update-service --cluster <ім'я кластера> --service <ім'я служби> --force-new-розгортання працювало для мене!
gvasquez

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
Це надмірність. Повинно бути можливим розгортання за допомогою термоформи або просто однієї лінії ecs-cli.
Holms

@holms Я використовую Terraform для оновлення зображення завдання ECS. Це так само надмірно, як і вищезгаданий python-код. Необхідні кроки такі ж складні.
Ярі Туркія

3

AWS CodePipeline.

Ви можете встановити ECR як джерело, а ECS - як ціль, до якої потрібно розгорнути.


2
чи можете ви посилання на будь-яку документацію для цього?
BenDog

1

Наступний працював для мене у випадку, якщо тег зображення docker є таким же:

  1. Перейдіть до кластеру та обслуговування.
  2. Виберіть службу та натисніть на оновлення.
  3. Встановіть кількість завдань як 0 та оновіть.
  4. Після завершення розгортання змініть масштаб кількості завдань до 1.

1

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

Зміни визначення завдання 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.Тегніть зображення як < своє-зображення-ім'я>: останнє . Остання клавіша забезпечує потягнення за відповідним завданням 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

Примітка. Я написав усі команди, припускаючи, що цей регіон є нами схід-1 . Просто замініть його на відповідний регіон під час впровадження.


Я помітив, що параметри - це параметри форми; Будь-які ідеї, як досягти того ж для CloudFormation: у мене є AutoScalingGroup MinSize: 0 та MaxSize: 1; що ще потрібно встановити?
Уейн

0

За допомогою AWS cli я спробував послугу оновлення aws ecs, як було запропоновано вище. Не забрав останній докер з ECR. Врешті-решт я перезапустив мою програму Ansible, яка створила кластер ECS. Версія визначення завдання нарікається під час запуску ecs_taskdefinition. Тоді все добре. Зображено нове зображення докера.

Поправді не впевнений, чи зміна версії задачі змушує перевстановитись, або якщо ігрова книга, що використовує службу ecs_service, спричинить перезавантаження завдання.

Якщо хтось зацікавиться, я отримаю дозвіл на публікацію санізованої версії своєї ігрової книги.


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

0

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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.