web-dev-qa-db-fra.com

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

Quelle est la bonne approche pour que mes tâches Amazon ECS mettent à jour leurs images Docker, une fois que ces images ont été mises à jour dans le registre correspondant?

71
aknuds1

J'ai créé n script pour déployer les images Docker mises à jour sur un service de transfert sur ECS, de sorte que la définition de tâche correspondante fasse référence aux versions actuelles des images Docker. Je ne sais pas si je suis les meilleures pratiques, alors vos commentaires sont les bienvenus.

Pour que le script fonctionne, vous avez besoin d'une instance de sauvegarde ECS ou d'un deploymentConfiguration.minimumHealthyPercent valeur pour qu'ECS puisse voler une instance sur laquelle déployer la définition de tâche mise à jour.

Mon algorithme est comme ça:

  1. Marquez 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 dans les registres correspondants.
  3. Annulez l'enregistrement des 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 maintenant référence aux images Docker marquées avec les révisions Git actuelles.
  5. Service de mise à jour 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
3
aknuds1

Chaque fois que vous démarrez une tâche (par le biais d'appels d'API StartTask et RunTask ou démarrée automatiquement dans le cadre d'un service), l'agent ECS exécute un docker pull du image que vous spécifiez dans votre définition de tâche. Si vous utilisez le même nom d'image (y compris la balise) à chaque poussée dans votre base de 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, problèmes de réseau ou d’authentification), l’agent ECS tentera d’utiliser une image mise en cache; Si vous souhaitez éviter que les images en cache ne soient utilisées lors de la mise à jour de votre image, vous devez insérer chaque fois une balise différente dans votre registre 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 maintenant être réglé via le ECS_IMAGE_PULL_BEHAVIOR variable d’environnement définie sur l’agent ECS. Voir la documentation pour plus de détails. Au moment de la rédaction de ce document, 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 d'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 le tirage d'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 automatique des images.

  • 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 en cache. Sinon, l'image mise en cache sur l'instance est utilisée. Le nettoyage automatique des images est désactivé pour le conteneur afin de garantir que l'image mise en cache n'est pas supprimée.

51
Samuel Karp

Si votre tâche est exécutée sous un service, vous pouvez forcer un nouveau déploiement. Cela oblige à réévaluer la définition de la tâche et à extraire la nouvelle image du conteneur.

aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
50
Dima

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 constituent l'approche recommandée par AWS. Le moyen le plus simple de le faire est de:

  1. Naviguer vers les définitions de tâches
  2. Sélectionnez la tâche correcte
  3. Choisissez créer une nouvelle révision
  4. Si vous tirez déjà la dernière version de l'image du conteneur avec quelque chose comme: la dernière balise, 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. Puis attendez que le service soit redémarré

Ce tutoriel contient plus de détails 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 les conteneurs de Bitnami et je travaille pour Bitnami. Cependant, les pensées exprimées ici sont les miennes et non l'opinion de Bitnami.

20
Neal

AWS CodePipeline.

Vous pouvez définir ECR en tant que source et ECS en tant que cible sur laquelle déployer.

1
Guy

En utilisant AWS cli, j'ai essayé le service de mise à jour aws ecs comme suggéré ci-dessus. N'a pas récupéré le dernier menu fixe d'ECR. En fin de compte, j'ai relancé mon livre de lecture Ansible qui a créé le cluster ECS. La version de la définition de la tâche est modifiée lors de l'exécution de ecs_taskdefinition. Alors tout va bien. La nouvelle image du menu fixe est sélectionnée.

En vérité, ne pas être sûr que la modification de la version de la tâche force le redéploiement ou que le livre de lecture utilisant ecs_service provoque le rechargement de la tâche.

Si quelqu'un est intéressé, j'aurai la permission de publier une version assainie de mon livre de jeu.

1
mpechner

Les commandes suivantes ont fonctionné pour moi

docker build -t <repo> . 
docker Push <repo>
ecs-cli compose stop
ecs-cli compose start
0
explorer

eh bien, j'essaie également de trouver un moyen automatisé de le faire, c’est-à-dire d’appliquer les modifications à la caisse enregistreuse électronique, puis la dernière étiquette doit être récupérée par le service. À droite, vous pouvez le faire manuellement en arrêtant la tâche de votre service à partir de votre cluster. Les nouvelles tâches vont extraire les conteneurs ECR mis à jour.

0
Avijeet