1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ECS で latest だけ使うのはもうやめよう - ECR タグ戦略のススメ

1
Posted at

はじめに:なぜ「タグ戦略」が大事なのか

コンテナをECRにpushしてECSを動かすとき、何も考えずにタグをこんな感じでつけていないでしょうか?
ecs-sample-app:latest

とりあえずはこれで動くは動く。だがしかし、長期運用を考えるとlatestだけの運用はまぁ事故ります。

実体験としてECRのイメージタグは「latest」のみで運用されていてトラブルが発生した際、今動いてコードはGit上のどれなのか。いつ何の変更でリリースされたものなのか追おうと思っても追うことができないのでチケットなどから恐らくこうだろうの推測を立てるしかないといったことがありました。困った:sweat:

この記事では、以下を整理していく。

  • latestだけを使うと何が起こりうるのか
  • それを解決するためのlatest + commit SHA戦略
  • GitHub Actionsでの実装イメージ

1.latestタグだけの運用に潜む落とし穴

1. どのバージョンで動いているか分からない

latestだけを使っていると、ECSのタスク定義はこうなる
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-sample-app:latest
一見シンプルで問題なさそうですが、ここに情報は何もないです。

  • どのコミットからビルドされたイメージなのか?
  • 誰がいつデプロイしたものなのか?
  • 1個前のデプロイとどこが違うのか?

すべて不明

「XXのバージョンに戻してほしい」がほぼ不可能

Biz「急ぎ、3つ前のバージョンに戻してほしいです。」
開発者「うーん。3つ前ってどれのことだろう。:disappointed_relieved:

どれで動いていたか分からないので戻すのに時間がかかるなんて言いずらいってもんじゃないです。

ECS側にはpushされたイメージ履歴は残っているように見えますが、

  • タグはlatestのまま上書きされる
  • 過去イメージは「タグなしイメージ」になってしまう
  • そのタグなしイメージがどのコミット由来なのか分からない

5-2.ecs-ciコミット.png

このことから、特定のバージョンに戻すのは現実的には難しい。

2. ECS側のキャッシュ・挙動が読めない

latestは「常に最新を指したい」タグのはずですが、
ECS/Fargateが 毎回必ずpullし直すとは限りません。

  • イメージのdigest(ハッシュ)とキャッシュ状況を見て、変わってなさそうならpullしないという挙動になることがある
    例:Webサービスのタスクが3つ並列稼働している場合
    1. latest = v1でタスクA/B/Cを起動
    2. 新しいlatest(v2)をECRにpush
    3. タスクAだけ再起動 → v2をpull
    4. タスクB/Cは再起動されず → v1のまま動き続ける
      → 同じECSサービス内でv1とv2が混在する可能性あり。
      :bulb:短時間で終わる、1回切りのバッチなら影響は出にくいですが、
      並列タスク長時間稼働サービスでは、バージョン混在が普通に起こりえます。

3. 再現性のないデプロイになる

一番怖い点はここ

「同じタスク定義を再デプロイしても、同じイメージが動くとは限らない」
例えば、タスク定義はずっとこうだとします。
image: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-sample-app:latest
最初のデプロイ(Day1)

  • 最初のデプロイ時点のlatest(v1)でデプロイ、正常に動いている

2日後(Day3)

  • 他の開発者がlatst(v2)をpush、あなたは「昨日と同じタスク定義で再デプロイ」したつもり
    しかし、その時点でlatestはv2に変わっているので、Day1と全く同じ定義なのに実際に動くのはv2のコードになります。

つまり、

「同じ操作をしても、同じ結果にならない」=インフラ運用としては、致命的欠陥状態です。

2.方針:latest + commit SHAタグ戦略

課題・問題をどう解決していくかを考えていく

1. ECRには毎回2つのタグをpushする

1つのイメージに対して、2つのタグをつける。

  • ecs-sample-app:<commit SHA>
  • ecs-sample-app:latest

ECRにはこんな感じで溜まっていくイメージ
image.png

2. ECSタスク定義ではSHAタグだけを参照する

ECSのタスク定義にはlatestではなくSHAの方を書きます。
image: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-sample-app:9f3a0b1c2d34...

latestはどうするのかというと、”表札”的な役割に限定する。

  • 人が「いまの最新イメージを手元でpullして確認したい」ときに使う
  • docker pull .../ecs-sample-app:latestで確認用

3. 何が嬉しいんだっけ?(メリットを整理)

1. 「どのコミットが動いているか」が一目でわかる

タスク定義のimageを見るだけでGitHubのコミットにジャンプできる
ecs-sample-app:9f3a0b1c2d34...

一発で追跡することができる

  • 誰がコミットしたか
  • どのファイルが変更されたか
  • 何を修正したリリースなのか

2. ECSのキャッシュ問題からの解放

ECSは「タグ名が同じ」だから挙動が分かりづらくなるのであって、
タグ名が完全にユニークなら話は別:bangbang:

ecs-sample-app:9f3a0b1c2d34...
ecs-sample-app:f2c30aa94cd8...

デプロイのたびに必ず違うタグを使うようにすることで

  • タスクごとにpullする/しないのブレが発生しない
  • 同じサービス内でバージョンが混在しない
  • 再デプロイ時勝手に違うものが動いたといったことが発生しない

3. ロールバックが圧倒的に簡単になる

  • 現在動いているのはecs-sample-app:f2c30aa9...
  • その前の安定版はecs-sample-app:9f3a0b1c...
    もし新しいデプロイでバグが出た場合は、以下のようなアクションをとることで、安全にロールバック
  • ECSのタスク定義を「1つ前のSHA」に戻す
  • もしくはGitHub ActionsなどのCI/CDから「過去のコミットを指定して再実行」

4. GitHub Actionsでの実装イメージ

YAML実装の一例

1. env:にSHA用タグを追加

env:
  AWS_REGION: ${{ secrets.AWS_REGION }}
  AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
  ECR_REPOSITORY: ecs-sample-app

  IMAGE_TAG_SHA: ${{ github.sha }}  # デプロイ対象タグ
  IMAGE_TAG: latest                 # 表札としての latest

2. Build:SHAタグで1回だけビルド

- name: Build Docker image
  run: |
    docker build -t $ECR_REPOSITORY:$IMAGE_TAG_SHA .

3. Tag:ECR用にSHAとlatestの2つを付ける

- name: Tag image for ECR
  run: |
    docker tag $ECR_REPOSITORY:$IMAGE_TAG_SHA \
      $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$IMAGE_TAG_SHA

    docker tag $ECR_REPOSITORY:$IMAGE_TAG_SHA \
      $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$IMAGE_TAG

4. Push:2つのタグをpush

- name: Push image to ECR
  run: |
    docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$IMAGE_TAG_SHA
    docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$IMAGE_TAG

5. ECSに渡すのはSHAだけ

- name: Set IMAGE_URI
  run: |
    IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$IMAGE_TAG_SHA"
    echo "IMAGE_URI=$IMAGE_URI" >> $GITHUB_ENV

6. update-serviceにつなげる

# 新しいタスク定義を登録し、その ARN を取得
- name: Register new task definition
  if: github.ref == 'refs/heads/main'
  run: |
    NEW_TASK_DEF_ARN=$(
      aws ecs register-task-definition \
        --cli-input-json file://new_taskdef.json \
        --query 'taskDefinition.taskDefinitionArn' \
        --output text
    )
    echo "NEW_TASK_DEF_ARN=$NEW_TASK_DEF_ARN" >> $GITHUB_ENV
    echo "Registered new task definition: $NEW_TASK_DEF_ARN"

# ECS サービスを新タスク定義で更新(ローリングデプロイ)
- name: Update ECS service
  if: github.ref == 'refs/heads/main'
  run: |
    echo "Updating ECS service $ECS_SERVICE on cluster $ECS_CLUSTER"

    aws ecs update-service \
      --cluster $ECS_CLUSTER \
      --service $ECS_SERVICE \
      --task-definition $NEW_TASK_DEF_ARN \
      --force-new-deployment

IMAGE_URIを使ってタスク定義のimageを差し替え、update-serviceにつなげれば、
常に"コミットごとのタグ"でデプロイされるECSになる。

5. まとめ

latestだけの運用は、一見楽ですが次のような問題を抱えている

  • どのバージョンで動いているか分からない
  • タスクごとにpullタイミングがずれてバージョン混在しうる
  • 同じタスク定義をapplyしても、同じものがデプロイされる保証ない

一方で、latest + commit SHAというタグ戦略を取ることで

  • 「どのコミットが本番で動いているか」が一目で分かる
  • ロールバックが簡単
  • 再現性のあるデプロイになる

「latestは人用、ECSはSHAを使う」:exclamation:

改めて、こうすることで感覚、推測での調査戻し作業が不要だったことから
はじめにタグ戦略ミスると保守・運用性の低下し困るのはチーム全体となる。ちゃんとしよう思う。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?