Qual é a abordagem certa para fazer com que minhas tarefas do Amazon ECS atualizem suas imagens Docker, uma vez que essas imagens foram atualizadas no registro correspondente?
Qual é a abordagem certa para fazer com que minhas tarefas do Amazon ECS atualizem suas imagens Docker, uma vez que essas imagens foram atualizadas no registro correspondente?
Respostas:
Se sua tarefa estiver sendo executada em um serviço, você pode forçar uma nova implantação. Isso força a definição da tarefa a ser reavaliada e a nova imagem do contêiner a ser obtida.
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
Sempre que você inicia uma tarefa (por meio das chamadas de API StartTask
e RunTask
ou que é iniciada automaticamente como parte de um serviço), o Agente ECS executará um docker pull
dos que image
você especificar em sua definição de tarefa. Se você usar o mesmo nome de imagem (incluindo tag) cada vez que for enviar para o registro, poderá fazer com que a nova imagem seja executada executando uma nova tarefa. Observe que se o Docker não conseguir acessar o registro por qualquer motivo (por exemplo, problemas de rede ou problemas de autenticação), o Agente ECS tentará usar uma imagem em cache; se você deseja evitar que imagens em cache sejam usadas ao atualizar sua imagem, você desejará enviar uma tag diferente para seu registro a cada vez e atualizar sua definição de tarefa de forma correspondente antes de executar a nova tarefa.
Atualizar: esse comportamento agora pode ser ajustado por ECS_IMAGE_PULL_BEHAVIOR
meio da variável de ambiente definida no agente ECS. Consulte a documentação para obter detalhes. No momento da escrita, as seguintes configurações são suportadas:
O comportamento usado para personalizar o processo de pull de imagem para suas instâncias de contêiner. O seguinte descreve os comportamentos opcionais:
Se
default
for especificado, a imagem é extraída remotamente. Se a extração da imagem falhar, o contêiner usará a imagem em cache na instância.Se
always
for especificado, a imagem é sempre puxada remotamente. Se a extração da imagem falhar, a tarefa falhará. Esta opção garante que a versão mais recente da imagem seja sempre obtida. Todas as imagens em cache são ignoradas e estão sujeitas ao processo de limpeza de imagem automatizado.Se
once
for especificado, a imagem será extraída remotamente apenas se não tiver sido extraída por uma tarefa anterior na mesma instância do contêiner ou se a imagem em cache foi removida pelo processo de limpeza de imagem automatizado. Caso contrário, a imagem em cache na instância será usada. Isso garante que nenhum pull de imagem desnecessário seja tentado.Se
prefer-cached
for especificado, a imagem será extraída remotamente se não houver imagem em cache. Caso contrário, a imagem em cache na instância será usada. A limpeza de imagem automatizada é desativada para o contêiner para garantir que a imagem em cache não seja removida.
/var/log/ecs
.
Registrar uma nova definição de tarefa e atualizar o serviço para usar a nova definição de tarefa é a abordagem recomendada pela AWS. A maneira mais fácil de fazer isso é:
Este tutorial tem mais detalhes e descreve como as etapas acima se encaixam em um processo de desenvolvimento de produto de ponta a ponta.
Divulgação completa: este tutorial apresenta contêineres da Bitnami e eu trabalho para a Bitnami. No entanto, os pensamentos expressos aqui são meus e não a opinião de Bitnami.
Existem duas maneiras de fazer isso.
Primeiro, use o AWS CodeDeploy. Você pode configurar as seções de implantação Azul / Verde na definição de serviço ECS. Isso inclui um CodeDeployRoleForECS, outro TargetGroup para switch e um Listener de teste (opcional). O AWS ECS criará o aplicativo CodeDeploy e o grupo de implantação e vinculará esses recursos do CodeDeploy ao seu ECS Cluster / Service e ELB / TargetGroups para você. Em seguida, você pode usar CodeDeploy para iniciar uma implantação, na qual você precisa inserir um AppSpec que especifica o uso de qual tarefa / contêiner para atualizar qual serviço. Aqui é onde você especifica sua nova tarefa / contêiner. Em seguida, você verá que novas instâncias são ativadas no novo TargetGroup e o antigo TargetGroup é desconectado do ELB e, em breve, as instâncias antigas registradas no antigo TargetGroup serão encerradas.
Isso parece muito complicado. Na verdade, uma vez que / se você habilitou o escalonamento automático em seu serviço ECS, uma maneira simples de fazer isso é apenas forçar uma nova implantação usando o console ou CLI, como um cavalheiro aqui apontou:
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
Dessa forma, você ainda pode usar o tipo de implantação "rolling update", e o ECS simplesmente ativará novas instâncias e drenará as antigas sem tempo de inatividade do serviço, se tudo estiver OK. O lado ruim é que você perde o controle fino da implantação e não pode reverter para a versão anterior se houver um erro e isso interromperá o serviço em andamento. Mas esta é uma maneira muito simples de fazer.
BTW, não se esqueça de definir os números adequados para porcentagem mínima saudável e porcentagem máxima, como 100 e 200.
Criei um script para implantar imagens atualizadas do Docker em um serviço de teste no ECS, de modo que a definição de tarefa correspondente se refira às versões atuais das imagens do Docker. Não sei ao certo se estou seguindo as práticas recomendadas, portanto, comentários serão bem-vindos.
Para que o script funcione, você precisa de uma instância sobressalente do ECS ou de um deploymentConfiguration.minimumHealthyPercent
valor para que o ECS possa roubar uma instância para implantar a definição de tarefa atualizada.
Meu algoritmo é assim:
Meu código colado abaixo:
#!/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
O seguinte funcionou para mim, caso a tag da imagem do docker fosse a mesma:
Encontrou o mesmo problema. Depois de passar horas, concluí estas etapas simplificadas para implantação automatizada de imagem atualizada:
1. Mudanças na definição da tarefa ECS: Para um melhor entendimento, vamos supor que você criou uma definição de tarefa com os detalhes abaixo (nota: esses números mudariam de acordo com a definição da sua tarefa):
launch_type = EC2
desired_count = 1
Em seguida, você precisa fazer as seguintes alterações:
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. Marque sua imagem como < nome-da-sua-imagem>: mais recente . A chave mais recente se encarrega de ser puxada pela respectiva tarefa do 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. Empurre para a imagem para ECR
sudo docker push <your_account_id>.dkr.ecr.us-east-1.amazonaws.com/<your-image-name>:latest
4. Aplicar desdobramento forçado
sudo aws ecs update-service --cluster <your-cluster-name> --service <your-service-name> --force-new-deployment --region us-east-1
Observação: escrevi todos os comandos presumindo que a região seja us-east-1 . Basta substituí-lo por sua respectiva região durante a implementação.
Usando o AWS cli, experimentei o aws ecs update-service conforme sugerido acima. Não foi obtido o docker mais recente do ECR. No final, executei novamente meu manual do Ansible que criou o cluster ECS. A versão da definição da tarefa é alterada quando ecs_taskdefinition é executado. Então está tudo bem. A nova imagem da janela de encaixe é selecionada.
Sinceramente, não tenho certeza se a alteração da versão da tarefa força a reimplantação ou se o manual usando o ecs_service faz com que a tarefa seja recarregada.
Se alguém estiver interessado, terei permissão para publicar uma versão limpa de meu manual.
Bem, também estou tentando encontrar uma maneira automatizada de fazer isso, ou seja, enviar as alterações para ECR e, em seguida, a tag mais recente deve ser selecionada pelo serviço. Certo, você pode fazer isso manualmente, parando a tarefa para seu serviço de seu cluster. Novas tarefas extrairão os contêineres ECR atualizados.
Os seguintes comandos funcionaram para mim
docker build -t <repo> .
docker push <repo>
ecs-cli compose stop
ecs-cli compose start