공부/AWS

[Terraform/AWS ECR] 외부 이미지 ECR에 캐싱하기 (Pull Through Cache Rule)

haejang 2024. 6. 30. 23:49
728x90
728x90

 

 

# ECR Pull Through Cache Rule 이란?

쉽게 말해, 외부의 도커 이미지 레지스트리(ex - docker.io, ghcr.io, public.ecr.aws) 에 있는 도커 이미지를 내 AWS 계정의 ECR로 가져올 수 있게 규칙을 생성해 두는 것이다.

공식 도큐 - https://docs.aws.amazon.com/AmazonECR/latest/userguide/pull-through-cache-creating-rule.html

 

Creating a pull through cache rule in Amazon ECR - Amazon ECR

The AWS Management Console only displays Secrets Manager secrets with names using the ecr-pullthroughcache/ prefix. The secret must also be in the same account and Region that the pull through cache rule is created in.

docs.aws.amazon.com

 

 

Pull Through Cache Rule 은 아래 레지스트리들을 업스트림으로 생성할 수 있다.

- Kubernetes (registry.k8s.io)

- ECR Public (public.ecr.aws)

- Docker Hub (registry-1.docker.io (docker.io))

- Quay (quay.io)

- Github Container Registry (ghcr.io)

- Microsoft Azure Container Registry ({custom}.azurecr.io)

- Gitlab Container Registry (registry.gitlab.com)

 

이 중 Docker Hub, Github, Gitlab, Azure는 액세스 가능한 토큰값을 저장한 Secrets Manager도 미리 생성해두어야 한다.

 

# 그래서 이미지 캐싱을 하는 법은?

우선 위의 업스트림 규칙을 만들어두면, 

`<내 계정의 ECR Registry 주소>/<Pull Through cache rule prefix>/<이미지 레포/이미지:태그>` 주소로 docker pull을 하는 순간 ECR에 레포지토리가 생기며 해당 이미지가 캐싱되어 있다. 

그러니까 예를 들어 ... docker-hub에서 bitnami/metrics-server:0.6.3-debian-11-r21 이미지를 받으려고 하는 경우,

1) Docker Hub 용 Pull Through Cache Rule을 만들어두고,

2) `docker pull {account_id}.dkr.ecr.{region}.amazonaws.com/docker-hub/bitnami/metrics-server:0.6.3-debian-11-r21`

명령을 날려주면,

이렇게 레포지토리와 이미지가 내 ECR에 잘 들어와 있는것을 확인할 수 있다.

 

# 이걸 하는 이유는?

망할 폐쇄망 구축을 하게 되어서... 꼭 필요한 오픈소스 이미지들을 미리 가져다 두려고 한다.

 

# 근데 x나 귀찮다.

1) 일단 레포 단위 미러링이 아닌 이미지 태그 한개 단위라는거...가 귀찮았다.

2) 쿠버네티스는 도커에서 containerd로 바뀐지 한참 되어서, docker pull을 쿠버 클러스터에서 날려주지 않기 때문에 필요한 경우 수동으로 이미지 하나하나 도커 풀을 날려줘야 했다.

 

-> 결론 : 테라폼으로 하자.

테라폼 만세!

 

# 테라폼으로 하기 🚀

일단 난 k8s / ecr / docker / quay / github 에 관해서만 규칙을 생성했다.

따라서 docker / github 토큰용 시크릿 매니저만 만들 필요가 있고, 시크릿 매니저 자체는 그냥 콘솔에서 수동으로 만들어 주었다.

github용 시크릿
docker hub용 시크릿

사진에서 보다싶이, `ecr-pullthroughcache/` 가 반드시 prefix로 들어가야 한다.

 

테라폼 작업도 시작해보자. 우선, `cache-images.yaml` 이란 파일을 만들어서 내가 원하는 이미지 태그들을 리스트로 작성할 것이다.

# cache-images.yaml

# 사용 가능:
#   - registry.k8s.io
#   - public.ecr.aws
#   - registry-1.docker.io (docker.io)
#   - quay.io
#   - ghcr.io

# for metrics-server
- registry-1.docker.io/bitnami/metrics-server:0.6.3-debian-11-r21

# for argo-cd
- public.ecr.aws/docker/library/redis:7.0.11-alpine
- quay.io/argoproj/argo-rollouts:v1.5.0
- quay.io/argoproj/argocd:v2.7.3
- ghcr.io/dexidp/dex:v2.36.0

 

이 파일을 읽어서 pull cache rule을 만들고 docker pull까지 하는 main 파일은 아래와 같다.

locals {
  account_id = ""
  aws_region = ""

  ecr_registry      = "${local.account_id}.dkr.ecr.${local.aws_region}.amazonaws.com"
  docker_secret_arn = "arn:aws:secretsmanager:${local.aws_region}:${local.account_id}:secret:ecr-pullthroughcache/docker-hub"
  github_secret_arn = "arn:aws:secretsmanager:${local.aws_region}:${local.account_id}:secret:ecr-pullthroughcache/github"

  cache_images_info = yamldecode(file("./cache-images.yaml"))

  # ecr pull through cache rule 이란 뜻
  ecr_ptcr = {
    k8s = {
      secrets_manager_arn = null
      registry_url        = "registry.k8s.io"
      pull_images = [
        for i in local.cache_images_info : trimprefix(i, "registry.k8s.io/")
        if startswith(i, "registry.k8s.io")
      ]
    }
    ecr-public = {
      secrets_manager_arn = null
      registry_url        = "public.ecr.aws"
      pull_images = [
        for i in local.cache_images_info : trimprefix(i, "public.ecr.aws/")
        if startswith(i, "public.ecr.aws")
      ]
    }
    docker-hub = {
      secrets_manager_arn = local.docker_secret_arn
      registry_url        = "registry-1.docker.io"
      pull_images = [
        for i in local.cache_images_info : trimprefix(i, "registry-1.docker.io/")
        if startswith(i, "registry-1.docker.io")
      ]
    }
    quay = {
      secrets_manager_arn = null
      registry_url        = "quay.io"
      pull_images = [
        for i in local.cache_images_info : trimprefix(i, "quay.io/")
        if startswith(i, "quay.io")
      ]
    }
    github = {
      secrets_manager_arn = local.github_secret_arn
      registry_url        = "ghcr.io"
      pull_images = [
        for i in local.cache_images_info : trimprefix(i, "ghcr.io/")
        if startswith(i, "ghcr.io")
      ]
    }
  }

  ecr_repos = flatten([
    for k, v in local.ecr_ptcr : distinct([
      for image in v.pull_images : "${k}/${split(":", image)[0]}"
    ])
  ])
}

################################################################################
# ECR Pull Through Cache Rule 설정
################################################################################
resource "aws_ecr_pull_through_cache_rule" "this" {
  for_each = local.ecr_ptcr

  ecr_repository_prefix = each.key
  upstream_registry_url = each.value.registry_url
  credential_arn        = each.value.secrets_manager_arn
}

################################################################################
# Docker Pull
################################################################################
locals {
  docker_images = [
    for k, v in local.ecr_ptcr : {
      for image in v.pull_images : "${k}_${replace(image, ":", "_")}" => {
        ptcr  = k
        image = image
      }
    }
  ]

  merged_docker_images = zipmap(
    flatten([for item in local.docker_images : keys(item)]),
    flatten([for item in local.docker_images : values(item)])
  )
}

resource "docker_image" "this" {
  for_each = local.merged_docker_images
  name     = "${local.ecr_registry}/${each.value.ptcr}/${each.value.image}"

  depends_on = [aws_ecr_pull_through_cache_rule.this]
}

 

만들어진 레포지토리에 여러 계정에서 액세스할 수 있게 policy를 걸어주려면 아래 내용을 추가해주면 된다.

################################################################################
# ECR Repository Policy 설정 - Cross Account 허용
################################################################################
locals {
  allow_accounts = []
}

data "aws_iam_policy_document" "this" {
  statement {
    sid    = "AllowPushPull"
    effect = "Allow"

    principals {
      type = "AWS"
      identifiers = [
        for k, v in local.allow_accounts : v
      ]
    }

    actions = [
      "ecr:BatchCheckLayerAvailability",
      "ecr:BatchGetImage",
      "ecr:CompleteLayerUpload",
      "ecr:GetDownloadUrlForLayer",
      "ecr:InitiateLayerUpload",
      "ecr:PutImage",
      "ecr:UploadLayerPart",
      "ecr:ListImages"
    ]
  }
}

resource "aws_ecr_repository_policy" "this" {
  for_each = toset(local.ecr_repos)

  repository = each.key
  policy     = data.aws_iam_policy_document.this.json

  depends_on = [
    docker_image.this
  ]
}

 

아 참고로... docker terraform provider는 아래처럼 선언해 두었다.

# providers.tf

provider "aws" {
  region  = local.aws_region
  profile = local.aws_profile
}

data "aws_ecr_authorization_token" "token" {}

locals {
  docker_pwd = data.aws_ecr_authorization_token.token.password
}

### Docker Provider
terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "3.0.2"
    }
  }
}

provider "docker" {
  host = "unix:///var/run/docker.sock"

  registry_auth {
    address  = local.ecr_registry
    username = "AWS"
    password = local.docker_pwd
  }
}

 

 

끝~!!

 

 

728x90
728x90