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?
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?
Risposte:
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
Ogni volta che si avvia un'attività (tramite le chiamate StartTask
e RunTask
API o che viene avviata automaticamente come parte di un servizio), l'agente ECS eseguirà una docker pull
delle operazioni image
specificate 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_BEHAVIOR
variabile 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
default
viene 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-cached
viene 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.
/var/log/ecs
.
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 è:
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.
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.
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.minimumHealthyPercent
valore in modo che ECS possa rubare un'istanza per distribuire la definizione dell'attività aggiornata.
Il mio algoritmo è così:
Il mio codice incollato di seguito:
#!/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
Di seguito ha funzionato per me nel caso in cui il tag immagine docker sia lo stesso:
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.
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.
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.
I seguenti comandi hanno funzionato per me
docker build -t <repo> .
docker push <repo>
ecs-cli compose stop
ecs-cli compose start