[Terraform/AWS ECR] 외부 이미지 ECR에 캐싱하기 (Pull Through Cache Rule)
# 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
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 토큰용 시크릿 매니저만 만들 필요가 있고, 시크릿 매니저 자체는 그냥 콘솔에서 수동으로 만들어 주었다.
사진에서 보다싶이, `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
}
}
끝~!!