Wie stelle ich aktualisierte Docker-Images für Amazon ECS-Aufgaben bereit?


110

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?


Ich würde empfehlen, eine automatisierte / geplante Lambda-Funktion auszuführen. Auf diese Weise befindet es sich außerhalb der Instanz. Hast du das versucht? Sie können SWF auch verwenden, um Schritte gleichzeitig
auszuführen

Ich muss es nicht automatisieren @iSkore. Ich möchte irgendwann ein Skript dafür schreiben, wähle aber selbst, wann ich es ausführen möchte.
aknuds1

Ahh gotcha. War mir da nicht sicher. Können Sie uns etwas mehr Informationen geben?
iSkore

@iSkore Ich weiß nicht, wie ich es besser beschreiben soll als ich es bereits getan habe. Vorgehensweise ist: 1. Neue Version des Docker-Images in die Registrierung übertragen. 2. Stellen Sie eine neue Image-Version für ECS bereit. Die Frage ist, wie letzteres umgesetzt werden kann.
aknuds1

Dies ist auch bei EKS nicht einfach oder offensichtlich. Wie ist das F die häufigste Aufgabe bei der Verwendung eines Clusters und der Bereitstellung eines neuen Images, das in der Dokumentation so undurchsichtig ist?

Antworten:


88

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

1
Ich denke, damit dies funktioniert, müssen Sie sicherstellen, dass auf Ihren ECS-Instanzen genügend Ressourcen vorhanden sind, um eine zusätzliche Aufgabe derselben Größe bereitzustellen. Ich gehe davon aus, dass AWS versucht, im Wesentlichen einen Hotswap durchzuführen und darauf wartet, dass eine neue Taskinstanz vor dem Start gestartet wird, bevor die alte beendet wird. Wenn Sie dies nicht tun, werden weiterhin "Bereitstellungen" -Einträge mit 0 laufenden Instanzen hinzugefügt.
Alex Fedulov

3
@ AlexFedulov, yep, ich denke du bist richtig. Um beim Erstellen einer neuen Bereitstellung keine Ausfallzeiten zu verursachen, können Sie entweder 1) genügend Instanzen bereitstellen, um die neue Version neben der alten Version bereitzustellen. Dies kann durch automatische Skalierung erreicht werden. 2) Verwenden Sie den Bereitstellungstyp Fargate. Sie können die Zuweisung zusätzlicher Ressourcen vermeiden, indem Sie den Parameter "Minimum Health Prozent" des Dienstes auf 0 setzen, damit ECS Ihren alten Dienst entfernen kann, bevor der neue bereitgestellt wird. Dies führt jedoch zu Ausfallzeiten.
Dima

3
Unbekannte Optionen: - Force-New-Deployment
Benutzer4674453

1
Unbekannte Optionen: - Force-New-Deployment: Upgrade awscli
Kyle Parisi

1
Ich habe diesen Befehl ausprobiert. Er aktualisiert den Container nicht mit einem neuen Image. Er dreht einen anderen Container mit demselben alten Image. Am Ende laufen also zwei Container, obwohl ich im Dienst die gewünschte Anzahl = 1 angegeben habe
Mathe

61

Jedes Mal, wenn Sie eine Aufgabe starten (entweder über die StartTaskund RunTaskAPI-Aufrufe oder automatisch als Teil eines Dienstes gestartet), führt der ECS-Agent eine docker pullder imagein 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_BEHAVIORauf 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 defaultangegeben, wird das Bild aus der Ferne gezogen. Wenn das Abrufen des Images fehlschlägt, verwendet der Container das zwischengespeicherte Image der Instanz.

  • Wenn alwaysangegeben, 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 onceangegeben, 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-cachedangegeben, 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.


4
Bist du sicher? Ich habe Fälle gesehen, in denen alte Docker-Images ausgeführt werden, selbst nachdem ich ein neues Image an Dockerhub gesendet habe (unter Verwendung des gleichen Tag-Namens). Ich denke, vielleicht sollte ich den Tag-Namen jedes Mal erhöhen, wenn ein neues Bild erstellt wird. Dies war jedoch meiner Erfahrung nach ziemlich selten, so dass es sich möglicherweise nur um vorübergehende Netzwerkprobleme handelte. (Ich bin mir bewusst, dass Sie an ECS arbeiten, also sind Sie die beste Person, um dies zu beantworten, aber das ist nicht genau das, was ich erlebt habe. Entschuldigung, wenn dies als unhöflich herauskommt, nicht meine Absicht!)
Ibrahim

1
Ja, das aktuelle Verhalten ist, dass jedes Mal ein Pull versucht wird. Wenn der Pull fehlschlägt (Netzwerkprobleme, fehlende Berechtigungen usw.), wird versucht, ein zwischengespeichertes Image zu verwenden. Weitere Details finden Sie in den Agentenprotokolldateien, die sich normalerweise in befinden /var/log/ecs.
Samuel Karp

26

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:

  1. Navigieren Sie zu Aufgabendefinitionen
  2. Wählen Sie die richtige Aufgabe
  3. Wählen Sie Neue Revision erstellen
  4. Wenn Sie bereits die neueste Version des Container-Images mit dem folgenden Tag abrufen: Klicken Sie einfach auf Erstellen. Andernfalls aktualisieren Sie die Versionsnummer des Container-Images und klicken Sie dann auf Erstellen.
  5. Erweitern Sie Aktionen
  6. Wählen Sie Update Service (zweimal)
  7. Warten Sie dann, bis der Dienst neu gestartet wurde

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.


3
Dies funktioniert, aber Sie müssen möglicherweise Ihre Service-Min / Max-Werte ändern. Wenn Sie nur eine EC2-Instanz haben, müssen Sie den minimalen fehlerfreien Prozentsatz auf Null setzen. Andernfalls wird die Aufgabe niemals beendet (wodurch Ihr Dienst vorübergehend offline geschaltet wird), um den aktualisierten Container bereitzustellen.
Malvineous

3
@ Malvineous Guter Punkt! Im Abschnitt ECS-Setup des Tutorials beschreibe ich genau das. Hier ist die empfohlene Konfiguration aus diesem Abschnitt: Anzahl der Aufgaben - 1, Minimaler gesunder Prozentsatz - 0, Maximaler Prozentsatz - 200.
Neal

@Neal Ich habe versucht, Ihren Ansatz wie hier angegeben ... immer noch keine Freude
Hafiz

@Hafiz Wenn Sie Hilfe benötigen, um dies herauszufinden, sollten Sie beschreiben, wie weit Sie gekommen sind und welchen Fehler Sie getroffen haben.
Neal

Dies funktioniert nur für Dienste, nicht für Aufgaben ohne Dienste.
Zaitsman

8

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.


Gibt es eine Möglichkeit, dies zu tun, ohne die IP ändern zu müssen? In meinem
Fall hat

@ Migdotcom Ich hatte ein ähnliches Problem, als ich eine Proxy-NLB benötigte. Kurz gesagt, die einzige Möglichkeit, die IP einer EC2-Instanz gleich zu halten, besteht darin, entweder elastische IP-Adressen oder einen anderen Ansatz zu verwenden. Ich kenne Ihren Anwendungsfall nicht, aber die Verknüpfung von Global Accelerator mit dem ECS-verknüpften ALB hat mir statische IP-Adressen bereitgestellt. Dies hat meinen Anwendungsfall gelöst. Wenn Sie dynamische interne IPs kennen möchten, müssen Sie den ALB mit einem Lambda abfragen. Das war viel Mühe. Link unten: aws.amazon.com/blogs/networking-and-content-delivery/…
Marcus

aws ecs update-service --cluster <Clustername> --service <Dienstname> --force-new-deploying hat bei mir funktioniert!
Gvasquez

3

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.minimumHealthyPercentWert, damit ECS eine Instanz stehlen kann, für die die aktualisierte Aufgabendefinition bereitgestellt werden soll.

Mein Algorithmus ist wie folgt:

  1. Kennzeichnen Sie Docker-Images, die Containern in der Aufgabendefinition entsprechen, mit der Git-Revision.
  2. Schieben Sie die Docker-Image-Tags in die entsprechenden Register.
  3. Melden Sie alte Aufgabendefinitionen in der Aufgabendefinitionsfamilie ab.
  4. Registrieren Sie eine neue Aufgabendefinition, die sich jetzt auf Docker-Images bezieht, die mit aktuellen Git-Revisionen versehen sind.
  5. Aktualisieren Sie den Dienst, um die neue Aufgabendefinition zu verwenden.

Mein Code wurde unten eingefügt:

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

@Andris Danke, behoben.
aknuds1

5
Das ist übertrieben. Sollte über Terraform oder nur eine einzige ecs-cli-Leitung bereitgestellt werden können.
Holms

@holms Ich verwende Terraform, um das ECS-Task-Image zu aktualisieren. Das ist so übertrieben wie der obige Python-Code. Die erforderlichen Schritte sind ebenso kompliziert.
Jari Turkia

3

AWS CodePipeline.

Sie können ECR als Quelle und ECS als Ziel für die Bereitstellung festlegen.


2
Können Sie hierzu auf eine Dokumentation verweisen?
BenDog

1

Folgendes hat bei mir funktioniert, falls das Docker-Image-Tag identisch ist:

  1. Gehen Sie zu Cluster und Service.
  2. Wählen Sie Dienst und klicken Sie auf Aktualisieren.
  3. Setzen Sie die Anzahl der Aufgaben auf 0 und aktualisieren Sie sie.
  4. Skalieren Sie nach Abschluss der Bereitstellung die Anzahl der Aufgaben auf 1.

1

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.


Mir ist aufgefallen, dass die Parameter Terraform-Parameter sind. Irgendwelche Ideen, wie man dasselbe für CloudFormation erreichen kann: Ich habe meine AutoScalingGroup MinSize: 0 und MaxSize: 1; Was muss noch eingestellt werden?
Wayne

0

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.


Ich glaube, dass eine Überarbeitung der Aufgabendefinition nur erforderlich ist, wenn Sie die tatsächliche Konfiguration der Aufgabendefinition aktualisieren. In diesem Fall müssen Sie die Konfiguration nicht ändern, wenn Sie ein Bild mit dem neuesten Tag verwenden. Natürlich ist es hilfreich, eine Festschreibungs-ID als Tag zu haben und auch eine separate Überarbeitung der Aufgabendefinition zu haben, damit Sie ein Rollback durchführen können, aber dann sieht Ihr CI alle Anmeldeinformationen, die Sie für den Container verwenden, was nicht die Art ist, wie ich Dinge implementieren möchte.
Holms

0

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.


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.