Как правильно заставить мои задачи Amazon ECS обновлять свои образы Docker после обновления этих образов в соответствующем реестре?
Как правильно заставить мои задачи Amazon ECS обновлять свои образы Docker после обновления этих образов в соответствующем реестре?
Ответы:
Если ваша задача выполняется в рамках службы, вы можете принудительно выполнить новое развертывание. Это приводит к повторной оценке определения задачи и извлечению нового образа контейнера.
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
Каждый раз, когда вы запускаете задачу (либо через вызовы API StartTask
и, RunTask
либо запускается автоматически как часть службы), агент ECS будет выполнять одну docker pull
из image
задач, указанных в определении задачи. Если вы используете одно и то же имя образа (включая тег) каждый раз, когда отправляете в реестр, вы сможете запустить новый образ, запустив новую задачу. Обратите внимание: если Docker не может получить доступ к реестру по какой-либо причине (например, проблемы с сетью или проблемы с аутентификацией), агент ECS попытается использовать кэшированный образ; Если вы хотите избежать использования кэшированных изображений при обновлении образа, вам нужно каждый раз вставлять в реестр другой тег и соответственно обновлять определение задачи перед запуском новой задачи.
Обновление: теперь это поведение можно настроить с помощью ECS_IMAGE_PULL_BEHAVIOR
переменной среды, заданной в агенте ECS. Подробности смотрите в документации . На момент написания поддерживаются следующие настройки:
Поведение, используемое для настройки процесса извлечения изображения для ваших экземпляров контейнера. Ниже описаны дополнительные варианты поведения:
Если
default
указано, изображение извлекается удаленно. Если получение изображения не удается, то контейнер использует кэшированное изображение в экземпляре.Если
always
указано, изображение всегда извлекается удаленно. Если получение изображения не удается, задача не выполняется. Этот параметр гарантирует, что всегда будет загружаться последняя версия образа. Любые кэшированные изображения игнорируются и подлежат автоматической очистке.Если
once
указано, изображение извлекается удаленно, только если оно не было извлечено предыдущей задачей в том же экземпляре контейнера или если кэшированное изображение было удалено в процессе автоматической очистки образа. В противном случае используется кэшированное изображение в экземпляре. Это гарантирует, что не будут предприниматься попытки ненужного извлечения изображения.Если
prefer-cached
указано, изображение извлекается удаленно, если кэшированного изображения нет. В противном случае используется кэшированное изображение в экземпляре. Автоматическая очистка образа отключена для контейнера, чтобы гарантировать, что кешированный образ не будет удален.
/var/log/ecs
.
AWS рекомендует зарегистрировать новое определение задачи и обновить сервис для использования нового определения задачи. Самый простой способ сделать это:
Это руководство содержит более подробную информацию и описывает, как вышеуказанные шаги вписываются в непрерывный процесс разработки продукта.
Полное раскрытие информации: в этом руководстве представлены контейнеры от Bitnami, и я работаю на Bitnami. Однако высказанные здесь мысли являются моими собственными, а не мнением Bitnami.
Есть два способа сделать это.
Сначала используйте 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.
Я создал сценарий для развертывания обновленных образов Docker в промежуточной службе на ECS, чтобы соответствующее определение задачи относилось к текущим версиям образов Docker. Я не знаю наверняка, следую ли я лучшим практикам, поэтому обратная связь будет приветствоваться.
Чтобы скрипт работал, вам нужен либо запасной экземпляр ECS, либо deploymentConfiguration.minimumHealthyPercent
значение, чтобы 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)
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. Изменения в определении задачи 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 . Просто замените его на свой регион при внедрении.
Следующее сработало для меня, если тег изображения докера такой же:
Используя AWS cli, я попробовал aws ecs update-service, как было предложено выше. Не забирал последний докер из ECR. В конце концов, я перезапускаю свой сценарий Ansible, в котором был создан кластер ECS. Версия определения задачи изменяется при запуске ecs_taskdefinition. Тогда все хорошо. Подбирается новый образ докера.
По правде говоря, не уверен, вызывает ли изменение версии задачи повторное развертывание или playbook, использующий ecs_service, вызывает перезагрузку задачи.
Если кому-то интересно, я получу разрешение на публикацию очищенной версии моей пьесы.
ну, я также пытаюсь найти автоматический способ сделать это, то есть нажать изменения в ECR, а затем последний тег должен быть получен службой. Правильно, вы можете сделать это вручную, остановив задачу для своей службы из кластера. Новые задачи потянут обновленные контейнеры ECR.
Следующие команды работали для меня
docker build -t <repo> .
docker push <repo>
ecs-cli compose stop
ecs-cli compose start