イメージが対応するレジストリで更新された後、Amazon ECSタスクがDockerイメージを更新するための適切なアプローチは何ですか?
イメージが対応するレジストリで更新された後、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
が指定されている場合、キャッシュされたイメージがない場合、イメージはリモートでプルされます。それ以外の場合は、インスタンスのキャッシュされたイメージが使用されます。キャッシュされたイメージが削除されないようにするため、コンテナーのイメージの自動クリーンアップは無効になっています。
/var/log/ecs
。
AWSが推奨するアプローチは、新しいタスク定義を登録し、新しいタスク定義を使用するようにサービスを更新することです。これを行う最も簡単な方法は、次のとおりです。
このチュートリアルはより詳細であり、上記のステップがエンドツーエンドの製品開発プロセスにどのように適合するかを説明します。
完全な開示:このチュートリアルでは、Bitnamiのコンテナーを取り上げ、私はBitnamiで働いています。しかし、ここで述べられた考えは私自身のものであり、ビットナミの意見ではありません。
これを行うには2つの方法があります。
まず、AWS CodeDeployを使用します。ECSサービス定義でBlue / Green展開セクションを構成できます。これには、CodeDeployRoleForECS、スイッチの別のTargetGroup、およびテストリスナー(オプション)が含まれます。AWS ECSは、CodeDeployアプリケーションとデプロイメントグループを作成し、これらのCodeDeployリソースをECSクラスター/サービスおよびELB / TargetGroupsにリンクします。次に、CodeDeployを使用してデプロイを開始できます。ここで、どのサービス/コンテナを使用してどのサービスを更新するかを指定する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イメージの現在のバージョンを参照します。私がベストプラクティスに従っているかどうかは確かではないので、フィードバックを歓迎します。
スクリプトを機能させるには、deploymentConfiguration.minimumHealthyPercent
ECSがインスタンスを盗んで更新されたタスク定義を展開できるように、予備の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
同じ問題に遭遇しました。数時間を費やした後、更新されたイメージの自動展開のための次の簡略化された手順を完了しました。
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. force-deploymentを適用する
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を使用するPlaybookによってタスクがリロードされるのかは、確かにわかりません。
誰かが興味を持っている場合、私は私のプレイブックのサニタイズされたバージョンを公開する許可を得ます。
次のコマンドは私のために働きました
docker build -t <repo> .
docker push <repo>
ecs-cli compose stop
ecs-cli compose start