¿Cuál es el enfoque correcto para que mis tareas de Amazon ECS actualicen sus imágenes de Docker, una vez que dichas imágenes se hayan actualizado en el registro correspondiente?
¿Cuál es el enfoque correcto para que mis tareas de Amazon ECS actualicen sus imágenes de Docker, una vez que dichas imágenes se hayan actualizado en el registro correspondiente?
Respuestas:
Si su tarea se ejecuta bajo un servicio, puede forzar una nueva implementación. Esto obliga a reevaluar la definición de la tarea y extraer la nueva imagen del contenedor.
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
Cada vez que inicie una tarea (ya sea a través de las llamadas StartTask
y RunTask
API o que se inicie automáticamente como parte de un Servicio), el Agente de ECS realizará una docker pull
de las image
que especifique en la definición de la tarea. Si usa el mismo nombre de imagen (incluida la etiqueta) cada vez que ingresa a su registro, debería poder ejecutar la nueva imagen ejecutando una nueva tarea. Tenga en cuenta que si Docker no puede acceder al registro por algún motivo (por ejemplo, problemas de red o problemas de autenticación), el agente de ECS intentará utilizar una imagen en caché; Si desea evitar que se usen imágenes almacenadas en caché cuando actualice su imagen, querrá insertar una etiqueta diferente en su registro cada vez y actualizar la definición de la tarea correspondientemente antes de ejecutar la nueva tarea.
Actualización: este comportamiento ahora se puede ajustar a través de la ECS_IMAGE_PULL_BEHAVIOR
variable de entorno establecida en el agente de ECS. Consulte la documentación para obtener más detalles. En el momento de escribir este artículo, se admiten las siguientes configuraciones:
El comportamiento utilizado para personalizar el proceso de extracción de imágenes para sus instancias de contenedor. A continuación se describen los comportamientos opcionales:
Si
default
se especifica, la imagen se extrae de forma remota. Si la extracción de la imagen falla, el contenedor usa la imagen almacenada en caché en la instancia.Si
always
se especifica, la imagen siempre se extrae de forma remota. Si la extracción de la imagen falla, la tarea falla. Esta opción garantiza que siempre se extraiga la última versión de la imagen. Las imágenes almacenadas en caché se ignoran y están sujetas al proceso de limpieza de imágenes automatizado.Si
once
se especifica, la imagen se extrae de forma remota solo si no ha sido extraída por una tarea anterior en la misma instancia de contenedor o si la imagen en caché fue eliminada por el proceso de limpieza de imágenes automatizado. De lo contrario, se utiliza la imagen almacenada en caché en la instancia. Esto asegura que no se intente extraer imágenes innecesarias.Si
prefer-cached
se especifica, la imagen se extrae de forma remota si no hay una imagen en caché. De lo contrario, se utiliza la imagen almacenada en caché en la instancia. La limpieza automática de imágenes está deshabilitada para el contenedor para garantizar que la imagen almacenada en caché no se elimine.
/var/log/ecs
.
Registrar una nueva definición de tarea y actualizar el servicio para usar la nueva definición de tarea es el enfoque recomendado por AWS. La forma más sencilla de hacerlo es:
Este tutorial tiene más detalles y describe cómo los pasos anteriores encajan en un proceso de desarrollo de producto de un extremo a otro.
Divulgación completa: este tutorial presenta contenedores de Bitnami y yo trabajo para Bitnami. Sin embargo, los pensamientos expresados aquí son míos y no la opinión de Bitnami.
Hay dos maneras de hacer esto.
Primero, use AWS CodeDeploy. Puede configurar las secciones de implementación azul / verde en la definición del servicio ECS. Esto incluye un CodeDeployRoleForECS, otro TargetGroup para conmutador y un escucha de prueba (opcional). AWS ECS creará el grupo de implementación y la aplicación CodeDeploy y vinculará estos recursos de CodeDeploy con su clúster / servicio ECS y sus grupos ELB / TargetGroups por usted. Luego, puede usar CodeDeploy para iniciar una implementación, en la que debe ingresar una AppSpec que especifica el uso de qué tarea / contenedor para actualizar qué servicio. Aquí es donde especifica su nueva tarea / contenedor. Luego, verá que se activan nuevas instancias en el nuevo TargetGroup y el antiguo TargetGroup se desconecta del ELB, y pronto se cancelarán las antiguas instancias registradas en el antiguo TargetGroup.
Suena muy complicado. En realidad, dado que / si ha habilitado el escalado automático en su servicio ECS, una forma sencilla de hacerlo es simplemente forzar una nueva implementación usando la consola o cli, como un caballero señaló aquí:
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
De esta manera, puede seguir utilizando el tipo de implementación de "actualización continua", y ECS simplemente activará nuevas instancias y agotará las antiguas sin tiempo de inactividad de su servicio si todo está bien. El lado malo es que pierde el control sobre la implementación y no puede volver a la versión anterior si hay un error y esto interrumpirá el servicio en curso. Pero esta es una forma realmente sencilla de hacerlo.
Por cierto, no olvide establecer los números adecuados para el porcentaje mínimo saludable y el porcentaje máximo, como 100 y 200.
Creé un script para implementar imágenes de Docker actualizadas en un servicio de ensayo en ECS, de modo que la definición de tarea correspondiente se refiera a las versiones actuales de las imágenes de Docker. No sé con certeza si sigo las mejores prácticas, por lo que agradeceríamos recibir comentarios.
Para que el script funcione, necesita una instancia de ECS de repuesto o un deploymentConfiguration.minimumHealthyPercent
valor para que ECS pueda robar una instancia para implementar la definición de tarea actualizada.
Mi algoritmo es así:
Mi código pegado a continuación:
#!/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
Lo siguiente funcionó para mí en caso de que la etiqueta de la imagen de la ventana acoplable sea la misma:
Me encontré con el mismo problema. Después de pasar horas, he concluido estos pasos simplificados para la implementación automatizada de la imagen actualizada:
1.Cambios en la definición de la tarea de ECS: para una mejor comprensión, supongamos que ha creado una definición de tarea con los detalles a continuación (nota: estos números cambiarían en consecuencia según la definición de su tarea):
launch_type = EC2
desired_count = 1
Entonces necesitas hacer los siguientes cambios:
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. Etiquete su imagen como < su-nombre-de-imagen>: última . La última clave se encarga de que la tarea ECS correspondiente la tire.
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.Presione la imagen a ECR
sudo docker push <your_account_id>.dkr.ecr.us-east-1.amazonaws.com/<your-image-name>:latest
4.aplicar despliegue forzado
sudo aws ecs update-service --cluster <your-cluster-name> --service <your-service-name> --force-new-deployment --region us-east-1
Nota: He escrito todos los comandos asumiendo que la región es us-east-1 . Simplemente reemplácelo con su región respectiva durante la implementación.
Usando AWS cli probé aws ecs update-service como se sugirió anteriormente. No recogió la última ventana acoplable de ECR. Al final, volví a ejecutar mi libro de jugadas de Ansible que creó el clúster ECS. La versión de la definición de la tarea cambia cuando se ejecuta ecs_taskdefinition. Entonces todo va bien. Se recoge la nueva imagen de la ventana acoplable.
A decir verdad, no estoy seguro de si el cambio de versión de la tarea obliga a la redistribución o si el libro de jugadas que usa ecs_service hace que la tarea se recargue.
Si alguien está interesado, obtendré permiso para publicar una versión desinfectada de mi libro de jugadas.
Bueno, también estoy tratando de encontrar una forma automatizada de hacerlo, es decir, impulsar los cambios a ECR y luego el servicio debería recoger la última etiqueta. Puede hacerlo manualmente deteniendo la tarea de su servicio desde su clúster. Las nuevas tareas extraerán los contenedores ECR actualizados.
Los siguientes comandos funcionaron para mí
docker build -t <repo> .
docker push <repo>
ecs-cli compose stop
ecs-cli compose start