Jak wdrożyć zaktualizowane obrazy platformy Docker do zadań Amazon ECS?

110

Jakie jest właściwe podejście, aby moje zadania Amazon ECS aktualizowały obrazy Dockera, gdy te obrazy zostaną zaktualizowane w odpowiednim rejestrze?

aknuds1
źródło
Poleciłbym uruchomienie zautomatyzowanej / zaplanowanej funkcji Lambda. W ten sposób jest poza instancją. Czy próbowałeś tego? Możesz także użyć SWF, aby wykonywać kroki naraz
iSkore
Nie muszę tego automatyzować @iSkore. Chciałbym w końcu napisać dla niego skrypt, ale sam wybieram, kiedy go uruchomić.
aknuds1
Ach, rozumiem. Nie byłem tego pewien. Czy możesz podać trochę więcej informacji?
iSkore
@iSkore Nie wiem, jak to opisać lepiej niż wcześniej. Procedura: 1. Wypchnij nową wersję obrazu Docker do rejestru. 2. Wdróż nową wersję obrazu w ECS. Pytanie brzmi, jak wdrożyć to drugie.
aknuds1
nie jest to łatwe ani oczywiste w przypadku EKS-a ... w jaki sposób F jest najczęstszym zadaniem używania klastra, wdrażania nowego obrazu, tak niejasnego w dokumentacji?

Odpowiedzi:

89

Jeśli Twoje zadanie działa w ramach usługi, możesz wymusić nowe wdrożenie. Wymusza to ponowną ocenę definicji zadania i pobranie nowego obrazu kontenera.

aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
Dima
źródło
1
Myślę, że aby to zadziałało, musisz upewnić się, że w instancjach ECS jest wystarczająco dużo zasobów, aby wdrożyć dodatkowe zadanie o tej samej wielkości. Zakładam, że AWS zasadniczo próbuje wykonać hotswap, czekając na wstępne uruchomienie nowej instancji zadania, przed zakończeniem starej. Po prostu dodaje wpisy „wdrożenia” z 0 uruchomionymi instancjami, jeśli tego nie zrobisz.
Alex Fedulov
3
@AlexFedulov, tak, myślę, że masz rację. Aby nie powodować przestojów podczas tworzenia nowego wdrożenia, możesz 1) zapewnić wystarczającą liczbę instancji, aby wdrożyć nową wersję obok starej. Można to osiągnąć dzięki autoskalowaniu. 2) Użyj typu wdrożenia Fargate. Możesz uniknąć przydzielania dodatkowych zasobów, ustawiając parametr usługi „minimalny zdrowy procent” na 0, aby umożliwić ECS usunięcie starej usługi przed wdrożeniem nowej. Spowoduje to jednak pewne przestoje.
Dima,
3
Nieznane opcje: --force-new-deployment
user4674453
1
Nieznane opcje: --force-new-deployment: upgrade awscli
Kyle Parisi
1
Wypróbowałem to polecenie, nie aktualizuje kontenera o nowy obraz, obraca inny kontener z tym samym starym obrazem. W rezultacie mam dwa uruchomione kontenery, mimo że w służbie mam określoną żądaną liczbę = 1
matematyka
61

Przy każdym uruchomieniu zadania (zarówno przez StartTaski RunTaskAPI lub który jest uruchamiany automatycznie jako część a Service), Agent ECS będzie przeprowadzić docker pullz imagetobą określić w definicji zadań. Jeśli używasz tej samej nazwy obrazu (łącznie ze znacznikiem) za każdym razem, gdy wysyłasz dane do rejestru, uruchomienie nowego obrazu powinno być możliwe, uruchamiając nowe zadanie. Zwróć uwagę, że jeśli Docker nie może z jakiegokolwiek powodu dotrzeć do rejestru (np. Problemy z siecią lub problemy z uwierzytelnianiem), agent ECS spróbuje użyć buforowanego obrazu; jeśli chcesz uniknąć używania obrazów z pamięci podręcznej podczas aktualizowania obrazu, za każdym razem będziesz chciał wypchnąć inny tag do rejestru i odpowiednio zaktualizować definicję zadania przed uruchomieniem nowego zadania.

Aktualizacja: to zachowanie można teraz dostroić za pomocą ECS_IMAGE_PULL_BEHAVIORzmiennej środowiskowej ustawionej na agencie ECS. Szczegółowe informacje można znaleźć w dokumentacji . W chwili pisania tego tekstu obsługiwane są następujące ustawienia:

Zachowanie używane do dostosowywania procesu ściągania obrazu dla wystąpień kontenera. Poniżej opisano opcjonalne zachowania:

  • Jeśli defaultokreślono, obraz jest pobierany zdalnie. Jeśli pobieranie obrazu nie powiedzie się, kontener użyje obrazu z pamięci podręcznej w instancji.

  • Jeśli alwaysokreślono, obraz jest zawsze pobierany zdalnie. Jeśli pobieranie obrazu nie powiedzie się, zadanie nie powiedzie się. Ta opcja zapewnia, że ​​zawsze pobierana jest najnowsza wersja obrazu. Wszystkie obrazy w pamięci podręcznej są ignorowane i podlegają automatycznemu procesowi czyszczenia obrazu.

  • Jeśli oncejest określony, obraz jest pobierany zdalnie tylko wtedy, gdy nie został pobrany przez poprzednie zadanie w tej samej instancji kontenera lub jeśli buforowany obraz został usunięty przez automatyczny proces czyszczenia obrazu. W przeciwnym razie używany jest obraz z pamięci podręcznej w instancji. Gwarantuje to, że nie są podejmowane żadne niepotrzebne pobieranie obrazów.

  • Jeśli prefer-cachedokreślono, obraz jest pobierany zdalnie, jeśli nie ma obrazu w pamięci podręcznej. W przeciwnym razie używany jest obraz z pamięci podręcznej w instancji. Automatyczne czyszczenie obrazu jest wyłączone dla kontenera, aby zapewnić, że buforowany obraz nie zostanie usunięty.

Samuel Karp
źródło
4
Jesteś pewny? Widziałem przypadki, w których stare obrazy dockera były uruchamiane nawet po przesłaniu nowego obrazu do Dockerhub (używając tej samej nazwy tagu). Myślę, że powinienem po prostu podbijać nazwę tagu za każdym razem, gdy budowany jest nowy obraz. Jednak z mojego doświadczenia wynika, że ​​było to dość rzadkie, więc może były to tylko chwilowe problemy z siecią. (Wiem, że pracujesz nad ECS, więc jesteś najlepszą osobą, która odpowie na to pytanie, ale to nie jest dokładnie to, czego doświadczyłem. Przepraszam, jeśli to jest niegrzeczne, nie moja intencja!)
Ibrahim
1
Tak, obecne zachowanie polega na tym, że za każdym razem będzie próbował pociągnąć. Jeśli ściąganie nie powiedzie się (problemy z siecią, brak uprawnień itp.), Spróbuje użyć obrazu z pamięci podręcznej. Więcej szczegółów można znaleźć w plikach dziennika agentów, które zwykle znajdują się w /var/log/ecs.
Samuel Karp
26

Rejestracja nowej definicji zadania i aktualizacja usługi do korzystania z nowej definicji zadania to podejście zalecane przez AWS. Najłatwiej to zrobić:

  1. Przejdź do definicji zadań
  2. Wybierz właściwe zadanie
  3. Wybierz utwórz nową wersję
  4. Jeśli już pobierasz najnowszą wersję obrazu kontenera z czymś w rodzaju: najnowszy tag, po prostu kliknij Utwórz. W przeciwnym razie zaktualizuj numer wersji obrazu kontenera, a następnie kliknij Utwórz.
  5. Rozwiń działania
  6. Wybierz usługę aktualizacji (dwukrotnie)
  7. Następnie poczekaj na ponowne uruchomienie usługi

Ten samouczek zawiera więcej szczegółów i opisuje, w jaki sposób powyższe kroki wpisują się w kompleksowy proces rozwoju produktu.

Pełne ujawnienie: ten samouczek zawiera kontenery z Bitnami i ja pracuję dla Bitnami. Jednak wyrażone tutaj myśli są moimi własnymi, a nie opinią Bitnami.

Neal
źródło
3
To działa, ale może być konieczna zmiana wartości min / max usługi. Jeśli masz tylko jedną instancję EC2, musisz ustawić minimalny zdrowy procent na zero, w przeciwnym razie nigdy nie zabije zadania (zmieniając tymczasowo usługę w tryb offline), aby wdrożyć zaktualizowany kontener.
Malvineous
3
@Malvineous Dobra uwaga! W sekcji ustawień ECS tutorialu opiszę dokładnie to. Oto zalecana konfiguracja z tej sekcji: Liczba zadań - 1, Minimalny zdrowy procent - 0, Maksymalny procent - 200.
Neal
@Neal Wypróbowałem twoje podejście, jak podano tutaj ... nadal nie ma radości
Hafiz
@Hafiz Jeśli potrzebujesz pomocy, aby to rozgryźć, opisz, jak daleko zaszedłeś i jaki błąd napotkałeś.
Neal
Działa to tylko w przypadku usług, a nie zadań bez usług.
zaitsman
9

Można to zrobić na dwa sposoby.

Najpierw użyj AWS CodeDeploy. Sekcje wdrażania w kolorze niebieskim / zielonym można skonfigurować w definicji usługi ECS. Obejmuje to CodeDeployRoleForECS, inną grupę docelową dla przełącznika i odbiornik testowy (opcjonalnie). AWS ECS utworzy aplikację CodeDeploy i grupę wdrożeniową oraz połączy te zasoby CodeDeploy z Twoim klastrem / usługą ECS oraz ELB / TargetGroups. Następnie możesz użyć CodeDeploy, aby zainicjować wdrożenie, w którym musisz wprowadzić specyfikację AppSpec, która określa użycie zadania / kontenera do zaktualizowania usługi. Tutaj określasz nowe zadanie / kontener. Następnie zobaczysz, że nowe instancje są uruchamiane w nowej grupie docelowej, a stara grupa docelowa jest odłączona od ELB, a wkrótce stare instancje zarejestrowane w starej grupie docelowej zostaną zakończone.

Brzmi to bardzo skomplikowanie. Właściwie, ponieważ / jeśli włączyłeś automatyczne skalowanie w swojej usłudze ECS, prostym sposobem na to jest po prostu wymuszenie nowego wdrożenia za pomocą konsoli lub CLI, jak wskazał dżentelmen:

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

W ten sposób możesz nadal używać typu wdrażania „aktualizacja krocząca”, a ECS po prostu uruchomi nowe instancje i opróżni stare bez przestojów usługi, jeśli wszystko jest w porządku. Zła strona polega na tym, że tracisz kontrolę nad wdrożeniem i nie możesz przywrócić poprzedniej wersji, jeśli wystąpi błąd, co spowoduje przerwanie bieżącej usługi. Ale to naprawdę prosta droga.

BTW, nie zapomnij ustawić odpowiednich liczb dla Minimalnego zdrowego procentu i Maksymalnego procentu, na przykład 100 i 200.

Z.Wei
źródło
Czy można to zrobić bez konieczności zmiany adresu IP? W moim, kiedy to uruchomiłem, zadziałało, ale zmieniło prywatny adres IP, który uruchomiłem
Migdotcom
@Migdotcom Miałem podobny problem, gdy potrzebowałem proxy NLB. Krótko mówiąc, jedynym sposobem na utrzymanie takiego samego adresu IP instancji EC2 jest użycie elastycznych adresów IP lub zastosowanie innego podejścia. Nie znam twojego przypadku użycia, ale połączenie Global Accelerator z ALB połączonym z ECS zapewniło mi statyczne adresy IP. To rozwiązało mój przypadek użycia. Jeśli chcesz poznać dynamiczne wewnętrzne adresy IP, musisz zapytać ALB za pomocą lambda. To był duży wysiłek. Link poniżej: aws.amazon.com/blogs/networking-and-content-delivery/…
Marcus
aws ecs update-service --cluster <nazwa klastra> --service <nazwa usługi> --force-new-deployment działało dla mnie!
gvasquez
3

Stworzyłem skrypt do wdrażania zaktualizowanych obrazów Dockera w usłudze przejściowej w ECS, tak aby odpowiednia definicja zadania odnosiła się do bieżących wersji obrazów Dockera. Nie wiem na pewno, czy postępuję zgodnie z najlepszymi praktykami, więc opinie będą mile widziane.

Aby skrypt działał, potrzebna jest wolna instancja ECS lub deploymentConfiguration.minimumHealthyPercentwartość, dzięki której ECS może ukraść instancję i wdrożyć zaktualizowaną definicję zadania.

Mój algorytm wygląda tak:

  1. Oznacz obrazy platformy Docker odpowiadające kontenerom w definicji zadania za pomocą wersji Git.
  2. Wypchnij tagi obrazu platformy Docker do odpowiednich rejestrów.
  3. Wyrejestruj stare definicje zadań w rodzinie definicji zadań.
  4. Zarejestruj nową definicję zadania, odwołując się teraz do obrazów Docker oznaczonych bieżącymi wersjami Git.
  5. Zaktualizuj usługę, aby używała nowej definicji zadania.

Mój kod wklejony poniżej:

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
aknuds1
źródło
@Andris Dzięki, naprawione.
aknuds1
5
To przesada. Powinno być możliwe wdrożenie przez terraform lub pojedynczą linię ecs-cli.
holms
@holms Używam Terraform do aktualizacji obrazu zadania ECS. To tak samo przesada, jak powyższy kod Pythona. Wymagane kroki są równie skomplikowane.
Jari Turkia
3

AWS CodePipeline.

Możesz ustawić ECR jako źródło, a ECS jako cel do wdrożenia.

Chłopak
źródło
2
czy możesz podać link do jakiejkolwiek dokumentacji na ten temat?
BenDog,
2

Wpadłem na ten sam problem. Po spędzeniu wielu godzin zakończyłem te uproszczone kroki w celu automatycznego wdrożenia zaktualizowanego obrazu:

1. zmiany definicji zadania ECS: Dla lepszego zrozumienia załóżmy, że utworzyłeś definicję zadania z poniższymi szczegółami (uwaga: te liczby zmieniłyby się odpowiednio zgodnie z definicją zadania):

launch_type = EC2

desired_count = 1

Następnie musisz wprowadzić następujące zmiany:

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. Oznacz swój obraz jako < nazwa-twojego-obrazu>: najnowszy . Najnowszy klucz dba o to, aby zostać pociągniętym przez odpowiednie zadanie 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. Wciśnij obraz do ECR

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

4. zastosować rozmieszczenie sił

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

Uwaga: napisałem wszystkie polecenia, zakładając, że region to us-east-1 . Po prostu zastąp go odpowiednim regionem podczas wdrażania.

Abhishek Sinha
źródło
Zauważyłem, że parametry to parametry terraformy; Wszelkie pomysły, jak osiągnąć to samo dla CloudFormation: Mam moje AutoScalingGroup MinSize: 0 i MaxSize: 1; co jeszcze trzeba ustawić?
Wayne
1

Następujące działały dla mnie na wypadek, gdyby tag obrazu dockera był taki sam:

  1. Przejdź do klastra i usługi.
  2. Wybierz usługę i kliknij Aktualizuj.
  3. Ustaw liczbę zadań na 0 i zaktualizuj.
  4. Po zakończeniu wdrażania przeskaluj liczbę zadań do 1.
SaiNageswar S
źródło
0

Używając AWS cli, wypróbowałem usługę aktualizacji aws ecs, jak zasugerowano powyżej. Nie odebrałem najnowszego dockera z ECR. W końcu ponownie uruchomiłem Playbook Ansible, który utworzył klaster ECS. Wersja definicji zadania jest obciążana po uruchomieniu ecs_taskdefinition. Wtedy wszystko jest w porządku. Zostanie pobrany nowy obraz dockera.

Naprawdę nie jestem pewien, czy zmiana wersji zadania wymusza ponowne wdrożenie, czy też element playbook korzystający z usługi ecs_service powoduje ponowne załadowanie zadania.

Jeśli ktoś jest zainteresowany, uzyskam pozwolenie na opublikowanie oczyszczonej wersji mojego poradnika.

mpechner
źródło
Uważam, że zmiana definicji zadania jest wymagana tylko wtedy, gdy aktualizujesz rzeczywistą konfigurację definicji zadania. w tym przypadku, jeśli używasz obrazu z najnowszym tagiem, nie ma potrzeby modyfikowania konfiguracji? Oczywiście posiadanie identyfikatora zatwierdzenia jako znacznika jest fajne, a także posiadanie oddzielnej wersji definicji zadania, abyś mógł wycofać, ale wtedy twój CI zobaczy wszystkie poświadczenia, których używasz dla kontenera, co nie jest sposobem, w jaki chcę implementować rzeczy.
holms
0

cóż, próbuję też znaleźć zautomatyzowany sposób na zrobienie tego, to znaczy wypchnij zmiany do ECR, a następnie serwis powinien odebrać najnowszy tag. Tak, możesz to zrobić ręcznie, zatrzymując zadanie usługi z klastra. Nowe zadania ściągną zaktualizowane kontenery ECR.

Avijeet
źródło