0. 개요
여러 클라우드 인프라를 구축 프로젝트를 진행하다 보면 서비스나 환경이 다르더라도 비슷한 네트워크 구조, 리소스 구성을 가지고 있는 경우가 많습니다. 규모가 크지 않은 간단한 프로젝트들이나, dev,stg,prd 환경만 다르고 인프라 구성은 유사하게 구축해야할 때 반복 작업이 필연적으로 발생하는데요.
이번 시리즈에서는 다중 계정 환경에서 Terraform을 이용해 리소스를 일관되게 배포하고 운영하며 겪었던 경험을 토대로 작성해보겠습니다. 특히 Control Tower 와 결합된 시리즈이니 블로그를 보고 따라하시면 효과적으로 AWS 상에서 다중 계정을 구축/운영하는 데에 도움이 될 것입니다.
이 게시글에선 본격적으로 Terraform으로 배포하기 전에 필요한 사전 준비작업을 공유드리겠습니다.
배경
- OO사의 A프로젝트, B프로젝트. 각각 dev / stg / prd 용으로 총 6개 계정이 있다고 가정합니다.
- 멀티 어카운트 환경 관리를 위해 Control Tower 를 구성해두었습니다.
- 모든 리소스를 Terraform으로 배포하며, 초기 인프라 구축 뿐 아니라 추후 운영도 Terraform 코드로 관리합니다.
- 계정별 만들어진 리소스 목록은 동일하나, ip 대역이나 태그, Security group & Routing Rule 등이 다릅니다.
Control Tower를 구성했으면서 이번 실습에서 AFT (Account Factory for Terraform)을 사용하지 않은 이유
1. 이미 Control Tower를 통해 다중 계정 구조가 만들어진 상태. AFT는 계정 생성 시점부터 Terraform과 연계해서 초기 리소스를 구축할 때 강력한 기능입니다.
2. AFT의 구조와 제약(별도 관리 계정, 특정 디렉터리 구조 등)이 초기 도입 장벽이 있습니다.
1. 멀티 어카운트 환경을 위한 디렉토리 구조
제가 채택한 방법은 크게
1. modules 디렉토리 : 모든 계정/환경에서 공통으로 사용될 인프라 구성 요소들을 분리합니다.
2. environments 디렉토리 : 실제로 Terraform명령어를 실행하는 공간이며, 계정/환경별로 구분됩니다. 이 안에는 각 환경에 맞는 값 (예: CIDR, 태그, SG 룰 등)이 정의됩니다.
이렇게 코드의 재사용성을 높일 수 있고(modules), 또 계정 내 리소스별로 상태파일을 관리할 수 있어서 (environments)관리용이성도 확보할 수 있습니다.
물론 프로젝트 규모나 복잡성에 따라 디렉토리 구조를 세분화하거나 통합할 수 있습니다. 저같은 경우에는 tfstate 파일을 어떤 단위로 관리해야할 지 고민이 많았습니다. 초반에 디렉토리 구조를 확정하면 바꾸기 어려우니까요.
계정별로 tfstate 파일을 하나씩 만든다면, 리소스 간 의존성에 따라 충돌이 발생하거나 plan/apply 시 불필요한 업데이트가 많을 것 같아 리소스 별로 terraform 수행 환경을 구성했습니다.
|-- environments
| |-- dev
| | |-- Project A
| | | |-- ACM
| | | |-- ALB
| | | |-- EC2
| | | |-- EFS
| | | |-- IAM
| | | |-- NLB
| | | |-- NW
| | | |-- RDS
| | | `-- SG
| | `-- Project B
| | |-- ACM
| | |-- ALB
| | |-- EC2
| | |-- EFS
| | |-- IAM
| | |-- NLB
| | |-- NW
| | |-- RDS
| | `-- SG
| |-- prd
| | |-- Project A
| | |-- Project B
| |
| |
| |
|-- modules
| |-- acm
| |-- alb
| |-- ec2
| |-- efs
| |-- iam
| |-- net_common
| | |-- eip
| | |-- endpoint
| | |-- igw
| | |-- nat
| | |-- subnet
| | |-- vgw
| | `-- vpc.tf
| |-- net_route_table
| |-- net_security_group
| |-- net_tgw
| |-- nlb
| `-- rds
environments / 각 리소스 폴더에는
- aws.tf
- main.tf
- varaibles.tf
- terraform.tfvars
파일들이 있습니다. 이렇게 디렉토리를 세분화할수록 관리할 파일이 많아지는게 단점이라고 할 수 있습니다.
2. tfstate 파일 관리
Terraform으로 인프라를 구성 관리할 때 가장 중요한 것 중 하나가 tfstate 파일 관리입니다. 특히 여러 엔지니어들이 각자의 로컬에서 동일한 환경에 terraform을 배포하는 상황에서는 tfstate 파일의 일관성을 유지하는게 중요하겠죠.
기존에는 한명의 엔지니어가 모든 테라폼 소스를 관리하고 있었기 때문에, terraform backend를 local로 생성하여 로컬 디렉토리에 보관하고 있었습니다. 프로젝트 인원이 많아짐에 따라 Terraform 원격 백엔드를 구성해 S3 에 상태파일을 저장하기로 했습니다. 항상 똑같은 S3 디렉토리에 있는 상태 파일을 참조하기 때문에 동일한 상태를 기준으로 작업할 수 있습니다.
또한 동시에 작업하는 사용자의 충돌 방지를 위해 상태 파일에 대한 Lock을 걸어두는 것도 중요합니다. 전통적으로는 S3 + DynamoDB 를 조합해 원격 백엔드를 구성합니다.
+ Terraform 1.9버전부터 use_lockfile 옵션을 통해 S3 Native Locking 기능이 도입되었습니다 (참고 블로그)
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "project-a/dev/network/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
## use_lockfile 옵션
use_lockfile = true
}
}
하지만 terraform apply 과정에서 락 상태가 꼬였을 때 콘솔에서 쉽게 락파일을 삭제하기 위해서는 DynamoDB 가 간편할 것 같아 S3 + dynamoDB 조합으로 구성했습니다.
S3 폴더구조도 env 폴더의 구조와 동일하게 생성합니다. 그래서 S3 각 최하위 디렉토리에는 terraform.tfstate 파일만 업로드 되어있고, 이렇게 구성함으로써 로컬 디렉토리와 S3 간의 매핑이 직관적이고 관리가 용이합니다.
S3 버킷 생성 후 꼭 버전관리를 켜두는 걸 추천합니다. 그래야 실수로 tfstate 파일이 꼬였을 대 복구가 쉽습니다.
env 폴더의 각 리소스 디렉토리 밑의 aws.tf 내용은 이렇게 되겠죠.
# env/project A/dev/EC2/aws.tf 파일
terraform {
# Terraform Version
required_version = ">= 1.11.1"
required_providers {
aws = {
# AWS Provider Version
source = "hashicorp/aws"
version = "5.90.1"
}
}
backend "s3" {
bucket = "my-s3-bucket"
key = "terraform/ProjectA/DEV/ec2/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "my-dbnamodb"
profile = "tfstate-save-user"
}
}
provider "aws" {
# alias = var.account_name
region = "ap-northeast-2"
assume_role {
role_arn = "arn:aws:iam::ACCOUNTID:role/project-a-dev-terraform-role"
session_name = "terraform-session"
}
}
3. IAM (user, role)
인프라 엔지니어들이 사용하는 테라폼 코드와 S3, DynamoDB는 인프라 개발계 AWS 계정에 업로드되어있습니다.
다른 배포 대상 계정들은 해당 계정으로부터 AssumeRole 방식으로 접근하도록 구성했습니다.
배포 계정들마다 AccessKey를 발급할수도 있지만 각 사용자마다 키를 발급해야 하고 여러 엔지니어들의 키 관리 공수가 들기 때문에,
배포 계정에는 role하나만 생성하고 Terraform 코드에서 assume_role 블럭을 통해 인프라를 프로비저닝할 role을 지정해주는 것이 더 간편합니다.
따라서 생성해야할 IAM 리소스들은 다음과 같습니다.
1. 인프라 개발계 (Codecommit Terraform 코드, S3, DynamoDB가 생성된 계정)
- IAM 사용자 tfstate-save-user 생성
- 권한
A. assume-role-policy : 배포 계정의 Role을 Assume할 수 있는 권한
이 권한으로 tfstate-save-user는 각 배포 계정의 IAM Role로 임시 접근 권한을 획득할 수 있습니다.
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::PROJECT A DEV Account ID:role/project-a-dev-terraform-role",
"arn:aws:iam::PROJECT B DEV Account ID:role/project-b-dev-terraform-role",
]
}
B. S3 + DynamoDB 접근 권한 (Terraform Backend)
이 권한으로 tfstate-save-user는 Terraform 상태 파일이 저장된 S3와 DynamoDB에 접근할 수 있는 권한을 갖습니다.
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"dynamodb:PutItem",
"dynamodb:DescribeTable",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:UpdateItem",
"s3:ListBucket",
"s3:DeleteObject",
"s3:GetBucketLocation",
"dynamodb:Scan"
],
"Resource": [
"arn:aws:s3:::my-tfstate-save-s3-bucket",
"arn:aws:s3:::my-tfstate-save-s3-bucket/*",
"arn:aws:dynamodb::table/my-dbnamodb"
]
}
2. 각 배포 대상 계정
- Terraform Role만 생성
각 배포 대상 계정에는 별도의 IAM 사용자를 만들지 않습니다. 대신, 아래와 같은 Role을 생성하고, 위에서 만든 tfstate-save-user가 이 Role을 Assume할 수 있도록 신뢰 정책(Trust Policy) 를 설정합니다
- 권한 : Administration
- 신뢰정책 :
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::인프라 개발계 Account ID:root",
"arn:aws:iam::인프라 개발계 Account ID:user/cjfv-tfstate-save-user"
]
},
"Action": "sts:AssumeRole"
}
이렇게 권한 관리까지 마무리하면, 다중 계정 환경에서도 일관적으로 Terraform을 배포 / 운영할 수 있는 기반이 갖춰집니다.
이 글에서는 Terraform으로 다중 계정 환경에 리소스 배포를 위한
전체 구조 설계부터 디렉토리 구성, 상태 파일 관리, 그리고 IAM 역할 분리에 이르기까지 알아보았습니다.
도움이 되셨길 바라며, 다음 편에서는 Control Tower Control 배포 등 Control Tower 환경에서 테라폼으로 계정을 관리하는 방법들에 대하여 포스팅하겠습니다.
감사합니다.