Amazon ECS タスクがDockerイメージを更新するための適切なアプローチは何ですか?
スクリプト を作成し、更新された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
タスクを開始するたびに(StartTask
およびRunTask
API呼び出しを介して、またはサービスの一部として自動的に開始されます)、ECSエージェントはタスク定義で指定したimage
のdocker pull
を実行します。レジストリにプッシュするたびに同じイメージ名(タグを含む)を使用すると、新しいタスクを実行して新しいイメージを実行できるようになります。 Dockerが何らかの理由(ネットワークの問題や認証の問題など)でレジストリに到達できない場合、ECSエージェントはキャッシュされたイメージの使用を試みることに注意してください。画像を更新するときにキャッシュされた画像が使用されないようにする場合は、毎回異なるタグをレジストリにプッシュし、新しいタスクを実行する前にそれに応じてタスク定義を更新します。
更新:この動作は、ECSエージェントに設定されたECS_IMAGE_PULL_BEHAVIOR
環境変数を使用して調整できるようになりました。詳細については、 ドキュメント を参照してください。執筆時点では、次の設定がサポートされています。
コンテナインスタンスのプルイメージプロセスをカスタマイズするために使用される動作。次に、オプションの動作について説明します。
default
が指定されている場合、イメージはリモートでプルされます。イメージのプルが失敗した場合、コンテナはインスタンス上のキャッシュされたイメージを使用します。
always
が指定されている場合、イメージは常にリモートでプルされます。イメージのプルが失敗すると、タスクは失敗します。このオプションにより、イメージの最新バージョンが常にプルされます。キャッシュされた画像はすべて無視され、自動画像クリーンアッププロセスの対象となります。
once
が指定されている場合、同じコンテナインスタンス上の前のタスクによってプルされていない場合、またはキャッシュされたイメージが自動イメージクリーンアッププロセスによって削除された場合にのみ、イメージがリモートでプルされます。それ以外の場合、インスタンスのキャッシュされたイメージが使用されます。これにより、不必要なイメージのプルが試行されなくなります。
prefer-cached
が指定されている場合、キャッシュされたイメージがない場合、イメージはリモートでプルされます。それ以外の場合、インスタンスのキャッシュされたイメージが使用されます。キャッシュされたイメージが削除されないようにするため、コンテナーの自動イメージクリーンアップは無効になっています。
タスクがサービスの下で実行されている場合、新しい展開を強制できます。これにより、タスク定義が再評価され、新しいコンテナイメージがプルされます。
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
新しいタスク定義を登録し、新しいタスク定義を使用するようにサービスを更新することは、AWSが推奨するアプローチです。これを行う最も簡単な方法は次のとおりです。
このチュートリアル には詳細があり、上記の手順がエンドツーエンドの製品開発プロセスにどのように適合するかを説明しています。
完全開示:このチュートリアルでは、Bitnamiのコンテナーを取り上げ、私はBitnamiで働いています。ただし、ここで表明された考えは私自身のものであり、Bitnamiの意見ではありません。
AWS CodePipeline。
ECRをソースとして設定し、ECSを展開先として設定できます。
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
まあ私はそれを行う自動化された方法を見つけようとしています、それはECRに変更をプッシュし、サービスによって最新のタグが取得されるべきです。クラスターからサービスのタスクを停止することにより、手動で実行できます。新しいタスクは、更新されたECRコンテナーをプルします。