Quelle est la bonne approche pour que mes tâches Amazon ECS mettent à jour leurs images Docker, une fois que lesdites images ont été mises à jour dans le registre correspondant?
Quelle est la bonne approche pour que mes tâches Amazon ECS mettent à jour leurs images Docker, une fois que lesdites images ont été mises à jour dans le registre correspondant?
Réponses:
Si votre tâche s'exécute sous un service, vous pouvez forcer un nouveau déploiement. Cela force la réévaluation de la définition de tâche et l'extraction de la nouvelle image de conteneur.
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
Chaque fois que vous démarrez une tâche (via les appels d'API StartTask
et RunTask
ou qui est lancée automatiquement dans le cadre d'un service), l'agent ECS exécutera l'une docker pull
des opérations que image
vous spécifiez dans votre définition de tâche. Si vous utilisez le même nom d'image (y compris la balise) chaque fois que vous poussez dans votre registre, vous devriez pouvoir exécuter la nouvelle image en exécutant une nouvelle tâche. Notez que si Docker ne peut pas accéder au registre pour une raison quelconque (par exemple, des problèmes de réseau ou des problèmes d'authentification), l'agent ECS tentera d'utiliser une image mise en cache; si vous souhaitez éviter que les images mises en cache soient utilisées lorsque vous mettez à jour votre image, vous voudrez pousser une balise différente dans votre registre à chaque fois et mettre à jour votre définition de tâche en conséquence avant d'exécuter la nouvelle tâche.
Mise à jour: ce comportement peut désormais être réglé via la ECS_IMAGE_PULL_BEHAVIOR
variable d'environnement définie sur l'agent ECS. Consultez la documentation pour plus de détails. Au moment de la rédaction de cet article, les paramètres suivants sont pris en charge:
Le comportement utilisé pour personnaliser le processus d'image d'extraction pour vos instances de conteneur. Ce qui suit décrit les comportements facultatifs:
Si
default
est spécifié, l'image est extraite à distance. Si l'extraction de l'image échoue, le conteneur utilise l'image mise en cache sur l'instance.Si
always
est spécifié, l'image est toujours extraite à distance. Si l'extraction de l'image échoue, la tâche échoue. Cette option garantit que la dernière version de l'image est toujours extraite. Toutes les images mises en cache sont ignorées et sont soumises au processus de nettoyage d'image automatisé.Si
once
est spécifié, l'image est extraite à distance uniquement si elle n'a pas été extraite par une tâche précédente sur la même instance de conteneur ou si l'image mise en cache a été supprimée par le processus de nettoyage d'image automatisé. Sinon, l'image mise en cache sur l'instance est utilisée. Cela garantit qu'aucune extraction d'image inutile n'est tentée.Si
prefer-cached
est spécifié, l'image est extraite à distance s'il n'y a pas d'image mise en cache. Sinon, l'image mise en cache sur l'instance est utilisée. Le nettoyage d'image automatisé est désactivé pour le conteneur afin de garantir que l'image mise en cache n'est pas supprimée.
/var/log/ecs
.
L'enregistrement d'une nouvelle définition de tâche et la mise à jour du service pour utiliser la nouvelle définition de tâche est l'approche recommandée par AWS. La façon la plus simple de procéder est de:
Ce didacticiel est plus détaillé et décrit comment les étapes ci-dessus s'intègrent dans un processus de développement de produit de bout en bout.
Divulgation complète: Ce tutoriel présente des conteneurs de Bitnami et je travaille pour Bitnami. Cependant, les pensées exprimées ici sont les miennes et non l'opinion de Bitnami.
Il y a deux façons de faire ça.
Tout d'abord, utilisez AWS CodeDeploy. Vous pouvez configurer des sections de déploiement bleu / vert dans la définition de service ECS. Cela inclut un CodeDeployRoleForECS, un autre TargetGroup pour le commutateur et un écouteur de test (facultatif). AWS ECS créera l'application CodeDeploy et le groupe de déploiement et liera ces ressources CodeDeploy avec votre cluster / service ECS et vos ELB / TargetGroups pour vous. Ensuite, vous pouvez utiliser CodeDeploy pour lancer un déploiement, dans lequel vous devez entrer un AppSpec qui spécifie l'utilisation de quelle tâche / conteneur pour mettre à jour quel service. C'est ici que vous spécifiez votre nouvelle tâche / conteneur. Ensuite, vous verrez que de nouvelles instances sont lancées dans le nouveau TargetGroup et que l'ancien TargetGroup est déconnecté de l'ELB, et bientôt les anciennes instances enregistrées dans l'ancien TargetGroup seront arrêtées.
Cela semble très compliqué. En fait, depuis / si vous avez activé la mise à l'échelle automatique sur votre service ECS, un moyen simple de le faire est de simplement forcer un nouveau déploiement à l'aide de la console ou du cli, comme un gentleman l'a souligné ici:
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
De cette façon, vous pouvez toujours utiliser le type de déploiement «mise à jour progressive», et ECS lancera simplement de nouvelles instances et vidangera les anciennes sans temps d'arrêt de votre service si tout va bien. Le mauvais côté est que vous perdez un contrôle précis sur le déploiement et que vous ne pouvez pas revenir à la version précédente en cas d'erreur, ce qui interrompra le service en cours. Mais c'est une manière vraiment simple de procéder.
BTW, n'oubliez pas de définir des nombres appropriés pour le pourcentage sain minimum et le pourcentage maximum, comme 100 et 200.
J'ai créé un script pour déployer des images Docker mises à jour sur un service de préparation sur ECS, de sorte que la définition de tâche correspondante se réfère aux versions actuelles des images Docker. Je ne sais pas avec certitude si je suis les meilleures pratiques, donc vos commentaires seraient les bienvenus.
Pour que le script fonctionne, vous avez besoin d'une instance ECS de rechange ou d'une deploymentConfiguration.minimumHealthyPercent
valeur afin qu'ECS puisse voler une instance sur laquelle déployer la définition de tâche mise à jour.
Mon algorithme est comme ceci:
Mon code collé ci-dessous:
#!/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
Ran dans le même problème. Après avoir passé des heures, nous avons conclu ces étapes simplifiées pour le déploiement automatisé de l'image mise à jour:
Changements de la définition de tâche ECS: pour une meilleure compréhension, supposons que vous ayez créé une définition de tâche avec les détails ci-dessous (note: ces nombres changeraient en conséquence selon votre définition de tâche):
launch_type = EC2
desired_count = 1
Ensuite, vous devez apporter les modifications suivantes:
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.Tag votre image < votre image nom>: le dernier . La dernière clé s'occupe d'être tirée par la tâche ECS respective.
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.Pousser l'image vers ECR
sudo docker push <your_account_id>.dkr.ecr.us-east-1.amazonaws.com/<your-image-name>:latest
4. appliquer le déploiement de force
sudo aws ecs update-service --cluster <your-cluster-name> --service <your-service-name> --force-new-deployment --region us-east-1
Remarque: j'ai écrit toutes les commandes en supposant que la région est us-east-1 . Remplacez-le simplement par votre région respective lors de la mise en œuvre.
La suite a fonctionné pour moi au cas où la balise d'image du docker serait la même:
En utilisant AWS cli, j'ai essayé aws ecs update-service comme suggéré ci-dessus. N'a pas récupéré le dernier docker d'ECR. En fin de compte, j'ai réexécuté mon playbook Ansible qui a créé le cluster ECS. La version de la définition de tâche est modifiée lors de l'exécution de ecs_taskdefinition. Alors tout va bien. La nouvelle image du docker est récupérée.
Je ne sais vraiment pas si le changement de version de la tâche force le redéploiement ou si le playbook utilisant ecs_service provoque le rechargement de la tâche.
Si quelqu'un est intéressé, j'obtiendrai l'autorisation de publier une version nettoyée de mon playbook.
Eh bien, j'essaie également de trouver un moyen automatisé de le faire, c'est-à-dire pousser les modifications apportées à ECR, puis la dernière balise devrait être récupérée par le service. Vous pouvez le faire manuellement en arrêtant la tâche de votre service à partir de votre cluster. Les nouvelles tâches extrairont les conteneurs ECR mis à jour.
Les commandes suivantes ont fonctionné pour moi
docker build -t <repo> .
docker push <repo>
ecs-cli compose stop
ecs-cli compose start