[docker] 업데이트 된 Docker 이미지를 Amazon ECS 작업에 배포하려면 어떻게해야합니까?
해당 레지스트리에서 이미지가 업데이트 된 후 Amazon ECS 작업이 Docker 이미지를 업데이트 하도록하는 올바른 접근 방식은 무엇입니까 ?
답변
작업이 서비스에서 실행중인 경우 새 배포를 강제 할 수 있습니다. 이렇게하면 작업 정의를 다시 평가하고 새 컨테이너 이미지를 가져옵니다.
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
답변
때마다 당신이합니다 (통해서 작업을 시작 StartTask
하고 RunTask
API 호출 또는 그 서비스의 일부로 자동으로 시작됩니다)는 ECS 에이전트는 수행 docker pull
의 image
귀하의 작업 정의에 지정을. 레지스트리로 푸시 할 때마다 동일한 이미지 이름 (태그 포함)을 사용하는 경우 새 작업을 실행하여 새 이미지를 실행할 수 있어야합니다. Docker가 어떤 이유로 든 (예 : 네트워크 문제 또는 인증 문제) 레지스트리에 연결할 수없는 경우 ECS 에이전트는 캐시 된 이미지를 사용하려고 시도합니다. 이미지를 업데이트 할 때 캐시 된 이미지가 사용되지 않도록하려면 매번 레지스트리에 다른 태그를 푸시하고 새 작업을 실행하기 전에 그에 따라 작업 정의를 업데이트해야합니다.
업데이트 : 이제이 동작은 ECS_IMAGE_PULL_BEHAVIOR
ECS 에이전트에 설정된 환경 변수를 통해 조정할 수 있습니다 . 자세한 내용 은 설명서 를 참조하십시오. 작성 시점을 기준으로 다음 설정이 지원됩니다.
컨테이너 인스턴스에 대한 가져 오기 이미지 프로세스를 사용자 지정하는 데 사용되는 동작입니다. 다음은 선택적 동작에 대해 설명합니다.
경우
default
지정, 이미지가 원격으로 당겨진다. 이미지 가져 오기가 실패하면 컨테이너는 인스턴스에서 캐시 된 이미지를 사용합니다.경우
always
지정, 이미지는 항상 원격으로 당겨진다. 이미지 가져 오기가 실패하면 작업이 실패합니다. 이 옵션은 항상 최신 버전의 이미지를 가져 오도록합니다. 캐시 된 모든 이미지는 무시되며 자동화 된 이미지 정리 프로세스를 따릅니다.경우
once
지정, 이미지는이 같은 컨테이너 인스턴스에 이전 작업에 의해 끌려되지 않았거나 캐시 된 이미지가 자동 이미지 정리 프로세스에 의해 제거 된 경우에만 원격으로 당겨진다. 그렇지 않으면 인스턴스의 캐시 된 이미지가 사용됩니다. 이렇게하면 불필요한 이미지 가져 오기가 시도되지 않습니다.경우
prefer-cached
지정 캐시 된 이미지가없는 경우, 이미지가 원격으로 당겨진다. 그렇지 않으면 인스턴스의 캐시 된 이미지가 사용됩니다. 캐시 된 이미지가 제거되지 않도록 컨테이너에 대해 자동화 된 이미지 정리가 비활성화됩니다.
답변
새 작업 정의를 등록하고 새 작업 정의를 사용하도록 서비스를 업데이트하는 것은 AWS에서 권장하는 접근 방식입니다. 이를 수행하는 가장 쉬운 방법은 다음과 같습니다.
- 작업 정의로 이동
- 올바른 작업 선택
- 새 개정 만들기를 선택하십시오.
- : latest 태그와 같은 것을 사용하여 컨테이너 이미지의 최신 버전을 이미 가져오고있는 경우 만들기를 클릭하기 만하면됩니다. 그렇지 않으면 컨테이너 이미지의 버전 번호를 업데이트 한 다음 만들기를 클릭합니다.
- 작업 확장
- 업데이트 서비스 선택 (두 번)
- 그런 다음 서비스가 다시 시작될 때까지 기다립니다.
이 자습서 에는 더 자세한 내용이 포함되어 있으며 위 단계가 종단 간 제품 개발 프로세스에 어떻게 적용되는지 설명합니다.
전체 공개 :이 튜토리얼은 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.minimumHealthyPercent
ECS가 업데이트 된 작업 정의를 배포 할 인스턴스를 훔칠 수 있도록 예비 ECS 인스턴스 또는 값 이 필요합니다 .
내 알고리즘은 다음과 같습니다.
- Git 개정으로 작업 정의의 컨테이너에 해당하는 Docker 이미지에 태그를 지정합니다.
- Docker 이미지 태그를 해당 레지스트리로 푸시합니다.
- 작업 정의 패밀리에서 이전 작업 정의를 등록 취소합니다.
- 현재 Git 개정으로 태그가 지정된 Docker 이미지를 참조하여 새 작업 정의를 등록합니다.
- 새 작업 정의를 사용하도록 서비스를 업데이트합니다.
내 코드는 아래에 붙여 넣었습니다.
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를 배포 대상으로 설정할 수 있습니다.
답변
다음은 도커 이미지 태그가 동일한 경우 나를 위해 일했습니다.
- 클러스터 및 서비스로 이동하십시오.
- 서비스를 선택하고 업데이트를 클릭합니다.
- 작업 수를 0으로 설정하고 업데이트합니다.
- 배포가 완료되면 작업 수를 1로 다시 조정합니다.