# 기존 (직접 참조)output "vpc_id" { value = aws_vpc.main.id # ❌ 리소스가 루트에 없음}# 변경 (모듈 output 참조)output "vpc_id" { value = module.vpc.vpc_id # ✅ 모듈 output 경유}output "bastion_public_ip" { value = module.ec2.public_ip # ✅}output "security_group_ids" { value = { bastion = module.security.bastion_sg_id web = module.security.web_sg_id app = module.security.app_sg_id db = module.security.db_sg_id }}
Step 6. 기존 플랫 파일 삭제
rm vpc.tf security_groups.tf ec2.tf
4. state mv 완전 정복
💡 왜 state mv가 필요한가
🤔 질문: “코드만 모듈로 옮기면 되는 거 아닌가?”
아니다
Terraform은 리소스를 state 파일의 주소로 추적한다.
기존 주소: aws_vpc.main
모듈 주소: module.vpc.aws_vpc.main
주소가 다르면 Terraform은 “기존 것 삭제 + 새 것 생성”으로 판단한다. VPC가 삭제되면 안의 모든 리소스가 함께 날아간다.
📋 state mv 실행
# 1) terraform init (모듈 로딩)terraform init# 2) VPC 모듈 (12개 리소스)terraform state mv aws_vpc.main module.vpc.aws_vpc.mainterraform state mv aws_internet_gateway.main module.vpc.aws_internet_gateway.mainterraform state mv 'aws_subnet.public[0]' 'module.vpc.aws_subnet.public[0]'terraform state mv 'aws_subnet.public[1]' 'module.vpc.aws_subnet.public[1]'terraform state mv 'aws_subnet.private[0]' 'module.vpc.aws_subnet.private[0]'terraform state mv 'aws_subnet.private[1]' 'module.vpc.aws_subnet.private[1]'terraform state mv aws_route_table.public module.vpc.aws_route_table.publicterraform state mv aws_route_table.private module.vpc.aws_route_table.privateterraform state mv 'aws_route_table_association.public[0]' 'module.vpc.aws_route_table_association.public[0]'terraform state mv 'aws_route_table_association.public[1]' 'module.vpc.aws_route_table_association.public[1]'terraform state mv 'aws_route_table_association.private[0]' 'module.vpc.aws_route_table_association.private[0]'terraform state mv 'aws_route_table_association.private[1]' 'module.vpc.aws_route_table_association.private[1]'# 3) Security 모듈 (4개)terraform state mv aws_security_group.bastion module.security.aws_security_group.bastionterraform state mv aws_security_group.web module.security.aws_security_group.webterraform state mv aws_security_group.app module.security.aws_security_group.appterraform state mv aws_security_group.db module.security.aws_security_group.db# 4) EC2 모듈 (4개)terraform state mv aws_key_pair.main module.ec2.aws_key_pair.mainterraform state mv aws_instance.bastion module.ec2.aws_instance.bastionterraform state mv aws_eip.bastion module.ec2.aws_eip.bastionterraform state mv aws_eip_association.bastion module.ec2.aws_eip_association.bastion
count 사용 리소스 주의
aws_subnet.public[0] 같은 인덱스 리소스는 반드시 따옴표로 감싸야 한다.
# ✅ 올바른 방법terraform state mv 'aws_subnet.public[0]' 'module.vpc.aws_subnet.public[0]'# ❌ 쉘이 대괄호를 해석해서 오류terraform state mv aws_subnet.public[0] module.vpc.aws_subnet.public[0]
📋 검증
# 1) state 주소 확인 - 전부 module.xxx. 접두사terraform state list# 2) plan 확인 - 반드시 "No changes" 나와야 함terraform plan# 3) SSH 접속 확인 - EIP 유지 확인ssh -i ~/.ssh/id_ed25519 ec2-user@43.201.xxx.xxx
📊 state mv 전후 비교
전 (플랫)
후 (모듈)
aws_vpc.main
module.vpc.aws_vpc.main
aws_subnet.public[0]
module.vpc.aws_subnet.public[0]
aws_security_group.bastion
module.security.aws_security_group.bastion
aws_instance.bastion
module.ec2.aws_instance.bastion
aws_eip.bastion
module.ec2.aws_eip.bastion
data source는 state mv 불필요
data.aws_ami.amazon_linux 같은 data source는 매번 새로 조회하므로 state mv 대상이 아니다. 루트에 남은 stale 항목은 terraform state rm으로 정리하면 된다.
5. 트러블슈팅
🚨 “plan에서 destroy가 뜬다”
원인: state mv를 빠뜨린 리소스가 있음
# 어떤 리소스가 누락됐는지 확인terraform plan | grep "will be destroyed"# 누락된 리소스 state mv 추가 실행terraform state mv aws_xxx.yyy module.xxx.aws_xxx.yyy
🚨 “state mv 중 오류 발생”
원인: 리소스 이름이 모듈 코드와 불일치
# 롤백cp terraform.tfstate.pre-module-backup terraform.tfstate# 모듈 코드의 리소스 이름 확인 후 재시도
🚨 “module.xxx.output이 없다는 오류”
원인: 모듈의 outputs.tf에 해당 output을 선언하지 않음
# modules/vpc/outputs.tf에 추가output "vpc_id" { value = aws_vpc.main.id # 이게 없으면 module.vpc.vpc_id 참조 불가}