AWS ECR (Elastic Container Registry) 은 도커이미지를 버저닝하고 push/pull 하는 도커 스토리지다. Terraform 으로 ECR 을 생성하는 과정을 알아보자. (총 결과물은 dev-daeun/ecr-terrform 리퍼지토리 참고.)
Terraform 으로 할 것
프로젝트 초기 설정
provider.tf
작성
- backend 관련 리소스 프로비저닝
- 상태 스냅샷 파일을 저장할 S3
- 상태 스냅샷 locking 을 수행할 DynamoDB table
ECR 프로비저닝
- ECR 접근, push, pull 권한이 있는 IAM 유저 생성
- ECR 관련 리소스 생성
- ECR 리퍼지토리
- 생성한 IAM 유저만이 접근할 수 있도록 하는 레지스트리 정책
- 이미지 생명주기 정책
- 리퍼지토리에 푸시된 이미지의 취약점 스캐닝 설정
프로젝트 초기 설정
1. Terraform 설치
테라폼 설치 과정은 여기서 확인할 수 있다.
2. provider.tf 작성
작업 디렉토리에 아래와 같이
provider.tf
를 작성한다.argument | purpose |
terraform | 테라폼 관련 설정을 정의한다. 상수 값만 들어갈 수 있으며, 변수는 들어갈 수 없다.
https://www.terraform.io/language/settings |
terraform.required_providers | 설치할 provider의 레지스트리와 버전이 명시되어 있다.
아래 설정에서는 3.27 이상의 AWS 프로바이더를 요구한다.
https://www.terraform.io/language/providers/requirements |
terraform.required_version | 테라폼 버전을 명시한다. 아래 설정에서는 최소 0.14.9 버전 이상을 요구한다.
https://www.terraform.io/language/settings#specifying-a-required-terraform-version |
aws
라는 라벨로 정의된 provider
블록에는 테라폼이 사용할 AWS profile 과 region 이 정의되어 있다. 테라폼은 서로 다른 설정의 provider를 여러개 정의하고 resource
, module
단위로 provider를 선택해서 리소스를 만들 수 있는 장점이 있다. (자세한 내용은 여기 참고.)provider.tf
작성 후 $ terraform init
명령어를 실행한다.➜ ecr-terraform git:(master) ✗ terraform init Initializing the backend... Initializing provider plugins... - Reusing previous version of hashicorp/aws from the dependency lock file - Installing hashicorp/aws v3.74.0... - Installed hashicorp/aws v3.74.0 (signed by HashiCorp) Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
3. terraform backend 관련 리소스 생성
backend 를 사용하는 이유
여러 명이 같은 프로젝트에서 작업할 경우 테라폼을 통해 생성/수정/삭제된 리소스들의 상태들을 서로 공유할 필요가 있다. 여러 명의 개발자가 github 리퍼지토리를 통해 공동으로 작업하는 코드를 공유하는 것처럼 테라폼은 backend 를 사용함으로써 리소스들의 상태를 공유한다.
- S3 버킷은 apply 가 실행될 때마다 리소스의 상태가 기록되는
terraform.tfstate
파일을 버저닝하는 용도로 쓰인다.
- dynamoDB 는 상태 스냅샷에 race condition 이 발생하는 것을 방지하기 위한 locking 을 담당한다.
S3 버킷을 생성하는
s3.tf
는 아래와 같이 작성했다. argument | purpose |
bucket | 버킷 이름은 devdaeun-terraform-state 이다. |
versioning | 상태가 바뀌면 파일을 삭제하지 않고 새로 추가한다. |
dynamodb.tf
파일은 아래와 같이 작성했다.argument | purpose |
name | table 이름은 terraform-state-lock 이다. |
hash_key | string 타입의 LockID 라는 이름의 해시 키를 생성해야만 state locking 이 가능하다. https://www.terraform.io/language/settings/backends/s3#dynamodb-state-locking |
$ terraform plan
명령어로 생생될 리소스들의 정보를 미리 본다.➜ ecr-terraform git:(master) ✗ terraform plan Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_dynamodb_table.terraform_state_lock will be created + resource "aws_dynamodb_table" "terraform_state_lock" { + arn = (known after apply) + billing_mode = "PAY_PER_REQUEST" + hash_key = "LockID" + id = (known after apply) + name = "terraform-state-lock" + read_capacity = (known after apply) + stream_arn = (known after apply) + stream_label = (known after apply) + stream_view_type = (known after apply) + tags_all = (known after apply) + write_capacity = (known after apply) + attribute { + name = "LockID" + type = "S" } + point_in_time_recovery { + enabled = (known after apply) } + server_side_encryption { + enabled = (known after apply) + kms_key_arn = (known after apply) } + ttl { + attribute_name = (known after apply) + enabled = (known after apply) } } # aws_s3_bucket.tfstate will be created + resource "aws_s3_bucket" "tfstate" { + acceleration_status = (known after apply) + acl = "private" + arn = (known after apply) + bucket = "devdaeun-terraform-state" + bucket_domain_name = (known after apply) + bucket_regional_domain_name = (known after apply) + force_destroy = false + hosted_zone_id = (known after apply) + id = (known after apply) + region = (known after apply) + request_payer = (known after apply) + tags_all = (known after apply) + website_domain = (known after apply) + website_endpoint = (known after apply) + versioning { + enabled = true + mfa_delete = false } } Plan: 2 to add, 0 to change, 0 to destroy.
$ terraform apply
명령어로 S3 버킷과 DynamoDB 테이블을 생성한다.➜ ecr-terraform git:(master) ✗ terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_dynamodb_table.terraform_state_lock will be created + resource "aws_dynamodb_table" "terraform_state_lock" { + arn = (known after apply) + billing_mode = "PAY_PER_REQUEST" + hash_key = "LockID" + id = (known after apply) + name = "terraform-state-lock" + read_capacity = (known after apply) + stream_arn = (known after apply) + stream_label = (known after apply) + stream_view_type = (known after apply) + tags_all = (known after apply) + write_capacity = (known after apply) + attribute { + name = "LockID" + type = "S" } + point_in_time_recovery { + enabled = (known after apply) } + server_side_encryption { + enabled = (known after apply) + kms_key_arn = (known after apply) } + ttl { + attribute_name = (known after apply) + enabled = (known after apply) } } # aws_s3_bucket.tfstate will be created + resource "aws_s3_bucket" "tfstate" { + acceleration_status = (known after apply) + acl = "private" + arn = (known after apply) + bucket = "devdaeun-terraform-state" + bucket_domain_name = (known after apply) + bucket_regional_domain_name = (known after apply) + force_destroy = false + hosted_zone_id = (known after apply) + id = (known after apply) + region = (known after apply) + request_payer = (known after apply) + tags_all = (known after apply) + website_domain = (known after apply) + website_endpoint = (known after apply) + versioning { + enabled = true + mfa_delete = false } } Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aws_dynamodb_table.terraform_state_lock: Creating... aws_s3_bucket.tfstate: Creating... aws_s3_bucket.tfstate: Creation complete after 3s [id=devdaeun-terraform-state] aws_dynamodb_table.terraform_state_lock: Creation complete after 7s [id=terraform-state-lock] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
4. provider.tf 수정
2번에서 작성했던
provider.tf
를 아래와 같이 수정한다.argument | purpose |
terraform.backend.bucket | 2번에서 생성한 devdaeun-terraform-state 라는 이름의 AWS s3 버킷에 terraform.tfstate 파일을 저장하고 갱신하도록 설정했다. |
terraform.backend.dynamodb_table | 2번에서 생성한 terraform-state-lock 이라는 이름의 dynamoDB 테이블을 상태 스냅샷 locking 테이블로 사용한다. |
provider.tf
수정 후 다시 $ terraform init
을 실행한다. 로컬에서 만들어진
terraform.tfstate
파일을 S3 버킷에 복사하겠느냐고 묻는 메세지가 출력되면 yes
를 입력한다.➜ ecr-terraform git:(master) ✗ terraform init Initializing the backend... Do you want to copy existing state to the new backend? Pre-existing state was found while migrating the previous "local" backend to the newly configured "s3" backend. No existing state was found in the newly configured "s3" backend. Do you want to copy this state to the new "s3" backend? Enter "yes" to copy and "no" to start with an empty state. Enter a value: yes Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... - Reusing previous version of hashicorp/aws from the dependency lock file - Using previously-installed hashicorp/aws v3.74.0 Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
ECR 프로비저닝
5. variables 정의
variables.tf
라는 파일을 만들고 environment
와 service
라는 변수를 정의한다.environment
변수의 디폴트 값은 dev
로 한다.다른 tf 파일에 변수를 주입할 수 있도록
terraform.tfvars
를 작성한다. terraform.tfvars
에 있는 값이 먼저 쓰이고, terraform.tfvars
에 변수가 없을 경우 variables.tf
에 있는 디폴트 값이 사용된다.6. IAM 유저 생성
iam.tf
파일을 만들고 아래와 같이 IAM 유저를 정의한다. 아래 코드는 AWS 프로바이더가 사용하는 profile의 계정에 생성되어 있는 모든 ECR 을 상대로 모든 action (
DeleteRepository
, BatchGetImage
등) 을 수행할 권한이 있는 IAM 유저와 IAM 유저의 액세스 키를 생성한다.
IAM 유저가 ECR 레지스트리 인증을 하고 리퍼지토리에 이미지를 push/pull 하기 위해서는 IAM policy에 ecr:GetAuthorizationToken 액션이 allow 되어야 한다. (자세한 내용은 여기 참고.)
$ terraform apply
를 실행하면 아래와 같이 출력된다.➜ ecr-terraform git:(master) ✗ terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: ...<중략> aws_iam_user.ecr-user: Creating... aws_iam_user.ecr-user: Creation complete after 3s [id=ecr-user] aws_iam_user_policy.ecr-user-policy: Creating... aws_iam_access_key.ecr-user-access-key: Creating... aws_iam_access_key.ecr-user-access-key: Creation complete after 1s [id=AKIAR577ZNV3DGCWAFTG] aws_iam_user_policy.ecr-user-policy: Creation complete after 1s [id=ecr-user:ecr-user-policy] Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
7. ECR 리소스 생성
위에서 프로비저닝한 IAM 유저가 접근할 수 있는 ECR을 생성해보자.
aws_ecr_repository
ECR 리퍼지토리를 생성하는 블록을 아래와 같이 작성한다.
argument | purpose |
name | 리퍼지토리 이름은 devdaeun/ecr-with-terraform 이다. |
tags | 환경/서비스 별로 ecr 리소스를 구분짓기 위해 태그를 붙였다. |
aws_ecr_repository_policy
ECR 리퍼지토리의 접근 권한 정책을 설정하는 블록을 아래와 같이 작성한다.
argument | purpose |
repository | ecr-repo 라벨로 정의된 ECR 리퍼지토리의 이름 devdaeun/ecr-with-terraform 을 참조한다. |
policy | 6번에서 생성한 IAM 유저의 접근만 허용한다. IAM policy 에서는 IAM 유저에게 ECR 관련 모든 권한을 허용하고 있지만 ECR policy 에서는 ecr:BatchDeleteImage 액션을 명시적으로 허용하지 않으므로 IAM 유저는 devdaeun/ecr-with-terraform 리퍼지토리에 있는 이미지를 삭제할 수 없다. |
여기서 잠깐! IAM policy vs ECR policy
policy 우선순위는 아래와 같다.
Effect = "Deny"
(explicit deny) > Effect = "Allow"
> default (implicit deny)- Statement 에서
Effect = "Allow"
로 되어있지 않은 action 은 기본적으로 Deny 처리되어 (implicit deny) IAM 유저가 실행할 권한이 없다.
- IAM policy 와 ECR policy 중 하나라도 Statement 에서
Effect = "Allow"
되어있으면 IAM 유저는 해당 action 을 실행할 권한이 있다.
- IAM policy 와 ECR policy 중 하나라도 Statement 에서 action 이
Effect = "Deny"
되어있으면 (explicit deny) 다른 policy 에서Effect = "Allow"
되어 있더라도 IAM 유저는 해당 action 을 실행할 권한이 없다.
- 자세한 내용은 여기 참고.
aws_ecr_liifecycle_policy
ECR 리퍼지토리에 푸시된 도커이미지들의 생명주기 정책을 설정하는 블록을 아래와 같이 작성한다.
argument | purpose |
repository | ecr-repo 라벨로 정의된 ECR 리퍼지토리의 이름 devdaeun/ecr-with-terraform 을 참조한다. |
policy | 어떤 태그가 붙던지 상관없이 ECR 리퍼지토리에 푸시된 이후 30일이 경과된 도커이미지들은 삭제하도록 되어있다. (ECR 이미지 생명주기 policy 에 대한 자세한 내용은 여기 참고.) |
aws_ecr_registry_scanning_configuration
ECR 리퍼지토리에 있는 이미지들의 보안취약점 스캔 관련 정책을 설정하는 블록은 같이 작성한다.
(scan_type 과 filter에 대한 자세한 내용은 여기 참고)
argument | purpose |
scan_type | OS 와 프로그래밍 언어 관련 취약점까지 모두 스캔하도록 ENHANCED 으로 뒀다. |
rule | 이미지가 푸시되었을 때 스캔하며 이름에 devdaeun 을 포함한 모든 리퍼지토리를 스캔한다. |
$ terraform apply
를 실행하여 위 4가지 ECR 리소스를 프로비저닝한다.➜ ecr-terraform git:(master) ✗ terraform apply ... <중략> Apply complete! Resources: 3 added, 1 changed, 0 destroyed. Outputs: ecr-arn = "arn:aws:ecr:ap-northeast-2:133143555446:repository/devdaeun/ecr-with-terraform" ecr-repo-url = "133143555446.dkr.ecr.ap-northeast-2.amazonaws.com/devdaeun/ecr-with-terraform"