IaC ‐ terraform - 100-hours-a-week/KTB4-7th-Cloud GitHub Wiki

1. 도입 배경과 목표

v1 인프라는 AWS 콘솔에서 EC2, RDS, S3, 네트워크 등을 직접 설정해 운영하고 있었습니다. 현재 구성을 코드로 확인하거나, 변경 전후의 차이를 검토할 수 있는 기준이 필요했습니다. v2 확장 전에 기존 환경부터 Terraform으로 관리하도록 정리했습니다.

Terraform을 선택한 기준은 기존 AWS 자원을 import로 연결할 수 있고, plan으로 적용할 변경 사항을 먼저 확인하며, 설정을 Git에서 관리할 수 있다는 점입니다.

이번 작업의 목표는 현재 운영 환경을 그대로 코드에 반영하고, 기존 자원의 생성, 수정, 삭제 없이 Terraform 관리로 전환하는 것이었습니다. 인프라 개선과 구조 변경은 연결이 끝난 뒤 별도 작업으로 진행하도록 범위를 나눴습니다.

2. 관리 범위와 코드 구성

AWS에서 조회한 자원 ID와 설정을 기준으로 관리 대상을 정리했습니다. 기존 운영 자원 46개를 연결했으며, 여기서 46개는 서버 수가 아니라 Terraform resource 수입니다. S3 버전 관리나 IAM 정책 연결도 각각 하나로 계산합니다.

구분 관리 대상 개수 코드
네트워크 VPC, 서브넷, 인터넷 게이트웨이, 라우팅 테이블과 연결 8 network.tf
보안 백엔드, AI, DB 보안 그룹 3 security.tf
컴퓨팅 백엔드와 AI EC2, 백엔드 Elastic IP 3 compute.tf
DB RDS MySQL, DB 서브넷 그룹 2 database.tf
스토리지 업로드 S3와 버전 관리, 암호화, 공개 차단, 소유권, 수명 주기 6 storage.tf
이미지 저장소 백엔드와 AI ECR 2 registry.tf
권한 IAM 역할, 정책, 정책 연결, 인스턴스 프로파일, GitHub OIDC 20 iam.tf
환경 설정 백엔드와 AI Secrets Manager 메타데이터 2 secrets.tf
합계 기존 운영 자원 46

EC2 루트 디스크는 compute.tf의 root_block_device에서 함께 관리합니다. Secret 값은 코드에 넣지 않고 이름과 설명 등 메타데이터만 관리합니다. DB 레코드와 S3 업로드 파일도 Terraform 관리 대상에 포함하지 않았습니다.

terraform/
├── bootstrap/             # 전용 state 버킷과 관련 설정 5개
│   ├── main.tf
│   └── backend.tf
└── environments/prod/     # 기존 운영 자원 46개
    ├── network.tf ...     # 위 표의 영역별 자원 설정
    ├── imports.tf         # 기존 자원 ID와 Terraform 주소의 연결
    ├── backend.tf         # S3 state 저장 위치와 잠금
    ├── providers.tf       # AWS 계정과 리전 제한
    ├── versions.tf        # Terraform과 AWS Provider 버전
    └── outputs.tf         # EC2 ID, DB 주소, S3 이름

같은 실행 폴더의 .tf 파일들은 하나의 구성으로 읽힙니다. 파일은 관리 영역별로 나눴고, state는 운영 자원과 state 저장소를 기준으로 분리했습니다.

3. 기존 자원을 연결한 과정

① 현재 AWS 설정을 코드로 정리

AWS 조회 API로 자원 목록과 import ID를 수집한 뒤, AWS Provider로 기존 설정의 HCL을 생성했습니다. 생성한 설정을 영역별 파일로 나누고 자원 관계를 정리했습니다. 이때 Secret 값, DB 데이터, S3 파일 내용은 조회하지 않았습니다.

현재 설정과 코드가 다르면 import와 함께 수정이나 교체가 계획될 수 있습니다. 따라서 기존 값을 먼저 맞추고, 초기 plan에 인프라 변경이 섞이지 않도록 확인했습니다.

② 원격 state 저장소를 먼저 구성

전용 S3 버킷과 암호화, 버전 관리, 공개 차단, HTTPS 정책을 bootstrap 구성으로 생성했습니다. 버킷 생성에 사용한 로컬 state를 S3의 bootstrap 경로로 옮긴 뒤, 운영 구성도 같은 버킷의 별도 경로에 연결했습니다.

처음에는 state를 저장할 버킷이 없으므로 저장소를 먼저 만들고, 이후 원격 state로 옮기는 순서로 진행했습니다.

③ import 계획을 검토한 뒤 적용

imports.tf에 기존 AWS 자원 ID와 Terraform resource 주소를 연결했습니다. 이번 작업은 import 블록을 사용해 plan에서 연결 내용을 확인한 뒤, 저장한 계획을 apply하는 방식으로 진행했습니다.

예를 들어 기존 백엔드 EC2의 ID를 aws_instance.backend_server에 연결하면 Terraform이 그 서버를 관리 대상으로 인식합니다. 서버나 데이터를 다른 곳으로 옮기는 과정은 없습니다.

다만 import가 포함된 apply도 코드와 실제 설정이 다르면 자원을 변경할 수 있으므로, 적용 전에 다음 조건을 검사했습니다.

검사 항목 초기 연결 계획의 통과 조건
연결 대상 사전에 정리한 46개 자원 주소와 일치
import 수 46개
기존 자원 생성, 수정, 삭제 모두 0개
계획 JSON 검사 import 정보 외에 관리 자원의 변경 작업이 없어야 함

AWS CloudShell에서 인증된 세션을 사용해 저장한 계획을 적용했습니다. 기존 자원 연결에 새 장기 Access Key나 팀원 권한 추가는 필요하지 않았습니다.

④ 적용 후 다시 확인

apply 로그에서 46개 import와 기존 자원 변경 0개를 확인했습니다. 이어서 state 목록이 자원 목록과 일치하는지 확인하고, 운영 구성과 bootstrap 구성을 각각 다시 plan했습니다.

목록 조회 → 코드 작성 → state 저장소 구성 → import plan 검사 → 저장한 계획 적용 → state와 plan 재확인 순서로 진행했습니다.

4. state를 별도 S3에 저장한 이유

state는 Terraform 코드의 자원 주소와 실제 AWS 자원 ID, 조회한 속성을 기록하는 파일입니다. 팀원이 각자 로컬 state를 쓰면 같은 자원을 서로 다른 기준으로 관리할 수 있어, 공통 S3 backend를 사용하도록 구성했습니다.

여기서 별도 S3는 기존 업로드 버킷과 분리한 state 전용 버킷을 뜻합니다. 사용자 파일과 인프라 관리 파일의 접근 권한, 보관 정책을 따로 관리하기 위한 선택입니다. 원격 state를 사용하려면 각 실행자에게 해당 AWS 자원과 backend에 접근할 권한이 있어야 합니다.

같은 전용 버킷 안의 state key 관리 대상
memme/prod/terraform.tfstate 기존 운영 자원 46개
memme/bootstrap/terraform.tfstate state 버킷과 관련 설정 5개
설정 적용 이유
AES256 암호화 저장된 state 파일을 암호화
S3 버전 관리 state를 잘못 덮어쓰거나 손상했을 때 이전 파일을 복구할 수 있도록 보관
공개 접근 차단 state가 공개되지 않도록 제한
HTTPS 강제 정책 암호화되지 않은 전송을 거부
use_lockfile = true 같은 state를 사용하는 실행들이 동시에 state를 수정하지 않도록 잠금

암호화와 접근 권한은 각각 필요합니다. 버전 관리로 복구하는 대상은 state 파일이며, 실제 EC2나 DB를 과거 상태로 되돌리는 기능은 아닙니다. 이전 state를 복구한 경우에도 현재 AWS 설정과 다시 비교해야 합니다.

state와 plan 파일은 Git에 넣지 않았습니다. 저장소에는 설정 코드와 Provider 선택을 고정하는 .terraform.lock.hcl을 관리합니다. 이 파일은 S3의 실행 잠금 파일과 역할이 다릅니다.

5. 코드 작성 중 처리한 사항

보안 그룹의 서로 참조하는 규칙

백엔드와 AI, DB 보안 그룹에는 서로의 보안 그룹을 대상으로 허용하는 규칙이 있었습니다. 모든 상대 보안 그룹을 직접 resource 참조로 바꾸면 순환 의존성이 생길 수 있어, 이번 연결에서는 기존 상대 보안 그룹 ID를 유지했습니다.

현재 환경을 연결하는 데는 맞지만, 새 환경을 만드는 코드로 재사용하려면 규칙을 독립 resource로 분리하고 참조를 정리해야 합니다.

Secrets Manager의 변경 감지 범위

Secret 값은 Terraform으로 관리하지 않았습니다. 메타데이터를 연결할 때는 조회 API가 반환하지 않는 삭제 및 복제 요청 옵션인 recovery_window_in_days, force_overwrite_replica_secret만 ignore_changes로 제외했습니다.

전체 속성을 무시하지 않도록 범위를 두 항목으로 제한했고, 이름이나 태그 등 나머지 설정은 변경 감지 대상으로 유지했습니다.

삭제와 잘못된 계정 적용 방지

  • 모든 관리 자원에 prevent_destroy = true를 적용했습니다.
  • 기존 RDS의 deletion_protection = true, S3의 force_destroy = false를 유지했습니다.
  • Provider와 backend에 대상 AWS 계정을 제한하고, 서울 리전을 지정했습니다.
  • AWS Provider는 6.67.0으로 고정하고 Terraform 사용 범위는 >= 1.14, < 2.0으로 지정했습니다.

prevent_destroy가 있어도 resource 블록 자체를 삭제하면 해당 보호 설정도 사라집니다. 삭제 보호와 함께 매번 plan의 생성, 수정, 삭제 내용을 확인해야 합니다.

6. 적용 결과와 검증 근거

2026년 10월 6일 14:48 KST 연결 후 검증 기록 기준입니다.

Apply complete! Resources: 46 imported, 0 added, 0 changed, 0 destroyed.
검증 항목 결과
기존 운영 자원 46개 import, 기존 자원 생성 0개, 수정 0개, 삭제 0개
신규 state 저장소 전용 S3 버킷과 관련 설정 5개 생성
운영 state 목록 사전에 정리한 자원 주소 46개와 일치
bootstrap state 목록 저장소 설정 5개 확인
연결 후 plan -detailed-exitcode 두 구성 모두 exit 0, No changes
원격 state 두 key에서 AES256 암호화와 S3 Version ID 확인
버킷 보호 설정 버전 관리 Enabled, 공개 차단 4개 모두 true, HTTPS 정책 확인
실행 잠금 S3 lockfile 사용 설정, 검증 종료 후 남은 잠금 객체 없음
코드 검증 fmt -check, validate 통과
실행한 코드 일치 여부 로컬과 CloudShell의 운영 .tf 13개가 공백 제외 SHA-256 기준으로 일치

이 결과로 기존 자원 구성을 변경하지 않고 관리 대상을 연결했음을 확인했습니다. DB 데이터나 업로드 파일을 변경하는 작업은 수행하지 않았습니다. 별도의 서비스 가용성 시험이나 동시 apply 시험 결과는 이 검증에 포함하지 않았습니다.

7. 연결 이후 변경 방법과 남은 작업

이미 연결한 자원은 다시 import하지 않습니다. AWS 인증과 backend 접근 권한을 갖춘 환경에서 저장소 루트를 기준으로 실행합니다.

terraform -chdir=terraform/environments/prod init                     # Provider 설치 및 원격 state 연결
terraform -chdir=terraform/environments/prod fmt -check               # 코드 형식 검사
terraform -chdir=terraform/environments/prod validate                 # 문법과 설정 유효성 검사
terraform -chdir=terraform/environments/prod state list               # 현재 관리 중인 자원 주소 확인
terraform -chdir=terraform/environments/prod plan -out=change.tfplan  # 변경 계획을 확인하고 파일로 저장

plan의 대상과 변경 내용을 검토한 뒤 저장한 계획을 적용합니다.

terraform -chdir=terraform/environments/prod apply change.tfplan      # 검토한 계획을 그대로 적용
terraform -chdir=terraform/environments/prod plan -detailed-exitcode  # 적용 후 차이 확인: 0 없음 / 2 있음 / 1 오류

마지막 명령의 exit 0은 변경 없음, 2는 변경 있음, 1은 오류입니다. 초기 연결 때의 변경 0개 조건과 이후 의도한 인프라 변경은 구분해서 검토합니다.

ECS 실행 환경의 접근 규칙 반영

ECS 실행 환경을 구성하면서 기존 DB,AI 보안 그룹에 추가한 접근 규칙 두 개를 terraform/environments/prod/security.tf에도 반영했습니다. AWS에만 있던 규칙을 코드에 맞춰, 이후 Terraform 적용 시 ECS의 DB,AI 접근 규칙이 제거 대상으로 잡히지 않도록 정리한 변경입니다.

기존 관리 자원 추가한 수신 규칙 용도
aws_security_group.memme_db_sg ECS Backend SG → TCP 3306 ECS Backend의 MySQL 연결
aws_security_group.memme_ai_sg ECS Backend SG → TCP 8000 ECS Backend의 AI API 호출

출발지는 ECS Backend 보안 그룹 sg-010fbd5aa0b0c5651로 제한했습니다. 기존 EC2의 접근 규칙은 유지하고, 기존 ingress 목록에 각각 한 항목을 추가했습니다. 아래는 추가한 항목의 주요 필드이며, 실제 코드는 기존 목록 형식에 맞춘 나머지 필드도 포함합니다.

# DB 보안 그룹의 ingress에 추가
from_port       = 3306                       # MySQL 수신 포트
to_port         = 3306                       # 같은 포트만 허용
protocol        = "tcp"                      # TCP 연결
security_groups = ["sg-010fbd5aa0b0c5651"]    # ECS Backend SG에서 오는 요청만 허용

# AI 보안 그룹의 ingress에 추가
from_port       = 8000                       # AI API 수신 포트
to_port         = 8000                       # 같은 포트만 허용
protocol        = "tcp"                      # TCP 연결
security_groups = ["sg-010fbd5aa0b0c5651"]    # ECS Backend SG에서 오는 요청만 허용