電通総研 テックブログ

電通総研が運営する技術ブログ

コンテナの概要から掴むECRのリポジトリ作成テンプレートの紹介

はじめに

金融IT本部 2年目の坂江 克斗です。
今回はECRリポジトリ作成テンプレート機能について、2025年12月にプッシュ時の自動リポジトリ作成がサポート開始となりましたので、新機能を含めたテンプレート機能全体を検証し紹介します。
ベースの概念から含めて丁寧に解説できればと思います。

コンテナの概要

コンテナの概要に関して、ざっくりと説明します。

コンテナとは

コンテナ技術とは、Linux カーネルが持つ機能(namespaceやcgroup)を利用して、1つのOS上に論理的に隔離された実行環境を作成する技術を指します。この隔離された実行環境そのものをコンテナと呼びます。

コンテナ内で動作しているのは仮想マシンとは異なり、ホストOS上で動作する通常のプロセス(プログラム単位) です。
プロセスが属する namespace によって、プロセス・ファイルシステム・ネットワーク・Linuxユーザやグループの権限等を分離・隔離し、cgroupによってコンテナのプロセスが使用するリソース(CPU/メモリ)を制限することで、1つのOS上に存在しながら、独立した OS のように振る舞う環境が実現されています。

コンテナに関するリソースの管理や操作を行うOSSを、コンテナランタイムと呼びます。
このうち、Linux カーネルの機能を直接利用してコンテナの作成・起動・削除といった処理を実行するものを低レベルのコンテナランタイム(runc、gVisor、kata-containers)と呼びます。
一方で、イメージの管理やコンテナのライフサイクル管理を担い、コンテナの作成・削除といったリクエストを低レベルランタイムに委譲するものを高レベルのコンテナランタイム(containerdやCRI-O)と呼びます。

コンテナに関する基本用語

コンテナを扱ううえで、以下の用語が頻出します。

用語 説明
イメージ(Image) コンテナを実行するために必要なファイルシステムメタデータをまとめたもの。
レジストリ(Registry) コンテナイメージを保管・配布するためのサービス。
API による認証・認可やイメージ管理、保管・配布基盤などを含めた サービス全体 を指す。
リポジトリ(Repository) レジストリ内に作成される イメージの論理的な格納単位
通常はサービスやアプリケーション単位で作成し使用される。
タグ(Tag) イメージに付与されるバージョンや識別子として使用。

現在、コンテナに関する形式は Open Container Initiative(OCI)規格により定義されています。
OCI では、コンテナを実行するための低レベルなコンテナランタイムに加え、イメージおよびレジストリの仕様について標準化が行われています。

この標準化により、Docker に限らずさまざまなコンテナランタイムやレジストリ(Docker Hub、ECR、GitHub)で互換性が保たれており、私たちは内部実装の違いを意識することなくコンテナイメージを利用することができます。

Amazon ECR の概要

Amazon Elastic Container Registry(ECR)は、 AWS が提供する マネージドなコンテナレジストリサービスです。
ECR には、AWSアカウント毎に作成され、IAM によるアクセス制御が可能な プライベートレジストリと、イメージの取得(pull)を誰でも行うことができる パブリックレジストリの2 種類が存在します。

実際にアプリケーションをデプロイする際には、プライベートレジストリを使用してアプリケーションイメージをセキュアに管理・運用するケースが一般的です。一方で、パブリックレジストリはベースイメージの取得(踏み台サーバ用のAmazon Linuxイメージなど)といった、公開されているイメージを参照する用途で利用されることが多いです。

ECR には、レジストリとしての基本的機能だけでなく、イメージの脆弱性スキャンライフサイクル管理プルスルーキャッシュレプリケーション そして リポジトリ作成テンプレートなど、コンテナイメージ管理を効率化するためのさまざまな機能が用意されています。

本記事では、ECR が提供する機能のうち リポジトリ作成テンプレート機能に焦点を当て、その概要と活用方法について紹介します。

ECRに関してもっと詳しく知りたい方向けに、本記事の最後にECRの内部アーキテクチャや技術的な仕組みを解説した章を追加しましたので、ぜひご参照ください。

リポジトリ作成テンプレートの概要

名前の通り、本機能は ユーザの代わりに Amazon ECR 自身がリポジトリを作成する際に、そのリポジトリに適用される設定をテンプレートとして定義 できる機能です。

従来、ECR においてリポジトリが自動的に作成されるタイミングは、プルスルーキャッシュレプリケーション 機能を有効化した場合に限られていました。
しかし、2025年12月のアップデートにより、リポジトリ作成テンプレート機能の拡張としてpush 時にリポジトリを自動作成する機能が追加されました。

具体的にテンプレートに設定する項目は、以下となります。

  • 適用対象
    • Create on pushPull through cacheReplicationのいずれか、または複数を指定。
    • リポジトリ名のプレフィクス。
      • プレフィクスを指定せず、他のリポジトリ作成テンプレートに該当しない全リポジトリ を対象とすることも可能。
  • イメージタグ設定
    • Mutable(タグの上書き可能)もしくはImmutable(タグの上書き不可)を指定。
    • 上記設定から除外するタグを指定するための、フィルター用プレフィクス。
  • 暗号化設定
    • AES-256もしくはKMSを指定。
    • KMSの場合は、AWSマネージドキーもしくはカスタマーマネージドキー(CMK)を指定。
  • リポジトリ権限
    • リポジトリ単位で適用するIAMリソースポリシーを設定。
  • ライフサイクルポリシー
  • リポジトリ AWS タグ
    • イメージタグではなく、ECRリポジトリ自体に付与したいAWSリソース用のタグを指定。
  • リポジトリ作成ロール
    • 未定義の場合、サービスにリンクされたロールを自動的に使用。
    • ただし、上記のロールはecr:CreateRepository権限しかないため、ケースごとに以下の権限を追加付与したカスタムロールの作成が必要。(AWSが提案するポリシー
      • KMS キーを使用する場合:対象の KMS キーに対する権限
      • リソースタグを付与する場合:ecr:TagResource 権限

プルスルーキャッシュの概要

プルスルーキャッシュは、特定の プライベートECRリポジトリをキャッシュとして、オリジンとなるパブリックレジストリや別のプライベート ECRからECR自身がコンテナイメージを取得し、必要に応じてプライベート ECR リポジトリを自動作成したうえで、そのイメージを保存する機能です。

pull元のECSタスク等は、プライベートECRに対してイメージをpullするだけで、シームレスに利用できるようになります。

この仕組みにより、インターネットへの直接通信ができない ECS タスクであっても、追加で NAT Gateway をデプロイするなどネットワーク要件に影響を与える対応を行うことなく、ECR を経由した 外部のパブリックイメージのセキュアな取得 が可能となります。

注意点としては、イメージをキャッシュしてから再度イメージの最新をアップストリームレジストリに確認する周期が、少なくとも24時間に1回となっています。そのため、頻繁に更新されるイメージの最新を同期して使用したい場合には少し不便かもしれません。

プルスルーキャッシュの設定において特に重要な項目は、以下の 2 点です。

  • アップストリームレジストリ
    • キャッシュとして利用するプライベートECRに対する、オリジンとして使用する(上位の)レジストリを指します。
      • パブリックECR、プライベートECR、Docker Hub、Quay.io、KubernetesGitHub Container Registry、Microsoft Azure Container Registry、GitLab Container Registryのいずれかを指定。
  • リポジトリ名プレフィクス
    • ECRへのイメージpullリクエストが発生した際に、キャッシュリポジトリ作成をトリガーするリポジトリ名のプレフィクス。

ECR自身が、他アカウントのプライベートECRにアクセス、もしくはキャッシュ用のリポジトリを作成することから、IAM権限が必要となりますが、サービスにリンクされたロールが自動で付与されるため、ユーザ側での管理は必要ありません。
しかし、pull元のECSタスク等に対してはecr:CreateRepositoryecr:BatchImportUpstreamImage権限を明示的に付与する必要があります。

直感的には、ECR側に付与されたロールがこれらの権限を持つイメージですが、実際にはイメージを利用する側(ECS タスク等)のロール単位でプルスルーキャッシュの利用可否を制御できる設計となっています。

レプリケーションの概要

レプリケーションは、特定のプライベートECRリポジトリに pushされたイメージを、別のリージョンやAWSアカウントに存在するプライベートECRリポジトリへ自動的に複製(レプリケート) する機能です。
レプリケーション先にリポジトリが存在しない場合は、自動的にリポジトリが作成されます。

レプリケーションの設定において特に重要な項目は、以下の 2 点です。

プルスルーキャッシュ同様に、ECR自身に付与するIAM権限が必要となりますが、サービスにリンクされたロールが自動で付与されるため、ユーザ側での管理は必要ありません。
ただし、クロスアカウントの場合には、レプリケーション元のアカウントがレプリケーション先にイメージを複製するために、レプリケーション先のレジストリ側に適切な権限設定が必要となります。

リポジトリ作成テンプレートの検証

前提

  • 検証のためローカルで実装します。
  • 以下の構成を使用します。
    • プライベートサブネットのみデプロイし、ECSタスクからはVPCエンドポイントを経由してECRにアクセス。
    • クライアントはAWS CLIおよびdocker CLIを使用して、ECRにアクセス。

  • 以下の機能を設定します。
    • プルスルーキャッシュ
      • example-cache/というプレフィクスのECRリポジトリに対して、パブリックECR(public.ecr.aws)をアップストリームレジストリとして設定。
      • ECSタスクはこのリポジトリにpullをリクエストします。
    • レプリケーション
    • リポジトリ作成テンプレート
      • プッシュ時
        • example-create-on-push/というプレフィクスのECRリポジトリに対して、タグなしイメージを14日後に削除するライフサイクルを設定。
      • プルスルーキャッシュ
        • example-cache/というプレフィクスのECRリポジトリに対して、タグなしイメージを140日後に削除するライフサイクルを設定。
      • レプリケーション
        • example-replication/というプレフィクスのECRリポジトリに対して、タグなしイメージを365日後に削除するライフサイクルを設定。
        • ※ ただし、レプリケーション先のリポジトリus-east-1リージョンに作成されるため、本テンプレートのみus-east-1リージョンに作成。

実装

基本設定およびVPCなどのネットワークに関する定義をします。
ECSからVPCエンドポイント経由でECRにアクセスするため、最低限3つの VPC エンドポイント(または到達可能なエンドポイント)が必要となります。構成図に示したとおり、その内訳は以下となります。

  • s3
    • イメージレイヤーの実体の保存場所。
  • dkr.ecr
    • docker pull等、OCI準拠のレジストリアクセスを行うためのエンドポイント。
    • レジストリのホスト名が<アカウントID>.dkr.ecr.<リージョン名>.amazonaws.comという形式であることからも分かるように、このエンドポイントを名前解決可能な状態で利用する必要があります。
  • api.ecr
    • ECR用のAWS APIを使用する際のエンドポイント。
terraform {
  required_version = "~> 1.14.0"
  required_providers {
    aws = {
      version = "6.28.0"
      source  = "hashicorp/aws"
    }
  }
}

provider "aws" {
  region = local.regions.primary
}


locals {
  account_id = "<アカウントID>"
  regions = {
    primary   = "ap-northeast-1"
    secondary = "us-east-1"
  }
  availability_zones = {
    primary = ["${local.regions.primary}a", "${local.regions.primary}c", "${local.regions.primary}d"]
  }
}

data "aws_caller_identity" "current" {}

###########################################################################################
# VPC
###########################################################################################
## VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Project = "example"
  }
}

## Subnet
resource "aws_subnet" "private_a" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.2.0/24"
  availability_zone       = local.availability_zones.primary[0]
  map_public_ip_on_launch = false

  tags = {
    Project = "example"
  }
}

## Route Table
resource "aws_route_table" "private_a" {
  vpc_id = aws_vpc.main.id

  tags = {
    Project = "example"
  }
}
resource "aws_route_table_association" "private_a" {
  subnet_id      = aws_subnet.private_a.id
  route_table_id = aws_route_table.private_a.id
}

## VPC Endopoint
### https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint.html
resource "aws_vpc_endpoint" "s3" {
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.${local.regions.primary}.s3"
  vpc_endpoint_type = "Gateway"

  route_table_ids = [
    aws_route_table.private_a.id,
  ]

  tags = {
    Project = "example"
  }
}
resource "aws_vpc_endpoint" "ecr_dkr" {
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.${local.regions.primary}.ecr.dkr"
  vpc_endpoint_type = "Interface"
  subnet_ids        = [aws_subnet.private_a.id]
  security_group_ids = [
    aws_security_group.vpce.id,
  ]
  private_dns_enabled = true

  tags = {
    Project = "example"
  }
}
resource "aws_vpc_endpoint" "ecr_api" {
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.${local.regions.primary}.ecr.api"
  vpc_endpoint_type = "Interface"
  subnet_ids        = [aws_subnet.private_a.id]
  security_group_ids = [
    aws_security_group.vpce.id,
  ]
  private_dns_enabled = true

  tags = {
    Project = "example"
  }
}

## Security Group
resource "aws_security_group" "vpce" {
  name   = "vpce-sg"
  vpc_id = aws_vpc.main.id

  tags = {
    Project = "example"
  }
}
resource "aws_vpc_security_group_ingress_rule" "ingress_from_ecs" {
  security_group_id = aws_security_group.vpce.id

  from_port                    = 443
  to_port                      = 443
  ip_protocol                  = "tcp"
  referenced_security_group_id = aws_security_group.ecs.id
}

ECSの定義を行います。以下の項目に注意してください。

  • 変数の定義は後程記載しますが、public.ecr.aws/nginx/nginx:tagのイメージをプルスルーキャッシュにより取得。
    • イメージURIとしては<プライベートECRのホスト名>/<プルスルーキャッシュ条件のプレフィクス名>/nginx/nginx:tagを指定。
  • ECSの実行ロールに、プルスルーキャッシュ利用に必要な権限を付与。
    • ecr:CreateRepositoryecr:BatchImportUpstreamImage
  • ECSからS3へ通信できるように、ECSのセキュリティグループのルールを設定。
## Cluster
resource "aws_ecs_cluster" "example" {
  name = "example-cluster"
  tags = {
    Project = "example"
  }
}
## Service
resource "aws_ecs_service" "example" {
  name            = "example-service"
  cluster         = aws_ecs_cluster.example.name
  launch_type     = "FARGATE"
  desired_count   = 1
  task_definition = aws_ecs_task_definition.nginx.arn

  network_configuration {
    subnets         = [aws_subnet.private_a.id]
    security_groups = [aws_security_group.ecs.id]
  }

  tags = {
    Project = "example"
  }
}

## Task
resource "aws_ecs_task_definition" "nginx" {
  family                   = "example-nginx"
  requires_compatibilities = ["FARGATE"]
  cpu          = 256
  memory       = 512
  network_mode = "awsvpc"

  execution_role_arn = aws_iam_role.ecs_exec.arn
  task_role_arn      = aws_iam_role.ecs.arn


  container_definitions = <<EOL
[
  {
    "name": "nginx",
    "image": "${local.cached_image_uri}",
    "memory": 512,
    "cpu": 256,
    "essential": true,
    "portMappings": [
      {
        "containerPort": 80,
        "hostPort": 80,
        "protocol": "tcp"
      }
    ]
  }
]
EOL

  tags = {
    Project = "example"
  }
}
## Security Group
resource "aws_security_group" "ecs" {
  name   = "ecs-sg"
  vpc_id = aws_vpc.main.id

  tags = {
    Project = "example"
  }
}
resource "aws_vpc_security_group_egress_rule" "egress_to_vpce" {
  security_group_id = aws_security_group.ecs.id

  from_port                    = 443
  to_port                      = 443
  ip_protocol                  = "tcp"
  referenced_security_group_id = aws_security_group.vpce.id
}
data "aws_ec2_managed_prefix_list" "s3" {
  name = "com.amazonaws.${local.regions.primary}.s3"
}
resource "aws_vpc_security_group_egress_rule" "egress_to_s3_ip" {
  security_group_id = aws_security_group.ecs.id

  from_port      = 443
  to_port        = 443
  ip_protocol    = "tcp"
  prefix_list_id = data.aws_ec2_managed_prefix_list.s3.id
}


## IAM 
resource "aws_iam_role" "ecs" {
  name = "ecs-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Service = "ecs-tasks.amazonaws.com"
      }
      Action = "sts:AssumeRole"
    }]
  })
}
resource "aws_iam_role" "ecs_exec" {
  name = "ecs-exec-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Service = "ecs-tasks.amazonaws.com"
      }
      Action = "sts:AssumeRole"
    }]
  })
}
resource "aws_iam_policy" "ecr_cache" {
  name = "ecr-cache"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowEcrPullThroughCache"
        Effect = "Allow"
        Action = [
          "ecr:CreateRepository",
          "ecr:BatchImportUpstreamImage"
        ]
        Resource = "*"
      }
    ]
  })
}
resource "aws_iam_role_policy_attachment" "ecr_cache" {
  role       = aws_iam_role.ecs_exec.name
  policy_arn = aws_iam_policy.ecr_cache.arn
}
resource "aws_iam_role_policy_attachment" "ecs_exec" {
  role       = aws_iam_role.ecs_exec.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

プルスルーキャッシュおよびレプリケーションを定義します。

また、イメージURIなどについては、可能な限り共通で再利用可能な式として変数定義していますが、値としては以下に示すようなシンプルなものになっています。

  • ECSが本来取得したいアップストリームレジストリのイメージURI
    • origin_image_uripublic.ecr.aws/nginx/nginx:1.29-alpine
  • アップストリームレジストリのホスト名
    • origin_registry_hostpublic.ecr.aws
  • アップストリームレジストリのイメージパス
    • origin_image_pathnginx/nginx:1.29-alpine
  • プライベートECRのホスト名
    • cached_registry_host<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com
  • ECSタスクが参照するプライベートECRリポジトリ(キャッシュイメージの保存先)
    • cached_image_uri<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/example-cache/nginx/nginx:1.29-alpine
locals {
  ## pull_through_cache
  origin_image_uri          = "public.ecr.aws/nginx/nginx:1.29-alpine"
  origin_registry_host      = split("/", local.origin_image_uri)[0]                                 
  origin_image_path         = replace(local.origin_image_uri, "${local.origin_registry_host}/", "") 
  pull_through_cache_prefix = "example-cache"
  cached_registry_host      = "${local.account_id}.dkr.ecr.${local.regions.primary}.amazonaws.com"                         
  cached_image_uri          = "${local.cached_registry_host}/${local.pull_through_cache_prefix}/${local.origin_image_path}" 

  ## Replication
  replication_filter_prefix = "example-replication" 

  ## create on push
  create_on_push_prefix = "example-create-on-push" 
}

## pull through cache
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_pull_through_cache_rule
resource "aws_ecr_pull_through_cache_rule" "example" {
  ecr_repository_prefix = local.pull_through_cache_prefix
  upstream_registry_url = local.origin_registry_host
}

## replication 
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_replication_configuration
resource "aws_ecr_repository" "example_replication" {
  name                 = "example-replication/nginx/nginx"
  image_tag_mutability = "MUTABLE"
  tags = {
    Project = "example"
  }
}
resource "aws_ecr_replication_configuration" "example" {
  replication_configuration {
    rule {
      destination {
        region      = local.regions.secondary
        registry_id = local.account_id
      }
      repository_filter {
        filter      = local.replication_filter_prefix
        filter_type = "PREFIX_MATCH"
      }
    }
  }
}

リポジトリ作成テンプレートを以下の設定で定義します。

  • プッシュ時
    • リポジトリ名プレフィクス:example-create-on-push/
    • タグなしイメージを14日後に削除するライフサイクルを設定。
  • プルスルーキャッシュ
    • リポジトリ名プレフィクス:example-cache/
    • タグなしイメージを140日後に削除するライフサイクルを設定。
  • レプリケーション
    • リポジトリ名プレフィクス:example-replication/
    • タグなしイメージを365日後に削除するライフサイクルを設定。
    • ※ ただし、レプリケーション先のリポジトリus-east-1リージョンに作成されるため、本テンプレートのみus-east-1リージョンに作成。

また、今回はリソースタグの付与を行うことから、サービスにリンクされたロールの権限では不足となります。そのため、カスタムロールを作成し必要な権限を設定します。

## repository creation template
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_repository_creation_template
resource "aws_iam_role" "ecr_creation_template" {
  name = "ecr-creation-template"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Service = "ecr.amazonaws.com"
      }
      Action = "sts:AssumeRole"
    }]
  })
}
resource "aws_iam_policy" "ecr_creation_template" {
  name = "ecr-creation-template"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowEcrCreateRepo"
        Effect = "Allow"
        Action = [
          "ecr:CreateRepository",
          "ecr:ReplicateImage",
          "ecr:TagResource"
        ]
        Resource = "*"
      }
    ]
  })
}
resource "aws_iam_role_policy_attachment" "ecr_creation_template" {
  role       = aws_iam_role.ecr_creation_template.name
  policy_arn = aws_iam_policy.ecr_creation_template.arn
}
resource "aws_ecr_repository_creation_template" "example_create_on_push" {
  prefix               = local.create_on_push_prefix
  description          = "An example-create-on-push template"
  image_tag_mutability = "MUTABLE"
  custom_role_arn      = aws_iam_role.ecr_creation_template.arn

  applied_for = [
    "CREATE_ON_PUSH",
  ]

  encryption_configuration {
    encryption_type = "AES256"
  }

  lifecycle_policy = <<EOT
{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Expire images older than 14 days",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 14
      },
      "action": {
        "type": "expire"
      }
    }
  ]
}
EOT

  resource_tags = {
    Project = "example"
  }
}
resource "aws_ecr_repository_creation_template" "example_pull_through_cache" {
  prefix               = local.pull_through_cache_prefix
  description          = "An example-pull-through-cache template"
  image_tag_mutability = "MUTABLE"
  custom_role_arn      = aws_iam_role.ecr_creation_template.arn

  applied_for = [
    "PULL_THROUGH_CACHE",
  ]

  encryption_configuration {
    encryption_type = "AES256"
  }

  lifecycle_policy = <<EOT
{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Expire images older than 140 days",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 140
      },
      "action": {
        "type": "expire"
      }
    }
  ]
}
EOT

  resource_tags = {
    Project = "example"
  }
}
resource "aws_ecr_repository_creation_template" "example_replication" {
  region               = local.regions.secondary
  prefix               = local.replication_filter_prefix
  description          = "An example-replication template"
  image_tag_mutability = "MUTABLE"
  custom_role_arn      = aws_iam_role.ecr_creation_template.arn

  applied_for = [
    "REPLICATION",
  ]

  encryption_configuration {
    encryption_type = "AES256"
  }

  lifecycle_policy = <<EOT
{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Expire images older than 365 days",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 365
      },
      "action": {
        "type": "expire"
      }
    }
  ]
}
EOT

  resource_tags = {
    Project = "example"
  }
}

検証

terraform applyによりデプロイした後に、テンプレート毎の挙動を確認します。

プルスルーキャッシュ

ECSタスクが参照するECRリポジトリはTerraformで未定義でしたが、ECSが正常にデプロイされました。

プルスルーキャッシュにより、ECSタスクが参照するリポジトリが自動作成され、中にはnginxのイメージが追加されていることが確認できました。

リポジトリのライフサイクルに関しても、想定通りテンプレートに設定した値になっていることを確認できました。

レプリケーション

ローカルから<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/example-replication/nginx/nginxリポジトリにイメージをプッシュします。

katsu@maru MINGW64 ~/OneDrive/Documents/アプリ開発/AWS検証系
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded

katsu@maru MINGW64 ~/OneDrive/Documents/アプリ開発/AWS検証系
$ docker push <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/example-replication/nginx/nginx:1.29-test
The push refers to repository [<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/example-replication/nginx/nginx]
25f453064fd3: Pushed
0abf9e567266: Pushed
e096540205d5: Pushed
567f84da6fbd: Pushed
1074353eec0d: Pushed
33f95a0f3229: Pushed
da7c973d8b92: Pushed
085c5e5aaa8e: Pushed
1.29-test: digest: sha256:4e91fa982779ee440fa905e1727c977b6ea28c929f9d424bd8e030951298124e size: 1989

i Info → Not all multiplatform-content is present and only the available single-platform image was pushed
         sha256:648c87cd4f9a37a104d194db573d1afe8fa6b2632e618257c0f09e26a80d5110 -> sha256:4e91fa982779ee440fa905e1727c977b6ea28c929f9d424bd8e030951298124e

プッシュした結果、us-east-1リージョンに想定通りリポジトリが作成され、イメージもレプリケートされていることが確認できました。
また、他のECRリポジトリは作成されておらず、指定のプレフィクスを持つリポジトリのイメージのみレプリケートされていました。

リポジトリのライフサイクルに関しても、想定通りテンプレートに設定した値になっていることを確認できました。

プッシュ時の自動作成(Create on push)

ローカルから、以下2パターンの存在しないECRリポジトリにイメージをプッシュします。

  • テンプレート対象外:<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/example/nginx/nginx
  • テンプレート対象内:<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/example-create-on-push/nginx/nginx
katsu@maru MINGW64 ~/OneDrive/Documents/アプリ開発/AWS検証系
$ docker push <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/example/nginx/nginx:1.29-alpine
The push refers to repository [<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/example/nginx/nginx]
0abf9e567266: Unavailable
567f84da6fbd: Unavailable
1074353eec0d: Unavailable
25f453064fd3: Unavailable
e096540205d5: Unavailable
da7c973d8b92: Unavailable
33f95a0f3229: Unavailable
085c5e5aaa8e: Unavailable
unexpected status from POST request to https://<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/v2/example/nginx/nginx/blobs/uploads/?mount=sha256:25f453064fd3e8a9754b6e51b86c637e13203cbfc748fcf73f3c8b2d10816ae3&from=example/nginx: 404 Not Found

katsu@maru MINGW64 ~/OneDrive/Documents/アプリ開発/AWS検証系
$ docker push <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/example-create-on-push/nginx/nginx:1.29-test
The push refers to repository [<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/example-create-on-push/nginx/nginx]
1074353eec0d: Pushed
085c5e5aaa8e: Pushed
da7c973d8b92: Pushed
567f84da6fbd: Pushed
0abf9e567266: Pushed
e096540205d5: Pushed
25f453064fd3: Pushed
33f95a0f3229: Pushed
1.29-test: digest: sha256:4e91fa982779ee440fa905e1727c977b6ea28c929f9d424bd8e030951298124e size: 1989

i Info → Not all multiplatform-content is present and only the available single-platform image was pushed
         sha256:648c87cd4f9a37a104d194db573d1afe8fa6b2632e618257c0f09e26a80d5110 -> sha256:4e91fa982779ee440fa905e1727c977b6ea28c929f9d424bd8e030951298124e

プッシュした結果、想定通りテンプレートが対象とするプレフィクスを持つリポジトリのみpush処理が成功しました。
また、想定通りリポジトリが作成されていることも確認できました。

リポジトリのライフサイクルに関しても、想定通りテンプレートに設定した値になっていることを確認できました。

まとめ

リポジトリ作成テンプレートは、特定の自動作成ユースケースに限定されるものの、共通のポリシーや設定を強制できるという点で、運用上のメリットが大きい機能です。

一方で、今回新たに追加された リポジトリの自動作成機能については、指定したプレフィクスさえ満たせばリポジトリが自動的に作成されてしまうため、入力ミスなどによって意図しないリポジトリが作成される可能性があります。
また、自動作成されたリポジトリは、明示的にimportしない限りTerraformなどのIaC管理下には置かれないため、実リソースとコードの乖離が発生しやすい点も、運用面でのデメリットと感じました。

そのため、本機能を利用する際は、リポジトリ名の命名規則や運用ルールを事前に明確化したうえで導入することが重要だと考えます。

おわりに

本記事では、コンテナおよびECRの概要を整理したうえで、リポジトリ作成テンプレートの概要と挙動について、実際に検証を行いました。
特定のユースケースに向けた機能であるため、私自身はしばらく使用する予定はありませんが、本記事がこれから利用を検討されている方の参考になれば幸いです。

個人的な感覚ではありますが、普段私たちが利用しているIT技術の多くは、数多くの抽象化や技術の積み重ねによって、シンプルで使いやすいインターフェースとして提供されています。そのため、実際に何が行われているのかを具体的にイメージしづらい部分も少なくありません。
こうした背景にある実装や仕組みを把握することは、日々の業務に直接活かされない場面も多いかもしれませんが、理解の実感が結果として技術に対する自信や判断力につながっていくと考えています。
そのため、今後の記事においても使用方法だけでなく、その背後にある技術や仕組みについても共有していきたいと考えています。

(補足)Dive deep into Amazon ECR より内部実装の紹介

Deep Dive 系の記事は AWS サービスごとに別途作成する予定ですが、今回はせっかくなので AWS re:Invent 2023 - Dive deep into Amazon ECR (CON405)を参考にしつつ、Amazon ECR に限定してその内部仕様について少しだけ見ていきたいと思います。

内部アーキテクチャ

AWS の各サービスも実体としてはアプリケーションであり、AWS API の仕様に基づいたリクエストを処理するFrontend Service (EC2)が存在します。その背後では、ストレージや名前解決などに関わる複数の AWS サービスが連携して動作しています。

ECRも例外ではなく、以下に示す構成となっております。背後では、S3やDynamoDBなどのストレージサービスも使用しています。
(これらの S3 や DynamoDB 自体も同様に、Frontend Service を起点とした構成になっていると考えられます)

ただし、レジストリサービスとして利用されるという特性上、コンテナイメージの操作に関するアクセス時には AWS API ではなく、OCI 規格に基づく push / pull リクエストが使用されます。
そのため ECR では、これらの OCI リクエストを受け付けるための専用の Proxy Service がデプロイされている点が、他の AWS サービスとは少し異なる特徴となっています。

あくまで推測にはなりますが、Lambda や S3 などの AWS サービスも、AWS API とは形式の異なるURLベースのアクセスが可能であることから、ECRと同様に中継層(Proxyに相当するコンポーネント)が存在している可能性があると考えています。

動作イメージ紹介

すべてを説明すると分量が多くなってしまうため、本記事では イメージの pull 時の挙動 に絞って説明します。

push / pull 時の仕様に関しても OCI で標準化されており、The OpenContainers Distribution Specで説明されています。
pullの仕様としては、以下2つのリクエストに分類されます。

  • <レジストリエンドポイント>/v2/<リポジトリ名>/manifests/<タグまたはイメージダイジェスト>へのメタデータ(manifest)の取得
    • manifestはJSON 形式のデータであり、イメージレイヤごとのダイジェスト(ハッシュ値)が記載されています。
  • <レジストリエンドポイント>/v2/<リポジトリ名>/blobs/<ダイジェスト>への blob(ダイジェストで管理される論理的なコンテンツ)の取得

ただし、2 つ目の blob の取得については、少なくとも Amazon ECR ではイメージレイヤそのものではなく保存先の URL を返却しつつリダイレクトさせる方式 が採用されています。

blob はあくまでダイジェストで管理される論理的なコンテンツとして、The OpenContainers Distribution Spec ではイメージレイヤなのか保存先URLなのかを明示的に指定しているわけではありません。
そのため、具体的に blob が返すものとして何が存在するかは実装依存となっており、レジストリの仕様やバージョンによって異なる可能性があります。


実際に ECR で pull が発生した際の、AWS サービス内部での処理フローは以下に示すような流れとなります。

OCI 準拠の pull リクエストは Proxy Serviceによって中継され、AWS API のエンドポイントとして動作する Frontend Service が、イメージ管理に関する主要な処理を担っています。
また、manifest やイメージレイヤの実体は S3 に保存されており、DynamoDBはあくまで参照情報のみ保存していることがわかります。

ここまでの内容から、ECR等のAWSサービスが抽象的なサービスではなく、複数の AWS サービスが連携して動作する実体のあるアプリケーションとして運用されているという点がイメージできたのではないでしょうか。

個人的に、サービスにリンクされたロールなど、AWS サービス自体が主体となって IAM 権限を基に操作するという感覚が、これまであまり掴めていませんでしたが、内部アーキテクチャにおけるFrontend Service の実体や役割を知ったことで、その挙動がよりしっくりと理解できるようになりました。

私たちは一緒に働いてくれる仲間を募集しています!

電通総研 キャリア採用サイト 電通総研 新卒採用サイト

執筆:@sakae.katsuto
レビュー:@kobayashi.hinami
Shodoで執筆されました