一旦在相应的注册表中更新了映像后,使我的Amazon ECS任务更新其Docker映像的正确方法是什么?
一旦在相应的注册表中更新了映像后,使我的Amazon ECS任务更新其Docker映像的正确方法是什么?
Answers:
如果您的任务在服务下运行,则可以强制进行新的部署。这将强制重新定义任务定义并提取新的容器映像。
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
指定,则在没有缓存的图像的情况下远程拖动图像。否则,将使用实例上的缓存图像。容器的自动图像清除功能已禁用,以确保不删除缓存的图像。
/var/log/ecs
。
AWS推荐注册新任务定义并更新服务以使用新任务定义。最简单的方法是:
本教程有更多详细信息,并描述了上述步骤如何适合端到端产品开发过程。
完全公开:本教程介绍了Bitnami的容器,我为Bitnami工作。但是这里表达的想法是我自己的,而不是Bitnami的观点。
有两种方法可以做到这一点。
首先,使用AWS CodeDeploy。您可以在ECS服务定义中配置“蓝色/绿色部署”部分。这包括CodeDeployRoleForECS,用于交换的另一个TargetGroup和测试侦听器(可选)。AWS ECS将创建CodeDeploy应用程序和部署组,并将这些CodeDeploy资源与您的ECS集群/服务和ELB / TargetGroups链接。然后,您可以使用CodeDeploy启动部署,您需要在其中输入一个AppSpec,该AppSpec指定使用哪个任务/容器来更新哪个服务。在此处指定新任务/容器。然后,您将看到新实例在新TargetGroup中旋转,并且旧TargetGroup与ELB断开连接,不久,注册到旧TargetGroup的旧实例将被终止。
这听起来很复杂。实际上,由于/如果您对ECS服务启用了自动扩展,那么一种简单的方法就是使用控制台或cli强制进行新部署,就像这里的先生指出的那样:
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
这样,您仍然可以使用“滚动更新”部署类型,如果一切正常,ECS只会启动新实例并耗尽旧实例,而不会造成服务停机。不利的一面是,您将失去对部署的良好控制,如果出现错误,则无法回滚到以前的版本,这将破坏正在进行的服务。但这是一个非常简单的方法。
顺便说一句,不要忘记为最小健康百分比和最大百分比设置适当的数字,例如100和200。
我创建了一个脚本,用于将更新的Docker映像部署到ECS上的登台服务,以便相应的任务定义引用Docker映像的当前版本。我不确定我是否遵循最佳做法,因此欢迎您提供反馈。
为了使脚本正常工作,您需要一个备用ECS实例或一个deploymentConfiguration.minimumHealthyPercent
值,以便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)
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
以下情况对我有用,以防docker image标签相同:
遇到同样的问题。花了几个小时后,完成了以下简化步骤,用于自动部署更新的映像:
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。实施时只需将其替换为您各自的区域即可。
如上所述,我使用AWS CLI尝试了aws ecs update-service。没有从ECR获取最新的docker。最后,我重新运行了创建ECS集群的Ansible剧本。运行ecs_taskdefinition时,任务定义的版本会增加。那一切都很好。拾取新的Docker映像。
完全不确定任务版本更改是否强制重新部署,或者使用ecs_service的剧本是否导致任务重新加载。
如果有人感兴趣,我将获得发布我的剧本的净化版本的许可。
以下命令对我有用
docker build -t <repo> .
docker push <repo>
ecs-cli compose stop
ecs-cli compose start