Comment déployer des images Docker mises à jour sur des tâches Amazon ECS?

110

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?

aknuds1
la source
Je recommanderais d'exécuter une fonction Lambda automatisée / programmée. De cette façon, c'est en dehors de l'instance. Avez-vous essayé cela? Vous pouvez également utiliser SWF pour effectuer des étapes à la fois
iSkore
Je n'ai pas besoin de l'automatiser @iSkore. Je voudrais éventuellement écrire un script pour cela, mais je choisis moi-même quand l'exécuter.
aknuds1
Ahh gotcha. J'étais pas sûr de ça. Pouvez-vous fournir un peu plus d'informations?
iSkore
@iSkore Je ne sais pas comment le décrire mieux que je ne l'ai déjà fait. La procédure est la suivante: 1. Poussez la nouvelle version de l'image Docker dans le registre. 2. Déployez une nouvelle version d'image sur ECS. La question est de savoir comment mettre en œuvre ce dernier.
aknuds1
ce n'est pas non plus facile ou évident avec EKS .. comment le F est la tâche la plus courante d'utilisation d'un cluster, de déploiement d'une nouvelle image, si obscure dans la documentation?

Réponses:

89

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
Dima
la source
1
Je pense que pour que cela fonctionne, vous devez vous assurer qu'il y a suffisamment de ressources sur vos instances ECS pour déployer une tâche supplémentaire de la même taille. Je suppose qu'AWS essaie essentiellement d'effectuer un échange à chaud, en attendant qu'une nouvelle instance de tâche soit pré-démarrée, avant de mettre fin à l'ancienne. Il ne cesse d'ajouter des entrées "déploiements" avec 0 instances en cours d'exécution, si vous ne le faites pas.
Alex Fedulov
3
@AlexFedulov, oui, je pense que vous avez raison. Afin de ne pas entraîner de temps d'arrêt lors de la création d'un nouveau déploiement, vous pouvez 1) Provisionner suffisamment d'instances pour déployer la nouvelle version avec l'ancienne version. Ceci peut être réalisé avec l'autoscaling. 2) Utilisez le type de déploiement Fargate. Vous pouvez éviter d'allouer des ressources supplémentaires en définissant le paramètre «minimum sain pour cent» du service sur 0 pour permettre à ECS de supprimer votre ancien service avant de déployer le nouveau. Cela entraînera cependant des temps d'arrêt.
Dima
3
Options inconnues: --force-new-deployment
user4674453
1
Options inconnues: --force-new-deployment: upgrade awscli
Kyle Parisi
1
J'ai essayé cette commande, elle ne met pas à jour le conteneur avec une nouvelle image, elle fait tourner un autre conteneur avec la même ancienne image. Donc, je finis par avoir deux conteneurs en cours d'exécution même si en service j'ai un nombre souhaité spécifique = 1
maths
61

Chaque fois que vous démarrez une tâche (via les appels d'API StartTasket RunTaskou qui est lancée automatiquement dans le cadre d'un service), l'agent ECS exécutera l'une docker pulldes opérations que imagevous 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_BEHAVIORvariable 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 defaultest 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 alwaysest 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 onceest 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-cachedest 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.

Samuel Karp
la source
4
Êtes-vous sûr? J'ai vu des cas où d'anciennes images de docker sont exécutées même après avoir poussé une nouvelle image vers Dockerhub (en utilisant le même nom de balise). Je suppose que je devrais peut-être simplement modifier le nom de la balise chaque fois qu'une nouvelle image est créée. Cependant, cela a été assez rare dans mon expérience, alors c'était peut-être juste des problèmes de réseau momentanés. (Je suis conscient que vous travaillez sur ECS, donc vous êtes la meilleure personne pour répondre, mais ce n'est pas exactement ce que j'ai vécu. Excusez-moi si cela semble impoli, pas mon intention!)
Ibrahim
1
Oui, le comportement actuel est qu'il tentera de tirer à chaque fois. Si l'extraction échoue (problèmes de réseau, manque d'autorisations, etc.), il tentera d'utiliser une image mise en cache. Vous pouvez trouver plus de détails dans les fichiers journaux de l'agent qui se trouvent généralement au format /var/log/ecs.
Samuel Karp
26

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:

  1. Accédez aux définitions de tâches
  2. Sélectionnez la bonne tâche
  3. Choisissez créer une nouvelle révision
  4. Si vous extrayez déjà la dernière version de l'image du conteneur avec quelque chose comme la balise: latest, cliquez simplement sur Créer. Sinon, mettez à jour le numéro de version de l'image du conteneur, puis cliquez sur Créer.
  5. Développer les actions
  6. Choisissez le service de mise à jour (deux fois)
  7. Attendez ensuite que le service soit redémarré

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.

Neal
la source
3
Cela fonctionne, mais vous devrez peut-être modifier les valeurs min / max de votre service. Si vous n'avez qu'une seule instance EC2, vous devez définir le pourcentage de santé minimum sur zéro, sinon cela ne tuera jamais la tâche (rendant votre service temporairement hors ligne) afin de déployer le conteneur mis à jour.
Malvineous
3
@Malvineous Bon point! Dans la section de configuration ECS du didacticiel , je décris exactement cela. Voici la configuration recommandée à partir de cette section: Nombre de tâches - 1, Pourcentage de santé minimum - 0, Pourcentage maximum - 200.
Neal
@Neal J'ai essayé votre approche comme indiqué ici ... toujours pas de joie
Hafiz
@Hafiz Si ​​vous avez besoin d'aide pour comprendre cela, vous devez décrire jusqu'où vous êtes arrivé et quelle erreur vous avez rencontrée.
Neal
Cela ne fonctionne que pour les services, pas pour les tâches sans services.
zaitsman
9

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.

Z.Wei
la source
Existe-t-il un moyen de le faire sans avoir à changer l'adresse IP? Dans le mien, quand j'ai exécuté cela, cela a fonctionné mais cela a changé l'adresse IP privée que j'utilisais
Migdotcom
@Migdotcom J'ai eu un problème similaire lorsque j'ai besoin d'un proxy NLB. En bref, le seul moyen de conserver une adresse IP d'instance EC2 identique est d'utiliser des adresses IP élastiques ou d'utiliser une approche différente. Je ne connais pas votre cas d'utilisation, mais la liaison de Global Accelerator à l'ALB lié à ECS m'a fourni des adresses IP statiques, ce qui a résolu mon cas d'utilisation. Si vous souhaitez connaître les adresses IP internes dynamiques, vous devrez interroger l'ALB avec un lambda. C'était beaucoup d'efforts. Lien ci-dessous: aws.amazon.com/blogs/networking-and-content-delivery/…
Marcus
aws ecs update-service --cluster <nom du cluster> --service <nom du service> --force-new-deployment a fonctionné pour moi!
gvasquez
3

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.minimumHealthyPercentvaleur afin qu'ECS puisse voler une instance sur laquelle déployer la définition de tâche mise à jour.

Mon algorithme est comme ceci:

  1. Balise les images Docker correspondant aux conteneurs dans la définition de tâche avec la révision Git.
  2. Poussez les balises d'image Docker vers les registres correspondants.
  3. Désenregistrez les anciennes définitions de tâches dans la famille de définitions de tâches.
  4. Enregistrez une nouvelle définition de tâche, faisant désormais référence aux images Docker étiquetées avec les révisions Git actuelles.
  5. Mettre à jour le service pour utiliser la nouvelle définition de tâche.

Mon code collé ci-dessous:

deploy-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
aknuds1
la source
@Andris Merci, corrigé.
aknuds1
5
C'est exagéré. Devrait être possible de déployer via terraform ou juste une seule ligne ecs-cli.
holms
@holms J'utilise Terraform pour mettre à jour l'image de la tâche ECS. C'est aussi exagéré que le code python ci-dessus. Les étapes requises sont aussi compliquées.
Jari Turkia
3

AWS CodePipeline.

Vous pouvez définir ECR comme source et ECS comme cible de déploiement.

Gars
la source
2
pouvez-vous créer un lien vers une documentation à ce sujet?
BenDog
2

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.

Abhishek Sinha
la source
J'ai remarqué que les paramètres sont des paramètres de terraforme; Toutes les idées pour obtenir la même chose pour CloudFormation: j'ai mon AutoScalingGroup MinSize: 0 et MaxSize: 1; que faut-il définir d'autre?
Wayne
1

La suite a fonctionné pour moi au cas où la balise d'image du docker serait la même:

  1. Accédez au cluster et au service.
  2. Sélectionnez le service et cliquez sur mettre à jour.
  3. Définissez le nombre de tâches sur 0 et mettez à jour.
  4. Une fois le déploiement terminé, redimensionnez le nombre de tâches à 1.
SaiNageswar S
la source
0

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.

mpechner
la source
Je pense que la révision de la définition de tâche n'est requise que lorsque vous mettez à jour la configuration de définition de tâche réelle. dans ce cas, si vous utilisez une image avec une balise la plus récente, il n'est pas nécessaire de modifier la configuration? Bien sûr, avoir un identifiant de validation comme balise est bien, et avoir une révision de définition de tâche distincte également pour que vous puissiez revenir en arrière, mais votre CI verra toutes les informations d'identification que vous utilisez pour le conteneur, ce qui n'est pas la façon dont je veux implémenter les choses.
holms
0

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.

Avijeet
la source