Jakie jest właściwe podejście, aby moje zadania Amazon ECS aktualizowały obrazy Dockera, gdy te obrazy zostaną zaktualizowane w odpowiednim rejestrze?
Jakie jest właściwe podejście, aby moje zadania Amazon ECS aktualizowały obrazy Dockera, gdy te obrazy zostaną zaktualizowane w odpowiednim rejestrze?
Odpowiedzi:
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
Przy każdym uruchomieniu zadania (zarówno przez StartTask
i RunTask
API lub który jest uruchamiany automatycznie jako część a Service), Agent ECS będzie przeprowadzić docker pull
z image
tobą 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_BEHAVIOR
zmiennej ś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
default
okreś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
always
okreś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
once
jest 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-cached
okreś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.
/var/log/ecs
.
Rejestracja nowej definicji zadania i aktualizacja usługi do korzystania z nowej definicji zadania to podejście zalecane przez AWS. Najłatwiej to zrobić:
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.
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.
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.minimumHealthyPercent
wartość, dzięki której ECS może ukraść instancję i wdrożyć zaktualizowaną definicję zadania.
Mój algorytm wygląda tak:
Mój kod wklejony poniżej:
#!/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
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.
Następujące działały dla mnie na wypadek, gdyby tag obrazu dockera był taki sam:
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.
Poniższe polecenia działały dla mnie
docker build -t <repo> .
docker push <repo>
ecs-cli compose stop
ecs-cli compose start