ECR クロスアカウントレプリケーションをTerraformで

2021.07.26

ECRクロスアカウントレプリケーションをTerraformを使って設定する機会がありましたのでレポートします。

ECRクロスアカウントレプリケーションとは

アカウントAのECRリポジトリにコンテナイメージをpushすると、アカウントBのECRリポジトリにそのイメージがレプリケーションされる、といった機能です。今年2021年1月にリリースされました。それまでは、レプリケーションさせたかったら自分で構築する必要がありました。もしくはレプリケーションを使わず、アカウントBから直接アカウントAのリポジトリへアクセスする必要がありました。このECRクロスアカウントレプリケーションを使うことで、アカウント間のアクセス権限設定をこの機能周りに集約することができ、なおかつ簡単に設定できるようになります。

構成概要

レプリケーション元アカウントsrc、レプリケーション先アカウントdestがあります。 ecr-cross-account-replication-with-terraform-1 レプリケーション元リポジトリと、レプリケーション先リポジトリを作成します。(後述しますが、実はレプリケーション先リポジトリの作成は必須ではありません。) ecr-cross-account-replication-with-terraform-2 リポジトリの上位概念として、プライベートレジストリがあります。これはアカウントxリージョン毎に一つだけ存在します。その中に0から複数個のリポジトリが存在するイメージです。 ecr-cross-account-replication-with-terraform-3 そして、レプリケーション元レジストリにて、レプリケーション先アカウントの特定のリージョンへのレプリケーションを設定します。リポジトリ単位ではなく、レジストリ全体として設定します。 ecr-cross-account-replication-with-terraform-4 最後に、レプリケーション先アカウントのレジストリポリシーにて、レプリケーション元アカウントからのレプリケーションの許可を設定します。この時、許可対象のリソースを特定のリポジトリのみにすることで、レジストリ内のあらゆるリポジトリをレプリケーションするのではなく、特定のリポジトリのみレプリケーションすることができます。 ecr-cross-account-replication-with-terraform-5

上記のとおり、レプリケーション元側ではレジストリ単位でざっくりと設定を行なうことしかできません。特定のリポジトリのみレプリケーションしたいといった細かい設定はレプリケーション先側で設定する必要があります。また、以下の点にもご留意ください。

  • レプリケーション元と先のリポジトリは同名にする必要があります。
  • レプリケーションはイメージがプッシュされたタイミングで実施されます。よってレプリケーション設定前にプッシュ済のイメージがレプリケーションされることはありません。

やってみる

最低限のTerraformの初期設定からスタートです。

terraform {
  required_version = "~> 1.0.3"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "3.51.0"
    }
  }
}

レプリケーション元と先両方のAWS providerを用意

Terraformではaliasを使うことにより、異なる設定の同じproviderを複数個用意することができます。これを利用して、レプリケーション元、レプリケーション先両アカウントに対してリソースのプロビジョニングができるようにします。profileは事前に~/.aws/configに設定しておきます。

provider "aws" {
  alias   = "src"
  region  = "ap-northeast-1"
  profile = "ecr-src"
}

provider "aws" {
  alias   = "dest"
  region  = "ap-northeast-1"
  profile = "ecr-dest"
}

aliasは全ての同じproviderに設定する必要はなく、一つだけ未設定でも構いません。例えば今回の場合alias="src"がなくても動きます。が、今回はわかりやすくするためにどちらにもaliasを設定しています。

レプリケーション元と先のリポジトリを作成

aliasを使う場合、resourceのprovider引数でどのproviderを使うか指定します。provider引数がない場合はdefaultのproviderつまりaliasを設定しなかったものが使われます。が、今回はdefault providerを作っていないのでprovider引数を設定する必要があります。

resource "aws_ecr_repository" "src" {
  provider = aws.src

  name                 = "replication-test-repo"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }

  encryption_configuration {
    encryption_type = "AES256"
  }
}

resource "aws_ecr_repository" "dest" {
  provider = aws.dest

  name                 = "replication-test-repo"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }

  encryption_configuration {
    encryption_type = "AES256"
  }
}

レプリケーション元レジストリにてレプリケーション設定

Data Sourceを使ってレプリケーション先の情報を使います。

data "aws_region" "dest" {
  provider = aws.dest
}
data "aws_caller_identity" "dest" {
  provider = aws.dest
}

resource "aws_ecr_replication_configuration" "test" {
  provider = aws.src

  replication_configuration {
    rule {
      destination {
        registry_id = data.aws_caller_identity.dest.account_id
        region      = data.aws_region.dest.name
      }
    }
  }
}

レプリケーション先レジストリにてレジストリポリシー設定

ecr_registry_policy.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid"    : "ReplicationAccessCrossAccount",
      "Effect" : "Allow",
      "Principal" : {
        "AWS" : "arn:aws:iam::${src_account_id}:root"
      },
      "Action" : [
        "ecr:ReplicateImage"
      ],
      "Resource" : [
        "${dest_repo_arn}"
      ]
    }
  ]
}

許可するActionとしてecr:ReplicateImageを設定していますが、ここにecr:CreateRepositoryも追加しておくと、レプリケーション先に該当リポジトリがない場合に自動で作成してくれます。故にレプリケーション先のリポジトリのプロビジョニングは不要です。が、レプリケーションしたいリポジトリが決まっているのであれば明示的にプロビジョニングしておいたほうがIaC的には良いと私は考えます。

上記ポリシーをtemplatefile関数を使って参照します。

data "aws_caller_identity" "src" {
  provider = aws.src
}

resource "aws_ecr_registry_policy" "example" {
  provider = aws.dest

  policy = templatefile(
    "./ecr_registry_policy.json",
    {
      src_account_id  = data.aws_caller_identity.src.account_id
      dest_repo_arn = aws_ecr_repository.dest.arn
    }
  )
}

イメージのプッシュ

以下を参考にレプリケーション元アカウントのリポジトリにイメージをプッシュして、レプリケーションされるか確認します。

% aws ecr get-login-password --region ap-northeast-1 --profile ecr-src  | docker login --username AWS --password-stdin (srcのアカウントID).dkr.ecr.ap-northeast-1.amazonaws.com
WARNING! Your password will be stored unencrypted in /Users/kazue.masaki/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

% docker tag 7de55dfbda43 (srcのアカウントID).dkr.ecr.ap-northeast-1.amazonaws.com/replication-test-repo
% docker push (srcのアカウントID).dkr.ecr.ap-northeast-1.amazonaws.com/replication-test-repo            
Using default tag: latest
The push refers to repository [(srcのアカウントID).dkr.ecr.ap-northeast-1.amazonaws.com/replication-test-repo]
4bc6e1889bca: Pushed 
cdc511887561: Pushed 
777b2c648970: Pushed 
latest: digest: sha256:7b1e232f564132b949b812ee138249b5398bedf3c6fab42b468d4ec8acf5343c size: 952

確認

まず、レプリケーション元アカウントにイメージがプッシュされていることが確認できました。

% aws ecr list-images --repository-name replication-test-repo --profile ecr-src 
{
    "imageIds": [
        {
            "imageDigest": "sha256:7b1e232f564132b949b812ee138249b5398bedf3c6fab42b468d4ec8acf5343c",
            "imageTag": "latest"
        }
    ]
}

次にレプリケーション先アカウントです。同じイメージが存在していることが確認できました。レプリケーション成功です。

% aws ecr list-images --repository-name replication-test-repo --profile ecr-dest
{
    "imageIds": [
        {
            "imageDigest": "sha256:7b1e232f564132b949b812ee138249b5398bedf3c6fab42b468d4ec8acf5343c",
            "imageTag": "latest"
        }
    ]
}

まとめ

ECRクロスアカウントレプリケーションをTerraformで設定しました。実は最初はレジストリ単位で設定する点など予想と違う部分があってすこし手間取りましたが、理解してしまえば簡単に設定できて非常に便利だと感じました。