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


110

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


자동화 / 예약 된 Lambda 기능을 실행하는 것이 좋습니다. 이렇게하면 인스턴스 외부에 있습니다. 시도해 보셨습니까? SWF를 사용하여 한 번에 단계를 수행 할 수도 있습니다
iSkore

@iSkore를 자동화 할 필요가 없습니다. 결국 스크립트를 작성하고 싶지만 언제 실행할지 스스로 선택합니다.
aknuds1

Ahh gotcha. 그것에 대해 확신하지 못했습니다. 좀 더 많은 정보를 제공 할 수 있습니까?
iSkore

@iSkore 내가 이미 한 것보다 더 잘 설명하는 방법을 모르겠습니다. 절차는 다음과 같습니다. 1. 새 버전의 Docker 이미지를 레지스트리에 푸시합니다. 2. ECS에 새 이미지 버전을 배포합니다. 질문은 후자를 구현하는 방법입니다.
aknuds1

이것은 EKS에서도 쉽지 않거나 명확하지 않습니다. F가 클러스터를 사용하고 새 이미지를 배포하는 가장 일반적인 작업 인 방법은 문서에서 모호합니다.

답변:


88

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

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

1
이것이 작동하려면 ECS 인스턴스에 동일한 크기의 추가 작업을 배포하기에 충분한 리소스가 있는지 확인해야합니다. AWS가 기본적으로 핫스왑을 수행하여 이전 작업 인스턴스를 종료하기 전에 새 작업 인스턴스가 사전 부팅 될 때까지 기다린다고 가정합니다. 그렇지 않은 경우 실행중인 인스턴스가 0 인 "배포"항목을 계속 추가합니다.
Alex Fedulov

3
@AlexFedulov, 네, 당신이 맞다고 생각합니다. 새 배포를 생성 할 때 다운 타임이 발생하지 않도록 1) 이전 버전과 함께 새 버전을 배포 할 수있는 충분한 인스턴스를 프로비저닝 할 수 있습니다. 이는 자동 확장으로 달성 할 수 있습니다. 2) Fargate 배포 유형을 사용합니다. ECS가 새 서비스를 배포하기 전에 이전 서비스를 제거 할 수 있도록 서비스의 "minimum healthy percent"파라미터를 0으로 설정하여 추가 리소스 할당을 방지 할 수 있습니다. 하지만 이로 인해 약간의 다운 타임이 발생합니다.
Dima

3
알 수없는 옵션 : --force-new-deployment
user4674453 '182018-09-24

1
알 수없는 옵션 : --force-new-deployment : awscli 업그레이드
Kyle Parisi

1
이 명령을 시도했지만 새 이미지로 컨테이너를 업데이트하지 않고 동일한 이전 이미지로 다른 컨테이너를 회전시킵니다. 그래서 서비스 중에 원하는 개수 = 1을 지정 했음에도 불구하고 두 개의 컨테이너를 실행하게됩니다
maths

61

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

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

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

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

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

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

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


4
확실합니까? 새 이미지를 Dockerhub에 푸시 한 후에도 이전 Docker 이미지가 실행되는 인스턴스를 보았습니다 (동일한 태그 이름 사용). 새 이미지가 만들어 질 때마다 태그 이름을 올려야 할 것 같습니다. 그러나 이것은 내 경험상 매우 드물기 때문에 일시적인 네트워크 문제 일 수 있습니다. (저는 당신이 ECS에서 일하고 있다는 것을 알고 있습니다. 그래서 당신이이 질문에 답할 수있는 가장 좋은 사람이지만 이것은 제가 경험 한 것과 정확히 다릅니다. 이것이 제 의도가 아니라 무례하게 나온다면 사과드립니다!)
Ibrahim

1
예, 현재 동작은 매번 당기기를 시도하는 것입니다. 가져 오기가 실패하면 (네트워크 문제, 권한 부족 등) 캐시 된 이미지를 사용하려고 시도합니다. 일반적으로에있는 에이전트 로그 파일에서 자세한 내용을 찾을 수 있습니다 /var/log/ecs.
Samuel Karp

26

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

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

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

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


3
이것은 작동하지만 서비스 최소 / 최대 값을 변경해야 할 수 있습니다. EC2 인스턴스가 하나만있는 경우 최소 정상 백분율을 0으로 설정해야합니다. 그렇지 않으면 업데이트 된 컨테이너를 배포하기 위해 작업을 종료하지 않습니다 (서비스를 일시적으로 오프라인으로 설정).
Malvineous

3
@Malvineous 좋은 지적! 에서 튜토리얼의 ECS 설정 섹션 , 나는 정확히 설명합니다. 해당 섹션에서 권장하는 구성은 다음과 같습니다. 작업 수-1, 최소 정상 백분율-0, 최대 백분율
Neal

@Neal 여기에 언급 된대로 접근 방식을 시도했습니다 ... 여전히 기쁨이 없습니다
Hafiz

@Hafiz이 문제를 파악하는 데 도움이 필요하면 얼마나 멀리 왔는지, 어떤 오류가 발생했는지 설명해야합니다.
Neal

이것은 서비스가없는 작업이 아니라 서비스에 대해서만 작동합니다.
zaitsman

8

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

먼저 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과 같이 최소 건강 백분율 및 최대 백분율에 적절한 숫자를 설정하는 것을 잊지 마십시오.


IP를 변경하지 않고이를 수행 할 수있는 방법이 있습니까? 내가 이것을 실행했을 때 그것은 효과가 있었지만 내가 실행하고 있던 사설 IP를 변경했습니다
Migdotcom

@Migdotcom 프록시 NLB가 필요할 때 비슷한 문제가 발생했습니다. 요컨대 EC2 인스턴스 IP를 동일하게 유지하는 유일한 방법은 탄력적 IP 주소를 사용하거나 다른 접근 방식을 사용하는 것입니다. 사용 사례는 모르지만 Global Accelerator를 ECS 연결된 ALB에 연결하면 고정 IP 주소가 제공되어 사용 사례가 해결되었습니다. 동적 내부 IP를 알고 싶다면 람다를 사용하여 ALB를 쿼리해야합니다. 이것은 많은 노력이었습니다. 아래 링크 : aws.amazon.com/blogs/networking-and-content-delivery/…
Marcus

aws ecs update-service --cluster <클러스터 이름> --service <서비스 이름> --force-new-deployment가 저에게 효과적이었습니다!
gvasquez

3

업데이트 된 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

@Andris 감사합니다.
aknuds1

5
이것은 과잉입니다. terraform 또는 단일 ecs-cli 라인을 통해 배포 할 수 있어야합니다.
holms

@holms Terraform을 사용하여 ECS 작업 이미지를 업데이트하고 있습니다. 위의 파이썬 코드만큼 과잉입니다. 필요한 단계는 매우 복잡합니다.
Jari Turkia

3

AWS CodePipeline.

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


2
이에 대한 문서를 링크 할 수 있습니까?
BenDog

1

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

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

1

같은 문제가 발생했습니다. 몇 시간을 보낸 후 업데이트 된 이미지의 자동 배포를 위해 다음과 같은 단순화 된 단계를 완료했습니다.

1. ECS 작업 정의 변경 : 더 나은 이해를 위해 아래 세부 정보가 포함 된 작업 정의를 만들었다 고 가정 해 보겠습니다 (참고 :이 숫자는 작업 정의에 따라 변경됨).

launch_type = EC2

desired_count = 1

그런 다음 다음과 같이 변경해야합니다.

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. 이미지에 < your-image-name> : latest 태그를 지정합니다 . 최신 키는 각 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. ECR로 이미지를 푸시합니다.

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

4. 강제 배포 적용

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

참고 : 지역을 us-east-1 이라고 가정하여 모든 명령을 작성했습니다 . 구현하는 동안 해당 지역으로 교체하십시오.


매개 변수가 테라 폼 매개 변수라는 것을 알았습니다. CloudFormation에서 동일한 작업을 수행하는 방법에 대한 아이디어 : AutoScalingGroup MinSize : 0 및 MaxSize : 1이 있습니다. 무엇을 설정해야합니까?
Wayne

0

AWS cli를 사용하여 위에서 제안한대로 aws ecs update-service를 시도했습니다. ECR에서 최신 도커를 선택하지 않았습니다. 마지막으로 ECS 클러스터를 생성 한 Ansible 플레이 북을 다시 실행합니다. 작업 정의 버전은 ecs_taskdefinition이 실행될 때 범프됩니다. 그러면 모든 것이 좋습니다. 새 도커 이미지가 선택됩니다.

작업 버전 변경으로 인해 강제로 재배포되는지 또는 ecs_service를 사용하는 플레이 북으로 인해 작업이 다시로드되는지 확실하지 않습니다.

관심이있는 사람이 있으면 내 플레이 북의 삭제 된 버전을 게시 할 수있는 권한을 얻습니다.


실제 작업 정의 구성을 업데이트 할 때만 작업 정의 수정이 필요하다고 생각합니다. 이 경우 latest 태그가있는 이미지를 사용하는 경우 구성을 수정할 필요가 없습니까? 물론 커밋 ID를 태그로 사용하는 것이 좋고 별도의 작업 정의 개정도 있으므로 롤백 할 수 있지만 CI는 내가 구현하려는 방식이 아닌 컨테이너에 사용중인 모든 자격 증명을 볼 수 있습니다.
holms

0

글쎄, 나는 또한 그것을하는 자동화 된 방법을 찾으려고 노력하고 있습니다. 즉, 변경 사항을 ECR에 푸시하고 최신 태그를 서비스에서 선택해야합니다. 클러스터에서 서비스 작업을 중지하여 수동으로 수행 할 수 있습니다. 새 작업은 업데이트 된 ECR 컨테이너를 가져옵니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.