Come si distribuiscono le immagini Docker aggiornate alle attività di Amazon ECS?


110

Qual è l'approccio giusto per fare in modo che le mie attività Amazon ECS aggiornino le loro immagini Docker, una volta che le immagini sono state aggiornate nel registro corrispondente?


Consiglierei di eseguire una funzione Lambda automatizzata / pianificata. In questo modo è fuori dall'istanza. L'hai provato? Puoi anche utilizzare SWF per eseguire passaggi alla volta
iSkore

Non ho bisogno di automatizzarlo @iSkore. Alla fine vorrei scrivere uno script per esso, ma scelgo io stesso quando eseguirlo.
aknuds1

Ahh gotcha. Non ne ero sicuro. Puoi fornire qualche informazione in più?
iSkore

@iSkore Non so come descriverlo meglio di quanto già sapessi. La procedura è: 1. Eseguire il push della nuova versione dell'immagine Docker nel registro. 2. Distribuire la nuova versione dell'immagine su ECS. La domanda è come implementare quest'ultimo.
aknuds1

questo non è facile o ovvio nemmeno con EKS .. come la F è il compito più comune di usare un cluster, distribuire una nuova immagine, così oscura nella documentazione?

Risposte:


89

Se l'attività viene eseguita in un servizio, è possibile forzare una nuova distribuzione. Ciò impone la rivalutazione della definizione dell'attività e il pull della nuova immagine del contenitore.

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

1
Penso che affinché funzioni sia necessario assicurarsi che ci siano risorse sufficienti sulle istanze ECS per distribuire un'attività aggiuntiva della stessa dimensione. Presumo che AWS cerchi essenzialmente di eseguire un hotswap, in attesa del pre-avvio di una nuova istanza di attività, prima di terminare quella vecchia. Continua ad aggiungere voci "distribuzioni" con 0 istanze in esecuzione, se non lo fai.
Alex Fedulov

3
@AlexFedulov, sì, penso che tu abbia ragione. Per non incorrere in tempi di inattività durante la creazione di una nuova distribuzione, è possibile 1) Fornire istanze sufficienti per distribuire la nuova versione insieme alla vecchia versione. Ciò può essere ottenuto con la scalabilità automatica. 2) Usa il tipo di distribuzione Fargate. È possibile evitare di allocare risorse aggiuntive impostando il parametro "percentuale minima integra" del servizio su 0 per consentire a ECS di rimuovere il vecchio servizio prima di distribuire quello nuovo. Tuttavia, ciò comporterà dei tempi di inattività.
Dima

3
Opzioni sconosciute: --force-new-deployment
user4674453

1
Opzioni sconosciute: --force-new-deployment: upgrade awscli
Kyle Parisi

1
ho provato questo comando, non aggiorna il contenitore con una nuova immagine, fa girare un altro contenitore con la stessa vecchia immagine. Quindi finisco per avere due container in esecuzione anche se in servizio ho specificato il conteggio desiderato = 1
matematica

61

Ogni volta che si avvia un'attività (tramite le chiamate StartTaske RunTaskAPI o che viene avviata automaticamente come parte di un servizio), l'agente ECS eseguirà una docker pulldelle operazioni imagespecificate nella definizione dell'attività . Se utilizzi lo stesso nome immagine (tag compreso) ogni volta che esegui il push nel registro, dovresti essere in grado di eseguire la nuova immagine eseguendo una nuova attività. Si noti che se Docker non riesce a raggiungere il registro per qualsiasi motivo (ad esempio, problemi di rete o problemi di autenticazione), l'agente ECS tenterà di utilizzare un'immagine memorizzata nella cache; se vuoi evitare che le immagini memorizzate nella cache vengano utilizzate quando aggiorni la tua immagine, ti consigliamo di inserire ogni volta un tag diverso nel tuo registro e aggiornare la definizione dell'attività di conseguenza prima di eseguire la nuova attività.

Aggiornamento: questo comportamento può ora essere regolato tramite la ECS_IMAGE_PULL_BEHAVIORvariabile di ambiente impostata sull'agente ECS. Vedere la documentazione per i dettagli. Al momento della scrittura, sono supportate le seguenti impostazioni:

Il comportamento utilizzato per personalizzare il processo dell'immagine pull per le istanze del contenitore. Di seguito vengono descritti i comportamenti opzionali:

  • Se defaultviene specificato, l'immagine viene estratta in remoto. Se il pull dell'immagine non riesce, il contenitore utilizza l'immagine memorizzata nella cache nell'istanza.

  • Se alwaysè specificato, l'immagine viene sempre estratta in remoto. Se il pull dell'immagine non riesce, l'attività non riesce. Questa opzione garantisce che venga sempre estratta l'ultima versione dell'immagine. Tutte le immagini memorizzate nella cache vengono ignorate e sono soggette al processo di pulizia automatica delle immagini.

  • Se onceè specificato, l'immagine viene estratta in remoto solo se non è stata estratta da un'attività precedente sulla stessa istanza del contenitore o se l'immagine memorizzata nella cache è stata rimossa dal processo di pulizia automatica dell'immagine. In caso contrario, viene utilizzata l'immagine memorizzata nella cache sull'istanza. Ciò garantisce che non vengano tentati pull di immagini non necessari.

  • Se prefer-cachedviene specificato, l'immagine viene estratta in remoto se non è presente alcuna immagine memorizzata nella cache. In caso contrario, viene utilizzata l'immagine memorizzata nella cache sull'istanza. La pulizia automatica dell'immagine è disabilitata per il contenitore per garantire che l'immagine memorizzata nella cache non venga rimossa.


4
Sei sicuro? Ho visto casi in cui le vecchie immagini Docker vengono eseguite anche dopo aver inviato una nuova immagine a Dockerhub (utilizzando lo stesso nome di tag). Immagino che forse dovrei semplicemente cambiare il nome del tag ogni volta che viene creata una nuova immagine. Tuttavia, questo è stato piuttosto raro nella mia esperienza, quindi forse si trattava solo di problemi di rete momentanei. (Sono consapevole che lavori su ECS, quindi sei la persona migliore per rispondere, ma questo non è esattamente quello che ho sperimentato. Mi scuso se risulta scortese, non è mia intenzione!)
Ibrahim

1
Sì, il comportamento corrente è che tenterà ogni volta un pull. Se il pull non riesce (problemi di rete, mancanza di autorizzazioni, ecc.), Tenterà di utilizzare un'immagine memorizzata nella cache. È possibile trovare ulteriori dettagli nei file di registro dell'agente che di solito si trovano in /var/log/ecs.
Samuel Karp

26

La registrazione di una nuova definizione di attività e l'aggiornamento del servizio per utilizzare la nuova definizione di attività è l'approccio consigliato da AWS. Il modo più semplice per farlo è:

  1. Vai a Definizioni attività
  2. Seleziona l'attività corretta
  3. Scegli crea nuova revisione
  4. Se stai già estraendo l'ultima versione dell'immagine del contenitore con qualcosa come il tag: latest, fai semplicemente clic su Crea. In caso contrario, aggiorna il numero di versione dell'immagine del contenitore, quindi fai clic su Crea.
  5. Espandi Azioni
  6. Scegli il servizio di aggiornamento (due volte)
  7. Quindi attendere il riavvio del servizio

Questo tutorial contiene maggiori dettagli e descrive come i passaggi precedenti si inseriscono in un processo di sviluppo del prodotto end-to-end.

Divulgazione completa: questo tutorial presenta contenitori di Bitnami e io lavoro per Bitnami. Tuttavia i pensieri espressi qui sono miei e non l'opinione di Bitnami.


3
Funziona, ma potrebbe essere necessario modificare i valori min / max del servizio. Se hai solo un'istanza EC2, devi impostare la percentuale minima di integrità su zero, altrimenti non interromperà mai l'attività (rendendo il servizio temporaneamente offline) per distribuire il contenitore aggiornato.
Malvineous

3
@ Malvineous Buon punto! Nella sezione di configurazione ECS del tutorial , descrivo esattamente questo. Ecco la configurazione consigliata da quella sezione: Numero di attività - 1, Percentuale minima di integrità - 0, Percentuale massima - 200.
Neal

@Neal Ho provato il tuo approccio come indicato qui ... ancora nessuna gioia
Hafiz

@Hafiz Se hai bisogno di aiuto per capirlo, dovresti descrivere quanto sei andato lontano e quale errore hai colpito.
Neal,

Funziona solo per i servizi, non per le attività senza servizi.
zaitsman

9

Ci sono due modi per farlo.

Innanzitutto, utilizza AWS CodeDeploy. È possibile configurare le sezioni di distribuzione Blu / Verde nella definizione del servizio ECS. Ciò include un CodeDeployRoleForECS, un altro TargetGroup per switch e un Listener di test (facoltativo). AWS ECS creerà l'applicazione CodeDeploy e il gruppo di distribuzione e collegherà queste risorse CodeDeploy al tuo cluster / servizio ECS e ai tuoi ELB / TargetGroup per te. Quindi è possibile utilizzare CodeDeploy per avviare una distribuzione, in cui è necessario immettere un AppSpec che specifica l'utilizzo di quale attività / contenitore per aggiornare quale servizio. Qui è dove specifichi la tua nuova attività / contenitore. Quindi, vedrai che nuove istanze vengono avviate nel nuovo TargetGroup e il vecchio TargetGroup viene disconnesso dall'ELB, e presto le vecchie istanze registrate nel vecchio TargetGroup verranno terminate.

Sembra molto complicato. In realtà, poiché / se hai abilitato il ridimensionamento automatico sul tuo servizio ECS, un modo semplice per farlo è semplicemente forzare una nuova distribuzione usando console o cli, come un gentiluomo qui ha sottolineato:

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

In questo modo puoi ancora utilizzare il tipo di distribuzione "aggiornamento in sequenza" e ECS semplicemente avvia nuove istanze e svuota quelle vecchie senza tempi di inattività del servizio se tutto va bene. Il lato negativo è che si perde il controllo accurato sulla distribuzione e non è possibile ripristinare la versione precedente se si verifica un errore e questo interromperà il servizio in corso. Ma questo è un modo davvero semplice per andare.

A proposito, non dimenticare di impostare i numeri corretti per Percentuale minima sana e Percentuale massima, come 100 e 200.


C'è un modo per farlo senza dover cambiare l'IP? Nel mio quando l'ho eseguito ha funzionato ma ha cambiato l'IP privato che stavo eseguendo
Migdotcom il

@Migdotcom Ho avuto un problema simile quando avevo bisogno di un proxy NLB. In breve, l'unico modo per mantenere lo stesso IP di un'istanza EC2 è utilizzare indirizzi IP elastici o utilizzare un approccio diverso. Non conosco il tuo caso d'uso, ma il collegamento di Global Accelerator all'ALB collegato a ECS mi ha fornito indirizzi IP statici, questo ha risolto il mio caso d'uso. Se vuoi conoscere gli IP interni dinamici, dovrai interrogare l'ALB con un lambda. Questo è stato un grande sforzo. Link di seguito: aws.amazon.com/blogs/networking-and-content-delivery/…
Marcus

aws ecs update-service --cluster <nome cluster> --service <nome servizio> --force-new-deployment ha funzionato per me!
gvasquez

3

Ho creato uno script per la distribuzione di immagini Docker aggiornate a un servizio di staging su ECS, in modo che la definizione dell'attività corrispondente faccia riferimento alle versioni correnti delle immagini Docker. Non so per certo se sto seguendo le migliori pratiche, quindi un feedback sarebbe il benvenuto.

Affinché lo script funzioni, è necessaria un'istanza ECS di riserva o un deploymentConfiguration.minimumHealthyPercentvalore in modo che ECS possa rubare un'istanza per distribuire la definizione dell'attività aggiornata.

Il mio algoritmo è così:

  1. Contrassegna le immagini Docker corrispondenti ai contenitori nella definizione dell'attività con la revisione Git.
  2. Esegui il push dei tag immagine Docker nei registri corrispondenti.
  3. Annullare la registrazione delle vecchie definizioni di attività nella famiglia di definizioni di attività.
  4. Registra la nuova definizione dell'attività, che ora fa riferimento alle immagini Docker contrassegnate con le revisioni Git correnti.
  5. Aggiorna il servizio per utilizzare la nuova definizione dell'attività.

Il mio codice incollato di seguito:

Installare-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

@Andris Thanks, fixed.
aknuds1

5
Questo è eccessivo. Dovrebbe essere possibile implementare tramite terraform o solo una singola linea ecs-cli.
holms

@holms Sto usando Terraform per aggiornare l'immagine dell'attività ECS. È eccessivo come il codice python sopra. I passaggi richiesti sono altrettanto complicati.
Jari Turkia

3

AWS CodePipeline.

È possibile impostare ECR come origine e ECS come destinazione in cui eseguire la distribuzione.


2
puoi collegarti a qualsiasi documentazione per questo?
BenDog

1

Di seguito ha funzionato per me nel caso in cui il tag immagine docker sia lo stesso:

  1. Vai a cluster e servizio.
  2. Seleziona il servizio e fai clic su Aggiorna.
  3. Imposta il numero di attività su 0 e aggiorna.
  4. Al termine della distribuzione, ridimensionare il numero di attività a 1.

1

Ho riscontrato lo stesso problema. Dopo aver trascorso ore, hanno concluso questi passaggi semplificati per la distribuzione automatica dell'immagine aggiornata:

1.Modifiche alla definizione dell'attività ECS: per una migliore comprensione, supponiamo di aver creato una definizione dell'attività con i dettagli di seguito (nota: questi numeri cambierebbero di conseguenza in base alla definizione dell'attività):

launch_type = EC2

desired_count = 1

Quindi è necessario apportare le seguenti modifiche:

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. Etichetta la tua immagine come < nome-immagine>: più recente . L'ultima chiave si occupa di essere trascinata dal rispettivo compito 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. Spingere l'immagine in ECR

sudo docker push  <your_account_id>.dkr.ecr.us-east-1.amazonaws.com/<your-image-name>:latest

4.applica la distribuzione forzata

sudo aws ecs update-service --cluster <your-cluster-name> --service <your-service-name> --force-new-deployment --region us-east-1

Nota: ho scritto tutti i comandi assumendo che la regione sia us-east-1 . Basta sostituirlo con la rispettiva regione durante l'implementazione.


Ho notato che i parametri sono parametri terraform; Qualche idea su come ottenere lo stesso risultato per CloudFormation: ho il mio AutoScalingGroup MinSize: 0 e MaxSize: 1; cos'altro deve essere impostato?
Wayne

0

Utilizzando AWS cli ho provato il servizio di aggiornamento aws ecs come suggerito sopra. Impossibile prelevare l'ultima finestra mobile da ECR. Alla fine, ho rieseguito il mio playbook Ansible che ha creato il cluster ECS. La versione della definizione dell'attività viene ignorata quando viene eseguito ecs_taskdefinition. Allora va tutto bene. La nuova immagine docker viene ripresa.

Sinceramente non sono sicuro che la modifica della versione dell'attività imponga la ridistribuzione o se il playbook che utilizza ecs_service causa il ricaricamento dell'attività.

Se qualcuno è interessato, otterrò il permesso di pubblicare una versione disinfettata del mio playbook.


Credo che la revisione della definizione dell'attività sia richiesta solo quando si aggiorna la configurazione della definizione dell'attività effettiva. in questo caso se stai usando un'immagine con un tag più recente, non c'è bisogno di modificare config? Ovviamente avere un commit id come tag è bello e avere anche una revisione della definizione dell'attività separata in modo da poter eseguire il rollback, ma poi il tuo CI vedrà tutte le credenziali che stai usando per il contenitore che non è il modo in cui voglio implementare le cose.
Holms

0

beh, sto anche cercando di trovare un modo automatizzato per farlo, ovvero spingere le modifiche a ECR e quindi l'ultimo tag dovrebbe essere ritirato dal servizio. Bene, puoi farlo manualmente interrompendo l'attività per il tuo servizio dal cluster. Le nuove attività estrarranno i contenitori ECR aggiornati.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.