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


110

イメージが対応するレジストリで更新された後、Amazon ECSタスクがDockerイメージを更新するための適切なアプローチは何ですか?


自動化/スケジュールされたLambda関数を実行することをお勧めします。このように、それはインスタンスの外にあります。試しましたか?SWFを使用して、一度にステップを実行することもできます
iSkore

@iSkoreで自動化する必要はありません。最終的にはスクリプトを書きたいのですが、いつ実行するかを自分で選択します。
aknuds1

ああっちゃ。それについてはよくわかりませんでした。もう少し情報を提供できますか?
iSkore 2016年

@iSkore私はそれを私がすでにやったよりも上手に説明する方法がわかりません。手順は次のとおりです。1.新しいバージョンのDockerイメージをレジストリにプッシュします。2.新しいイメージバージョンをECSに展開します。質問は、後者を実装する方法です。
aknuds1

これはEKSでも簡単ではないか、明白ではありません。Fは、クラスターを使用して新しいイメージをデプロイする最も一般的なタスクなので、ドキュメントではあいまいです。

回答:


88

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

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

1
これが機能するためには、ECSインスタンスに同じサイズの追加タスクをデプロイするのに十分なリソースがあることを確認する必要があると思います。AWSは基本的にホットスワップを実行しようとし、古いタスクを終了する前に新しいタスクインスタンスがプリブートされるのを待機していると思います。そうでない場合は、実行中のインスタンスが0の「デプロイメント」エントリを追加し続けるだけです。
Alex Fedulov

3
@AlexFedulov、うん、あなたは正しいと思う。新しいデプロイを作成するときにダウンタイムが発生しないようにするには、次のいずれかを行います。1)新しいバージョンを古いバージョンと一緒にデプロイするのに十分なインスタンスをプロビジョニングします。これは自動スケーリングで実現できます。2)Fargate展開タイプを使用します。新しいサービスをデプロイする前にECSが古いサービスを削除できるように、サービスの「最小正常パーセント」パラメーターを0に設定することで、余分なリソースの割り当てを回避できます。ただし、これによりダウンタイムが発生します。
ディマ2018

3
不明なオプション:
force

1
不明なオプション:
Kyle Parisi

1
私はこのコマンドを試しました、それは新しいイメージでコンテナを更新しません、それは同じ古いイメージで別のコンテナをスピンアップします。サービスには、私は希望回数= 1 specifidているにもかかわらず、実行している2つのコンテナに終わる私はそう
数学

61

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

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

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

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

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

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

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


4
本気ですか?(同じタグ名を使用して)新しいイメージをDockerhubにプッシュした後でも、古いdockerイメージが実行される例を見てきました。新しいイメージが作成されるたびにタグ名をバンプするだけでよいのではないでしょうか。しかし、これは私の経験ではかなりまれでしたので、おそらくそれは一時的なネットワークの問題でした。(あなたが最良の人がこれを答えるようにしているので、私は、あなたがECSに取り組むことを承知しているが、これは私が経験した、まさに謝罪を、これは失礼ではなく、私の意図として外れた場合ではありません。!)
イブラヒム・

1
はい、現在の動作では、毎回プルを試みます。プルが失敗した場合(ネットワークの問題、権限の欠如など)、キャッシュされたイメージを使用しようとします。詳細は、通常はにあるエージェントログファイルで確認できます/var/log/ecs
Samuel Karp

26

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

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

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

完全な開示:このチュートリアルでは、Bitnamiのコンテナーを取り上げ、私はBitnamiで働いています。しかし、ここで述べられた考えは私自身のものであり、ビットナミの意見ではありません。


3
これは機能しますが、サービスの最小値/最大値を変更する必要がある場合があります。EC2インスタンスが1つしかない場合は、min healthパーセントをゼロに設定する必要があります。そうしないと、更新されたコンテナーをデプロイするためにタスクが強制終了されることはありません(サービスを一時的にオフラインにする)。
Malvineous 2017年

3
@Malvineous良い点!でチュートリアルのECS設定部、私は正確に記述します。ここでは、そのセクションからの推奨設定があります:タスクの数- 1、最小健康パーセント- 0、最大パーセント- 200
ニール・

@ニール私はここに述べられているようにあなたのアプローチを試みました...それでも喜びはありません
ハフィズ

@Hafizこれを理解するために助けが必要な場合は、どこまで到達したか、どのエラーが発生したかを説明する必要があります。
ニール、2017年

これはサービスでのみ機能し、サービスのないタスクでは機能しません。
zaitsman 2017

8

これを行うには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のように、最小正常パーセントと最大パーセントに適切な数値を設定することを忘れないでください。


IPを変更せずにこれを行う方法はありますか?私がこれを実行したとき、それは機能しましたが、実行していたプライベートIPを変更しました
Migdotcom

@MigdotcomプロキシNLBが必要なときに同様の問題が発生しました。つまり、EC2インスタンスIPを同じに保つ唯一の方法は、エラスティックIPアドレスを使用するか、別のアプローチを使用することです。私はあなたのユースケースを知りませんが、ECSにリンクされたALBにGlobal Acceleratorをリンクすると、静的IPアドレスが提供され、これにより私のユースケースが解決されました。動的内部IPを知りたい場合は、ラムダを使用してALBをクエリする必要があります。これは大変な労力でした。下記のリンク: aws.amazon.com/blogs/networking-and-content-delivery/...
マーカス

aws ecs update-service --cluster <クラスター名> --service <サービス名> --force-new-deploymentがうまくいきました!
gvasquez

3

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

スクリプトを機能させるには、deploymentConfiguration.minimumHealthyPercentECSがインスタンスを盗んで更新されたタスク定義を展開できるように、予備の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

@Andrisありがとう、修正されました。
aknuds1

5
これはやりすぎです。terraformまたは単一のecs-cli行で展開できるはずです。
ホルムズ

@holms Terraformを使用してECSタスクイメージを更新しています。上記のpython-codeと同じくらいやりすぎです。必要な手順も複雑です。
Jari Turkia

3

AWS CodePipeline。

ECRをソースとして、ECSをデプロイ先のターゲットとして設定できます。


2
これに関するドキュメントにリンクできますか?
BenDog

1

docker imageタグが同じ場合、以下がうまくいきました:

  1. クラスターとサービスに移動します。
  2. サービスを選択し、[更新]をクリックします。
  3. タスクの数を0に設定して更新します。
  4. 展開が完了したら、タスクの数を1に再スケーリングします。

1

同じ問題に遭遇しました。数時間を費やした後、更新されたイメージの自動展開のための次の簡略化された手順を完了しました。

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であると想定して、すべてのコマンドを記述しました。実装するときに、それをそれぞれのリージョンに置き換えるだけです。


パラメータがテラフォームパラメータであることに気づきました。CloudFormationで同じことを達成するためのアイデア:AutoScalingGroupのMinSize:0およびMaxSize:1があります。他に何を設定する必要がありますか?
ウェイン

0

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

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

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


タスク定義の改訂は、実際のタスク定義構成を更新する場合にのみ必要だと思います。この場合、タグが最新のイメージを使用している場合、構成を変更する必要はありませんか?もちろん、コミットIDをタグとして持つのもいいですし、個別のタスク定義リビジョンもあるのでロールバックできますが、CIにはコンテナーに使用しているすべての資格情報が表示されますが、これは実装したくない方法です。
ホルム

0

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


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.