[docker] 업데이트 된 Docker 이미지를 Amazon ECS 작업에 배포하려면 어떻게해야합니까?

해당 레지스트리에서 이미지가 업데이트 된 후 Amazon ECS 작업이 Docker 이미지를 업데이트 하도록하는 올바른 접근 방식은 무엇입니까 ?



답변

작업이 서비스에서 실행중인 경우 새 배포를 강제 할 수 있습니다. 이렇게하면 작업 정의를 다시 평가하고 새 컨테이너 이미지를 가져옵니다.

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


답변

때마다 당신이합니다 (통해서 작업을 시작 StartTask하고 RunTaskAPI 호출 또는 그 서비스의 일부로 자동으로 시작됩니다)는 ECS 에이전트는 수행 docker pullimage귀하의 작업 정의에 지정을. 레지스트리로 푸시 할 때마다 동일한 이미지 이름 (태그 포함)을 사용하는 경우 새 작업을 실행하여 새 이미지를 실행할 수 있어야합니다. Docker가 어떤 이유로 든 (예 : 네트워크 문제 또는 인증 문제) 레지스트리에 연결할 수없는 경우 ECS 에이전트는 캐시 된 이미지를 사용하려고 시도합니다. 이미지를 업데이트 할 때 캐시 된 이미지가 사용되지 않도록하려면 매번 레지스트리에 다른 태그를 푸시하고 새 작업을 실행하기 전에 그에 따라 작업 정의를 업데이트해야합니다.

업데이트 : 이제이 동작은 ECS_IMAGE_PULL_BEHAVIORECS 에이전트에 설정된 환경 변수를 통해 조정할 수 있습니다 . 자세한 내용 은 설명서 를 참조하십시오. 작성 시점을 기준으로 다음 설정이 지원됩니다.

컨테이너 인스턴스에 대한 가져 오기 이미지 프로세스를 사용자 지정하는 데 사용되는 동작입니다. 다음은 선택적 동작에 대해 설명합니다.

  • 경우 default지정, 이미지가 원격으로 당겨진다. 이미지 가져 오기가 실패하면 컨테이너는 인스턴스에서 캐시 된 이미지를 사용합니다.

  • 경우 always지정, 이미지는 항상 원격으로 당겨진다. 이미지 가져 오기가 실패하면 작업이 실패합니다. 이 옵션은 항상 최신 버전의 이미지를 가져 오도록합니다. 캐시 된 모든 이미지는 무시되며 자동화 된 이미지 정리 프로세스를 따릅니다.

  • 경우 once지정, 이미지는이 같은 컨테이너 인스턴스에 이전 작업에 의해 끌려되지 않았거나 캐시 된 이미지가 자동 이미지 정리 프로세스에 의해 제거 된 경우에만 원격으로 당겨진다. 그렇지 않으면 인스턴스의 캐시 된 이미지가 사용됩니다. 이렇게하면 불필요한 이미지 가져 오기가 시도되지 않습니다.

  • 경우 prefer-cached지정 캐시 된 이미지가없는 경우, 이미지가 원격으로 당겨진다. 그렇지 않으면 인스턴스의 캐시 된 이미지가 사용됩니다. 캐시 된 이미지가 제거되지 않도록 컨테이너에 대해 자동화 된 이미지 정리가 비활성화됩니다.


답변

새 작업 정의를 등록하고 새 작업 정의를 사용하도록 서비스를 업데이트하는 것은 AWS에서 권장하는 접근 방식입니다. 이를 수행하는 가장 쉬운 방법은 다음과 같습니다.

  1. 작업 정의로 이동
  2. 올바른 작업 선택
  3. 새 개정 만들기를 선택하십시오.
  4. : latest 태그와 같은 것을 사용하여 컨테이너 이미지의 최신 버전을 이미 가져오고있는 경우 만들기를 클릭하기 만하면됩니다. 그렇지 않으면 컨테이너 이미지의 버전 번호를 업데이트 한 다음 만들기를 클릭합니다.
  5. 작업 확장
  6. 업데이트 서비스 선택 (두 번)
  7. 그런 다음 서비스가 다시 시작될 때까지 기다립니다.

이 자습서 에는 더 자세한 내용이 포함되어 있으며 위 단계가 종단 간 제품 개발 프로세스에 어떻게 적용되는지 설명합니다.

전체 공개 :이 튜토리얼은 Bitnami의 컨테이너를 다루며 저는 Bitnami에서 일합니다. 그러나 여기에 표현 된 생각은 내 생각이며 Bitnami의 의견이 아닙니다.


답변

이를 수행하는 두 가지 방법이 있습니다.

먼저 AWS CodeDeploy를 사용합니다. ECS 서비스 정의에서 Blue / Green 배포 섹션을 구성 할 수 있습니다. 여기에는 CodeDeployRoleForECS, 스위치 용 다른 TargetGroup 및 테스트 리스너 (선택 사항)가 포함됩니다. AWS ECS는 CodeDeploy 애플리케이션 및 배포 그룹을 생성하고 이러한 CodeDeploy 리소스를 ECS 클러스터 / 서비스 및 ELB / TargetGroups와 연결합니다. 그런 다음 CodeDeploy를 사용하여 배포를 시작할 수 있으며, 여기에서 어떤 서비스를 업데이트하기 위해 어떤 작업 / 컨테이너를 사용하는지 지정하는 AppSpec을 입력해야합니다. 여기에서 새 작업 / 컨테이너를 지정합니다. 그런 다음 새 TargetGroup에서 새 인스턴스가 스핀 업되고 이전 TargetGroup이 ELB에 연결되어 있지 않고 곧 이전 TargetGroup에 등록 된 이전 인스턴스가 종료되는 것을 볼 수 있습니다.

이것은 매우 복잡하게 들립니다. 실제로 ECS 서비스에서 Auto Scaling을 활성화 한 이후,이를 수행하는 간단한 방법은 여기 신사가 지적한 것처럼 콘솔 또는 CLI를 사용하여 새 배포를 강제하는 것입니다.

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

이러한 방식으로 “롤링 업데이트”배포 유형을 계속 사용할 수 있으며 ECS는 모든 것이 정상인 경우 서비스의 다운 타임없이 새 인스턴스를 스핀 업하고 이전 인스턴스를 드레 이닝합니다. 나쁜 점은 배포에 대한 세밀한 제어를 잃고 오류가있는 경우 이전 버전으로 롤백 할 수 없으며 이로 인해 진행중인 서비스가 중단된다는 것입니다. 하지만 이것은 정말 간단한 방법입니다.

BTW, 100 및 200과 같이 최소 건강 백분율 및 최대 백분율에 적절한 숫자를 설정하는 것을 잊지 마십시오.


답변

업데이트 된 Docker 이미지를 ECS의 스테이징 서비스에 배포하기위한 스크립트 를 생성 하여 해당 작업 정의가 현재 버전의 Docker 이미지를 참조하도록했습니다. 모범 사례를 따르고 있는지 확실하지 않으므로 피드백을 환영합니다.

스크립트가 작동하려면 deploymentConfiguration.minimumHealthyPercentECS가 업데이트 된 작업 정의를 배포 할 인스턴스를 훔칠 수 있도록 예비 ECS 인스턴스 또는 값 이 필요합니다 .

내 알고리즘은 다음과 같습니다.

  1. Git 개정으로 작업 정의의 컨테이너에 해당하는 Docker 이미지에 태그를 지정합니다.
  2. Docker 이미지 태그를 해당 레지스트리로 푸시합니다.
  3. 작업 정의 패밀리에서 이전 작업 정의를 등록 취소합니다.
  4. 현재 Git 개정으로 태그가 지정된 Docker 이미지를 참조하여 새 작업 정의를 등록합니다.
  5. 새 작업 정의를 사용하도록 서비스를 업데이트합니다.

내 코드는 아래에 붙여 넣었습니다.

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


답변

AWS CodePipeline.

ECR을 소스로 설정하고 ECS를 배포 대상으로 설정할 수 있습니다.


답변

다음은 도커 이미지 태그가 동일한 경우 나를 위해 일했습니다.

  1. 클러스터 및 서비스로 이동하십시오.
  2. 서비스를 선택하고 업데이트를 클릭합니다.
  3. 작업 수를 0으로 설정하고 업데이트합니다.
  4. 배포가 완료되면 작업 수를 1로 다시 조정합니다.