web-dev-qa-db-ja.com

更新されたDockerイメージをAmazon ECSタスクにデプロイするにはどうすればよいですか?

Amazon ECS タスクがDockerイメージを更新するための適切なアプローチは何ですか?

71
aknuds1

スクリプト を作成し、更新されたDockerイメージをECSのステージングサービスに展開して、対応するタスク定義がDockerイメージの現在のバージョンを参照するようにしました。ベストプラクティスに従っているかどうかは定かではないので、フィードバックを歓迎します。

スクリプトが機能するためには、予備のECSインスタンスまたはdeploymentConfiguration.minimumHealthyPercent値のいずれかが必要です。これにより、ECSは更新されたタスク定義をデプロイするインスタンスを盗むことができます。

私のアルゴリズムは次のようなものです。

  1. タスク定義のコンテナに対応するDockerイメージにGitリビジョンをタグ付けします。
  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
3
aknuds1

タスクを開始するたびに(StartTaskおよびRunTask API呼び出しを介して、またはサービスの一部として自動的に開始されます)、ECSエージェントはタスク定義で指定したimagedocker pullを実行します。レジストリにプッシュするたびに同じイメージ名(タグを含む)を使用すると、新しいタスクを実行して新しいイメージを実行できるようになります。 Dockerが何らかの理由(ネットワークの問題や認証の問題など)でレジストリに到達できない場合、ECSエージェントはキャッシュされたイメージの使用を試みることに注意してください。画像を更新するときにキャッシュされた画像が使用されないようにする場合は、毎回異なるタグをレジストリにプッシュし、新しいタスクを実行する前にそれに応じてタスク定義を更新します。

更新:この動作は、ECSエージェントに設定されたECS_IMAGE_PULL_BEHAVIOR環境変数を使用して調整できるようになりました。詳細については、 ドキュメント を参照してください。執筆時点では、次の設定がサポートされています。

コンテナインスタンスのプルイメージプロセスをカスタマイズするために使用される動作。次に、オプションの動作について説明します。

  • defaultが指定されている場合、イメージはリモートでプルされます。イメージのプルが失敗した場合、コンテナはインスタンス上のキャッシュされたイメージを使用します。

  • alwaysが指定されている場合、イメージは常にリモートでプルされます。イメージのプルが失敗すると、タスクは失敗します。このオプションにより、イメージの最新バージョンが常にプルされます。キャッシュされた画像はすべて無視され、自動画像クリーンアッププロセスの対象となります。

  • onceが指定されている場合、同じコンテナインスタンス上の前のタスクによってプルされていない場合、またはキャッシュされたイメージが自動イメージクリーンアッププロセスによって削除された場合にのみ、イメージがリモートでプルされます。それ以外の場合、インスタンスのキャッシュされたイメージが使用されます。これにより、不必要なイメージのプルが試行されなくなります。

  • prefer-cachedが指定されている場合、キャッシュされたイメージがない場合、イメージはリモートでプルされます。それ以外の場合、インスタンスのキャッシュされたイメージが使用されます。キャッシュされたイメージが削除されないようにするため、コンテナーの自動イメージクリーンアップは無効になっています。

51
Samuel Karp

タスクがサービスの下で実行されている場合、新しい展開を強制できます。これにより、タスク定義が再評価され、新しいコンテナイメージがプルされます。

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

新しいタスク定義を登録し、新しいタスク定義を使用するようにサービスを更新することは、AWSが推奨するアプローチです。これを行う最も簡単な方法は次のとおりです。

  1. タスク定義に移動します
  2. 正しいタスクを選択してください
  3. 新しいリビジョンを作成するを選択します
  4. 既に最新バージョンのコンテナイメージを:latestタグなどでプルしている場合は、[作成]をクリックするだけです。それ以外の場合は、コンテナイメージのバージョン番号を更新して、[作成]をクリックします。
  5. 展開アクション
  6. 更新サービスを選択(2回)
  7. その後、サービスが再起動されるのを待ちます

このチュートリアル には詳細があり、上記の手順がエンドツーエンドの製品開発プロセスにどのように適合するかを説明しています。

完全開示:このチュートリアルでは、Bitnamiのコンテナーを取り上げ、私はBitnamiで働いています。ただし、ここで表明された考えは私自身のものであり、Bitnamiの意見ではありません。

20
Neal

AWS CodePipeline。

ECRをソースとして設定し、ECSを展開先として設定できます。

1
Guy

AWS cliを使用して、上記のようにaws ecs update-serviceを試しました。 ECRから最新のdockerを取得しませんでした。最後に、ECSクラスターを作成したAnsibleプレイブックを再実行します。タスク定義のバージョンは、ecs_taskdefinitionの実行時にバンプされます。その後、すべてが良いです。新しいdockerイメージが取得されます。

真に、タスクバージョンの変更により再デプロイが強制されるかどうか、またはecs_serviceを使用するプレイブックによってタスクがリロードされるかどうかはわかりません。

誰かが興味を持っているなら、私は私のプレイブックのサニタイズされたバージョンを公開する許可を得ます。

1
mpechner

次のコマンドは私のために働いた

docker build -t <repo> . 
docker Push <repo>
ecs-cli compose stop
ecs-cli compose start
0
explorer

まあ私はそれを行う自動化された方法を見つけようとしています、それはECRに変更をプッシュし、サービスによって最新のタグが取得されるべきです。クラスターからサービスのタスクを停止することにより、手動で実行できます。新しいタスクは、更新されたECRコンテナーをプルします。

0
Avijeet