Was ist der richtige Ansatz, um meine Amazon ECS- Aufgaben dazu zu bringen, ihre Docker-Images zu aktualisieren, nachdem diese Images in der entsprechenden Registrierung aktualisiert wurden?
Was ist der richtige Ansatz, um meine Amazon ECS- Aufgaben dazu zu bringen, ihre Docker-Images zu aktualisieren, nachdem diese Images in der entsprechenden Registrierung aktualisiert wurden?
Antworten:
Wenn Ihre Aufgabe unter einem Dienst ausgeführt wird, können Sie eine neue Bereitstellung erzwingen. Dadurch wird die Aufgabendefinition neu bewertet und das neue Container-Image abgerufen.
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
Jedes Mal, wenn Sie eine Aufgabe starten (entweder über die StartTask
und RunTask
API-Aufrufe oder automatisch als Teil eines Dienstes gestartet), führt der ECS-Agent eine docker pull
der image
in Ihrer Aufgabendefinition angegebenen Aufgaben aus. Wenn Sie bei jedem Push in Ihre Registrierung denselben Image-Namen (einschließlich Tag) verwenden, sollte das neue Image durch Ausführen einer neuen Aufgabe ausgeführt werden können. Beachten Sie, dass der ECS-Agent versucht, ein zwischengespeichertes Image zu verwenden, wenn Docker aus irgendeinem Grund nicht auf die Registrierung zugreifen kann (z. B. Netzwerkprobleme oder Authentifizierungsprobleme). Wenn Sie vermeiden möchten, dass zwischengespeicherte Bilder beim Aktualisieren Ihres Bildes verwendet werden, sollten Sie jedes Mal ein anderes Tag in Ihre Registrierung verschieben und Ihre Aufgabendefinition entsprechend aktualisieren, bevor Sie die neue Aufgabe ausführen.
Update: Dieses Verhalten kann jetzt über die ECS_IMAGE_PULL_BEHAVIOR
auf dem ECS-Agenten festgelegte Umgebungsvariable optimiert werden. Einzelheiten finden Sie in der Dokumentation . Zum Zeitpunkt des Schreibens werden die folgenden Einstellungen unterstützt:
Das Verhalten, mit dem der Pull-Image-Prozess für Ihre Containerinstanzen angepasst wird. Im Folgenden werden die optionalen Verhaltensweisen beschrieben:
Wenn
default
angegeben, wird das Bild aus der Ferne gezogen. Wenn das Abrufen des Images fehlschlägt, verwendet der Container das zwischengespeicherte Image der Instanz.Wenn
always
angegeben, wird das Bild immer aus der Ferne gezogen. Wenn das Abrufen des Bildes fehlschlägt, schlägt die Aufgabe fehl. Diese Option stellt sicher, dass immer die neueste Version des Bildes abgerufen wird. Alle zwischengespeicherten Bilder werden ignoriert und unterliegen der automatischen Bildbereinigung.Wenn
once
angegeben, wird das Image nur dann remote abgerufen, wenn es nicht von einer vorherigen Aufgabe in derselben Containerinstanz abgerufen wurde oder wenn das zwischengespeicherte Image durch den automatischen Image-Bereinigungsprozess entfernt wurde. Andernfalls wird das zwischengespeicherte Image der Instanz verwendet. Dies stellt sicher, dass keine unnötigen Bildzüge versucht werden.Wenn
prefer-cached
angegeben, wird das Bild remote abgerufen, wenn kein zwischengespeichertes Bild vorhanden ist. Andernfalls wird das zwischengespeicherte Image der Instanz verwendet. Die automatische Bildbereinigung ist für den Container deaktiviert, um sicherzustellen, dass das zwischengespeicherte Bild nicht entfernt wird.
/var/log/ecs
.
Das Registrieren einer neuen Aufgabendefinition und das Aktualisieren des Dienstes zur Verwendung der neuen Aufgabendefinition ist der von AWS empfohlene Ansatz. Der einfachste Weg, dies zu tun, ist:
Dieses Tutorial enthält weitere Details und beschreibt, wie die oben genannten Schritte in einen durchgängigen Produktentwicklungsprozess passen.
Vollständige Offenlegung: Dieses Tutorial enthält Container von Bitnami und ich arbeite für Bitnami. Die hier geäußerten Gedanken sind jedoch meine eigenen und nicht die Meinung von Bitnami.
Es gibt zwei Möglichkeiten, dies zu tun.
Verwenden Sie zunächst AWS CodeDeploy. Sie können Blau / Grün-Bereitstellungsabschnitte in der ECS-Dienstdefinition konfigurieren. Dies umfasst eine CodeDeployRoleForECS, eine weitere TargetGroup für den Switch und einen Test-Listener (optional). AWS ECS erstellt eine CodeDeploy-Anwendung und eine Bereitstellungsgruppe und verknüpft diese CodeDeploy-Ressourcen für Sie mit Ihrem ECS-Cluster / Service und Ihren ELB / TargetGroups. Anschließend können Sie mit CodeDeploy eine Bereitstellung initiieren, in der Sie eine AppSpec eingeben müssen, die angibt, mit welcher Aufgabe / welchem Container welcher Dienst aktualisiert werden soll. Hier geben Sie Ihre neue Aufgabe / Ihren neuen Container an. Dann werden Sie sehen, dass neue Instanzen in der neuen Zielgruppe hochgefahren werden und die alte Zielgruppe von der ELB getrennt wird und bald die alten Instanzen, die bei der alten Zielgruppe registriert sind, beendet werden.
Das klingt sehr kompliziert. Da / wenn Sie die automatische Skalierung für Ihren ECS-Dienst aktiviert haben, besteht eine einfache Möglichkeit darin, einfach eine neue Bereitstellung mithilfe von Konsole oder CLI zu erzwingen, wie ein Gentleman hier ausgeführt hat:
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
Auf diese Weise können Sie weiterhin den Bereitstellungstyp "Rollendes Update" verwenden, und ECS startet einfach neue Instanzen und entleert die alten ohne Ausfallzeiten Ihres Dienstes, wenn alles in Ordnung ist. Die schlechte Seite ist, dass Sie die Kontrolle über die Bereitstellung verlieren und bei einem Fehler nicht zur vorherigen Version zurückkehren können, wodurch der laufende Dienst unterbrochen wird. Aber das ist ein wirklich einfacher Weg.
Übrigens, vergessen Sie nicht, die richtigen Zahlen für Minimum Healthy Percent und Maximum Prozent wie 100 und 200 festzulegen.
Ich habe ein Skript zum Bereitstellen aktualisierter Docker-Images für einen Staging-Dienst in ECS erstellt, sodass sich die entsprechende Aufgabendefinition auf die aktuellen Versionen der Docker-Images bezieht. Ich weiß nicht genau, ob ich Best Practices befolge, daher wäre Feedback willkommen.
Damit das Skript funktioniert, benötigen Sie entweder eine Ersatz-ECS-Instanz oder einen deploymentConfiguration.minimumHealthyPercent
Wert, damit ECS eine Instanz stehlen kann, für die die aktualisierte Aufgabendefinition bereitgestellt werden soll.
Mein Algorithmus ist wie folgt:
Mein Code wurde unten eingefügt:
#!/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
Folgendes hat bei mir funktioniert, falls das Docker-Image-Tag identisch ist:
Bin auf dasselbe Problem gestoßen. Nachdem Sie Stunden verbracht haben, haben Sie diese vereinfachten Schritte für die automatisierte Bereitstellung eines aktualisierten Images abgeschlossen:
1. Änderungen der ECS-Aufgabendefinition: Nehmen wir zum besseren Verständnis an, Sie haben eine Aufgabendefinition mit den folgenden Details erstellt (Hinweis: Diese Zahlen würden sich entsprechend Ihrer Aufgabendefinition entsprechend ändern):
launch_type = EC2
desired_count = 1
Dann müssen Sie folgende Änderungen vornehmen:
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.Taggen Sie Ihr Bild als < Ihr- Bildname >: spätestens . Der neueste Schlüssel sorgt dafür, dass er von der jeweiligen ECS-Aufgabe gezogen wird.
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.Push auf das Bild zu ECR
sudo docker push <your_account_id>.dkr.ecr.us-east-1.amazonaws.com/<your-image-name>:latest
4. Anwendung erzwingen
sudo aws ecs update-service --cluster <your-cluster-name> --service <your-service-name> --force-new-deployment --region us-east-1
Hinweis: Ich habe alle Befehle geschrieben, wobei davon ausgegangen wird, dass die Region us-east-1 ist . Ersetzen Sie es einfach während der Implementierung durch Ihre jeweilige Region.
Mit AWS cli habe ich aws ecs update-service wie oben vorgeschlagen ausprobiert. Habe nicht den neuesten Docker von ECR abgeholt. Am Ende führe ich mein Ansible-Playbook erneut aus, mit dem der ECS-Cluster erstellt wurde. Die Version der Aufgabendefinition wird beim Ausführen von ecs_taskdefinition gestoßen. Dann ist alles gut. Das neue Docker-Image wird aufgenommen.
Ehrlich gesagt nicht sicher, ob die Änderung der Aufgabenversion die erneute Bereitstellung erzwingt oder ob das Playbook, das den ecs_service verwendet, das Neuladen der Aufgabe bewirkt.
Wenn jemand interessiert ist, erhalte ich die Erlaubnis, eine bereinigte Version meines Spielbuchs zu veröffentlichen.
Nun, ich versuche auch, einen automatisierten Weg zu finden, das heißt, die Änderungen an ECR voranzutreiben und dann sollte das neueste Tag vom Service abgeholt werden. Richtig, Sie können dies manuell tun, indem Sie die Aufgabe für Ihren Dienst in Ihrem Cluster stoppen. Neue Aufgaben ziehen die aktualisierten ECR-Container.
Die folgenden Befehle haben bei mir funktioniert
docker build -t <repo> .
docker push <repo>
ecs-cli compose stop
ecs-cli compose start