0
0

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タスク定義はSHA256ダイジェストでイメージを指定すべき理由と実践方法

Posted at

概要

ECSのタスク定義でイメージをタグ指定すると、同じタグで異なるイメージが実行される問題が発生します。特にlatestタグは予期しない動作の原因になります。SHA256ダイジェスト指定により、イメージ内容を一意に特定でき、確実なイミュータブルデプロイが実現できます。本記事では、その重要性と実践方法を解説します。

目次

  1. はじめに
  2. イメージタグ指定の問題点
  3. SHA256ダイジェストとは
  4. SHA256ダイジェスト指定のメリット
  5. SHA256ダイジェストの取得方法
  6. タスク定義でのSHA256ダイジェスト指定方法
  7. 実践的な運用フロー
  8. 注意点とトラブルシューティング
  9. 終わりに

1. はじめに

Amazon ECS(Elastic Container Service)でコンテナアプリケーションをデプロイする際、タスク定義にコンテナイメージを指定する必要があります。多くの開発者は myapp:v1.0myapp:latest といったタグでイメージを指定していますが、これは本番環境において重大な問題を引き起こす可能性があります。

本記事では、ECSのタスク定義においてSHA256ダイジェストを使用してイメージを指定すべき理由と、その実践方法について解説します。対象読者は、AWSの基本知識を持ち、AWS CLIの操作が可能で、ECSの基本的な使い方を理解している方を想定しています。

2. イメージタグ指定の問題点

タグの可変性による問題

コンテナイメージのタグは、ポインタのようなものです。同じタグ名で異なる内容のイメージを指し示すことができます。つまり、タグは**ミュータブル(可変)**な存在です。

例えば、myapp:v1.0 というタグが今日はイメージAを指していても、明日には同じタグ名で別のイメージBがECRにプッシュされる可能性があります。これは、開発プロセスにおいて意図的に行われることもあれば、誤操作によって発生することもあります。

latestタグの落とし穴

特に問題となるのが latest タグです。latestタグには以下のような誤解と問題があります。

latestタグは「最新」を保証しない

多くの開発者が「latest」という名前から「常に最新のイメージが取得できる」と誤解していますが、これは正しくありません。latestタグも他のタグと同様に、単なるポインタに過ぎません。新しいイメージをプッシュする際に明示的にlatestタグを付けない限り、latestタグは古いイメージを指し続けます。

明示的指定と省略の違い

タスク定義で myapp:latest と明示的に指定する場合と、タグを省略して myapp とだけ指定する場合、どちらもDockerはlatestタグを参照しますが、意図が明確になるため、明示的な指定が推奨されます。

本番環境でlatestタグを避けるべき理由

本番環境でlatestタグを使用すると、以下のような問題が発生します:

  • どのバージョンのコードが実行されているか追跡困難
  • ロールバック時に正確な過去のバージョンを特定できない
  • 開発環境で動作確認したイメージと本番で実行されるイメージが異なる可能性
  • 複数のタスク間で異なるバージョンが混在するリスク

実際に起こりうるトラブル事例

AWSは2024年に「ソフトウェアバージョン一貫性」機能をECSに導入しました。この機能により、ECSはタスク定義で指定されたタグを最初のタスク起動時にSHA256ダイジェストに解決し、その後のタスクは同じダイジェストのイメージを使用するようになりました。

しかし、これにより新たな問題が発生しています。AWS re:Post ( https://repost.aws/questions/QU6Seb7jaHSSiATxE7fJnQ2w/aws-ecs-fargate-is-using-digest-instead-of-image-tag ) では、『latestタグで新しいイメージをECRにプッシュした後、ECSサービスが起動しない』という報告があります。

これは、ECSが以前のデプロイ時に解決したダイジェストを記憶しており、そのイメージがECRから削除されている場合、以下のようなエラーが発生するためです:

CannotPullContainerError: pull image manifest has been retried 1 time(s): 
failed to resolve ref xxxx.dkr.ecr.region.amazonaws.com/repo@sha256:xxxxx: 
not found

ローリングデプロイ時のイメージ混在リスク

ローリングデプロイ中に新しいイメージが同じタグでプッシュされた場合、以下のような状況が発生する可能性があります:

  1. 既存のタスクは古いイメージで実行中
  2. 新しいタスクは更新されたイメージで起動
  3. 同じサービス内で異なるバージョンのコードが混在
  4. ユーザーのリクエストが両方のバージョンに振り分けられる

この状況は、データベーススキーマの変更を伴うデプロイや、APIの互換性のない変更を含むリリースにおいて、深刻な問題を引き起こします。

image.png

3. SHA256ダイジェストとは

SHA256ダイジェストの基本概念

SHA256ダイジェストは、コンテナイメージがビルドされた際に、そのイメージの内容(マニフェスト)から生成される64文字の16進数文字列です。これは、イメージ内容のチェックサムであり、イメージ内容が1バイトでも変わると、まったく異なるダイジェスト値になります。

形式は以下のようになります:

sha256:4a1c6567c38904384ebc64e35b7eeddd8451110c299e3368d2210066487d97e5

イメージの一意性の保証

SHA256ダイジェストは、コンテナイメージの**イミュータブル(不変)**な識別子です。同じダイジェスト値を持つ2つのイメージは、必ず同じ内容であることが暗号学的に保証されています。

これは、家の住所のようなものです。タグは「田中家」のような名札で、同じ名前の家が複数存在したり、引っ越しで変わったりしますが、住所(ダイジェスト)は一意で変わりません。

タグ(latest含む)とダイジェストの違い

以下の表で、タグとダイジェストの特性を比較します:

特性 タグ(tag) ダイジェスト(digest)
可変性 ミュータブル(変更可能) イミュータブル(不変)
一意性 同じタグで異なる内容のイメージが存在可能 同じダイジェストは常に同じイメージ内容
可読性 人間にとって理解しやすい(v1.0、latestなど) 人間には読みにくい(64文字のハッシュ値)
追跡性 バージョン管理は可能だが、内容の保証なし 完全なトレーサビリティ
セキュリティ タグの上書きによる攻撃リスクあり 内容の改ざんが検出可能
latestタグ 「最新」を保証しない、混乱の原因になりやすい N/A

AWS公式ドキュメント「Amazon ECS task definition parameters for Fargate」( https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html ) では、ECRのイメージは registry/repository:tag または registry/repository@digest の形式で指定できることが説明されています。

4. SHA256ダイジェスト指定のメリット

イミュータブルなデプロイの実現

SHA256ダイジェストを使用することで、真のイミュータブルデプロイが実現できます。タスク定義のリビジョンごとに、実行されるイメージ内容が完全に固定されます。

これにより、「開発環境で動作確認したイメージ」と「本番環境で実行されるイメージ」が確実に同一であることが保証されます。

トレーサビリティの向上

各タスク定義リビジョンが特定のダイジェストに紐づくため、以下の追跡が可能になります:

  • いつどのイメージがデプロイされたか
  • 現在稼働中のタスクが使用している正確なイメージ内容
  • 問題発生時に、どのコードバージョンが実行されていたか

監査要件が厳しい業界や、規制対応が必要なシステムにおいて、この完全なトレーサビリティは重要な要件となります。

予期しないイメージ実行の防止

タグの上書きやECRライフサイクルポリシーによる意図しないイメージ削除から保護されます。ダイジェストで指定されたイメージが存在しない場合、タスクは起動に失敗するため、誤ったバージョンが実行されることはありません。

リビジョンごとの明確な紐付け

ECSサービスのデプロイ履歴を見たとき、各リビジョンで使用されたイメージが明確になります。これにより、ロールバックや問題調査が容易になります。

5. SHA256ダイジェストの取得方法

AWS CLIでの取得コマンド

ECRに格納されているイメージのダイジェストを取得する最も一般的な方法は、aws ecr describe-images コマンドを使用することです。

aws ecr describe-images \
  --repository-name myapp \
  --image-ids imageTag=v1.0 \
  --region ap-northeast-1 \
  --query 'imageDetails[0].imageDigest' \
  --output text

出力例:

sha256:4a1c6567c38904384ebc64e35b7eeddd8451110c299e3368d2210066487d97e5

複数のイメージ情報を一覧で取得する場合:

aws ecr describe-images \
  --repository-name myapp \
  --region ap-northeast-1 \
  --query 'imageDetails[*].[imageTags[0],imageDigest]' \
  --output table

docker inspectでの取得方法

ローカル環境でビルドしたイメージのダイジェストを確認する場合は、docker inspect コマンドが使えます:

docker inspect --format='{{index .RepoDigests 0}}' myapp:v1.0

ただし、これは既にレジストリにプッシュされたイメージでないと、RepoDigests情報が含まれません。ローカルでビルドしただけのイメージには、レジストリのダイジェスト情報は付与されていないため、まずECRにプッシュする必要があります。

CI/CDパイプラインでの自動取得例

CodeBuildなどのCI/CDパイプラインでは、イメージプッシュ後に自動的にダイジェストを取得して、次のステップに渡すことができます:

# イメージをビルド
docker build -t myapp:v1.0 .

# ECRにプッシュ
docker tag myapp:v1.0 ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:v1.0
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:v1.0

# プッシュ直後にダイジェストを取得
IMAGE_DIGEST=$(aws ecr describe-images \
  --repository-name myapp \
  --image-ids imageTag=v1.0 \
  --region ap-northeast-1 \
  --query 'imageDetails[0].imageDigest' \
  --output text)

echo "Image digest: ${IMAGE_DIGEST}"

6. タスク定義でのSHA256ダイジェスト指定方法

JSON形式でのタスク定義例

タスク定義JSONファイルでは、image フィールドに repository@digest の形式でダイジェストを指定します:

{
  "family": "myapp",
  "containerDefinitions": [
    {
      "name": "app",
      "image": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/myapp@sha256:4a1c6567c38904384ebc64e35b7eeddd8451110c299e3368d2210066487d97e5",
      "memory": 512,
      "cpu": 256,
      "essential": true,
      "portMappings": [
        {
          "containerPort": 8080,
          "protocol": "tcp"
        }
      ]
    }
  ],
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "256",
  "memory": "512"
}

タグとダイジェストの併用も可能です。この場合、ECSはダイジェストを優先しますが、人間にとってはタグが可読性を提供します:

"image": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:v1.0@sha256:4a1c6567c38904384ebc64e35b7eeddd8451110c299e3368d2210066487d97e5"

AWS CLIでの登録コマンド

タスク定義を登録するコマンド例:

aws ecs register-task-definition \
  --cli-input-json file://task-definition.json \
  --region ap-northeast-1

シェルスクリプトで動的に生成する場合:

#!/bin/bash

REPOSITORY_NAME="myapp"
IMAGE_TAG="v1.0"
AWS_ACCOUNT_ID="123456789012"
REGION="ap-northeast-1"

# ダイジェストを取得
IMAGE_DIGEST=$(aws ecr describe-images \
  --repository-name ${REPOSITORY_NAME} \
  --image-ids imageTag=${IMAGE_TAG} \
  --region ${REGION} \
  --query 'imageDetails[0].imageDigest' \
  --output text)

# イメージURIを構築
IMAGE_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPOSITORY_NAME}:${IMAGE_TAG}@${IMAGE_DIGEST}"

# タスク定義JSONを生成して登録
cat << EOF | aws ecs register-task-definition --cli-input-json file:///dev/stdin --region ${REGION}
{
  "family": "myapp",
  "containerDefinitions": [
    {
      "name": "app",
      "image": "${IMAGE_URI}",
      "memory": 512,
      "cpu": 256,
      "essential": true
    }
  ],
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "256",
  "memory": "512"
}
EOF

マネジメントコンソールでの設定

AWSマネジメントコンソールからタスク定義を作成する場合:

  1. ECSコンソールで「タスク定義」→「新しいタスク定義の作成」を選択
  2. コンテナの定義セクションで「イメージ」フィールドに以下の形式で入力:
    123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/myapp@sha256:4a1c6567...
    
  3. その他の設定を行い、「作成」をクリック

コンソールでは、イメージURIの入力時に自動補完機能がありませんので、事前にダイジェストをコピーしておくことをおすすめします。

7. 実践的な運用フロー

CI/CDパイプラインへの組み込み

以下は、CodePipelineとCodeBuildを使用した実践的なデプロイフローの例です:

buildspec.ymlの例

version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH}
  build:
    commands:
      - echo Build started on `date`
      - docker build -t ${REPOSITORY_NAME}:${IMAGE_TAG} .
      - docker tag ${REPOSITORY_NAME}:${IMAGE_TAG} ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY_NAME}:${IMAGE_TAG}
  post_build:
    commands:
      - echo Pushing the Docker image...
      - docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY_NAME}:${IMAGE_TAG}
      - echo Retrieving image digest...
      - IMAGE_DIGEST=$(aws ecr describe-images --repository-name ${REPOSITORY_NAME} --image-ids imageTag=${IMAGE_TAG} --region ap-northeast-1 --query 'imageDetails[0].imageDigest' --output text)
      - echo Image digest is ${IMAGE_DIGEST}
      - echo Creating task definition...
      - |
        cat << EOF > taskdef.json
        {
          "family": "myapp",
          "containerDefinitions": [
            {
              "name": "app",
              "image": "${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY_NAME}:${IMAGE_TAG}@${IMAGE_DIGEST}",
              "memory": 512,
              "cpu": 256,
              "essential": true,
              "portMappings": [{"containerPort": 8080}]
            }
          ],
          "requiresCompatibilities": ["FARGATE"],
          "networkMode": "awsvpc",
          "cpu": "256",
          "memory": "512"
        }
        EOF
      - aws ecs register-task-definition --cli-input-json file://taskdef.json --region ap-northeast-1
      - NEW_REVISION=$(aws ecs describe-task-definition --task-definition myapp --region ap-northeast-1 --query 'taskDefinition.revision' --output text)
      - echo Updating service with new task definition revision ${NEW_REVISION}
      - aws ecs update-service --cluster my-cluster --service myapp --task-definition myapp:${NEW_REVISION} --region ap-northeast-1

artifacts:
  files:
    - taskdef.json

image.png

リビジョン管理のベストプラクティス

  1. Gitコミットハッシュをイメージタグに使用

    • v1.0 のような抽象的なタグではなく、Gitコミットハッシュの短縮形(7文字)をタグに使用することで、コードとイメージの紐付けが明確になります
    • 例:myapp:a1b2c3d
  2. タスク定義にはタグとダイジェストの両方を記載

    • 可読性のためにタグを残しつつ、正確性のためにダイジェストを指定
    • 例:myapp:a1b2c3d@sha256:4a1c6567...
  3. タスク定義のリビジョンごとに新規登録

    • 既存のタスク定義を更新するのではなく、常に新しいリビジョンとして登録
    • これにより、デプロイ履歴が明確に残ります
  4. タスク定義JSONをGitで管理

    • デプロイされたタスク定義の内容をGitリポジトリで管理
    • これにより、インフラの変更履歴も追跡可能になります

ロールバック時の対応

ダイジェスト指定の利点は、ロールバックが確実に行えることです:

# 以前のタスク定義リビジョンを確認
aws ecs list-task-definitions \
  --family-prefix myapp \
  --region ap-northeast-1 \
  --sort DESC \
  --max-items 5

# 特定のリビジョンにロールバック
aws ecs update-service \
  --cluster my-cluster \
  --service myapp \
  --task-definition myapp:42 \
  --region ap-northeast-1

タスク定義リビジョン42で指定されていたダイジェストのイメージが、確実に実行されます。

8. 注意点とトラブルシューティング

ダイジェスト指定時の注意点

  1. ECRライフサイクルポリシーとの整合性

    • ダイジェストで指定したイメージがECRライフサイクルポリシーで削除されないよう注意が必要です
    • 本番環境で使用中のイメージは、タグが付いていなくても保持するポリシー設定を推奨します
  2. イメージの保持期間

    • ロールバックに備えて、少なくとも過去3〜5世代のイメージは保持する
    • 監査要件によっては、より長期間の保持が必要な場合があります
  3. マルチアーキテクチャイメージ

    • ARM64とAMD64の両方をサポートするマニフェストリストを使用している場合、ダイジェストはマニフェストリスト全体を指します
    • 各アーキテクチャの個別イメージのダイジェストではありません

イメージが見つからない場合の対処法

以下のエラーが発生した場合:

CannotPullContainerError: pull image manifest has been retried 1 time(s): 
failed to resolve ref account-id.dkr.ecr.region.amazonaws.com/repo@sha256:xxxxx: not found

原因と対処法

  1. イメージが削除されている

    • ECRリポジトリでイメージの存在を確認:
      aws ecr describe-images \
        --repository-name myapp \
        --image-ids imageDigest=sha256:xxxxx \
        --region ap-northeast-1
      
    • イメージが存在しない場合は、該当バージョンを再ビルドするか、別のリビジョンにロールバック
  2. リージョンが異なる

    • タスク定義で指定したECRリポジトリのリージョンと、ECSクラスターのリージョンが一致しているか確認
  3. IAM権限不足

    • タスク実行ロールに、ECRからイメージをプルする権限があるか確認:
      {
        "Effect": "Allow",
        "Action": [
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage",
          "ecr:BatchCheckLayerAvailability"
        ],
        "Resource": "*"
      }
      

タグとダイジェストの併用について

AWS公式ドキュメント「Use latest container image digest during Amazon ECS deployments」( https://repost.aws/knowledge-center/ecs-latest-image-digest ) によると、タグとダイジェストを併用した場合の挙動は以下のとおりです:

  • ECSはダイジェストを優先して使用します
  • タグは人間の可読性のために残されますが、実際のイメージ選択には影響しません
  • 形式:repository:tag@digest

この併用方式により、運用上のメリットと技術的な正確性の両方が得られます。

ECR Tag Immutabilityとの組み合わせ

ECRリポジトリのタグイミュータビリティ機能と組み合わせることで、さらに堅牢な運用が可能です。2025年7月からAWS ECRは、特定のタグ(例:latest)を除外してイミュータブルに設定できる機能をサポートしています:

aws ecr put-image-tag-mutability \
  --repository-name myapp \
  --image-tag-mutability IMMUTABLE_WITH_EXCLUSION \
  --image-tag-mutability-exclusion-filters filterType=WILDCARD,filter=latest \
  --region ap-northeast-1

この設定により、本番用のバージョンタグ(v1.0、v1.1など)はイミュータブルに、開発用のlatestタグはミュータブルに保つことができます。

9. 終わりに

まとめ

本記事では、ECSタスク定義におけるSHA256ダイジェスト指定の重要性と実践方法について解説しました。重要なポイントをまとめます:

  • タグは可変:特にlatestタグは「最新」を保証せず、予期しない動作の原因になる
  • ダイジェストは不変:SHA256ダイジェストは、イメージ内容を一意に特定し、イミュータブルデプロイを実現
  • 運用上のメリット:完全なトレーサビリティ、確実なロールバック、監査対応の容易さ
  • 実装方法:AWS CLIでダイジェストを取得し、タスク定義に repository:tag@digest 形式で指定
  • CI/CD統合:ビルドパイプラインに組み込むことで、自動化された安全なデプロイが実現

AWSブログ「Announcing software version consistency for Amazon ECS services」では、2024年にECSがソフトウェアバージョン一貫性機能を導入したことが説明されています。しかし、この機能があっても、最初から明示的にダイジェストを指定することで、より確実で予測可能なデプロイが実現できます。

次のステップ

ECSでのダイジェスト指定を実践するために、以下のステップをおすすめします:

  1. 既存のCI/CDパイプラインの見直し

    • 現在のビルドプロセスでダイジェスト取得を追加
    • タスク定義登録時にダイジェスト指定に切り替え
  2. ECRライフサイクルポリシーの最適化

    • 本番環境で使用中のイメージが削除されないようポリシーを設定
    • 適切なイメージ保持期間の設定
  3. タグイミュータビリティの有効化

    • ECRリポジトリでタグイミュータビリティ機能を有効化
    • 開発用タグ(latest)は除外設定を検討
  4. モニタリングとアラートの設定

    • イメージプル失敗のアラートを設定
    • デプロイ履歴とイメージバージョンの追跡体制を整備

SHA256ダイジェストを使用したイメージ指定は、初期設定に少し手間がかかりますが、長期的な運用の安定性と信頼性を大幅に向上させます。本番環境でのデプロイに不安を感じている方は、ぜひこの方法を検討してみてください。

参考文献・参考サイト

AWS公式ドキュメント

技術記事・コミュニティリソース

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?