·인프라

테라폼 딥다이브 — 인프라를 코드로 다루기

Terraform으로 인프라를 선언적으로 관리하는 법. HCL 문법부터 모듈화, 상태 관리까지.

#인프라#DevOps#Terraform

왜 인프라를 코드로 다루는가

서버를 만들고 설정하는 작업을 콘솔에서 클릭으로 처리하던 시대가 있었습니다. 작은 프로젝트에서는 충분했습니다. 그러나 서비스가 커지고 환경이 늘어나면서 문제가 생겼습니다. 스테이징과 프로덕션의 설정이 언제부터인가 달라져 있었습니다. 누가 언제 어떤 설정을 바꿨는지 추적할 방법이 없었습니다. 새 환경을 만들 때마다 같은 작업을 반복해야 했고, 실수가 끼어들 여지가 항상 있었습니다.

IaC(Infrastructure as Code)는 이 문제에 대한 답입니다. 인프라를 코드로 선언하면 버전 관리가 되고, 재현이 가능해지고, 리뷰가 가능해집니다. 서버 설정도 Pull Request로 논의할 수 있게 됩니다. Terraform은 이 패러다임의 대표 도구입니다. HashiCorp가 만들었고, 현재는 OpenTofu로 오픈소스 포크가 활발히 유지되고 있기도 합니다.

핵심 개념 다섯 가지

Terraform을 이해하려면 다섯 가지 개념부터 잡아야 합니다.

Provider는 Terraform이 어떤 클라우드나 서비스와 대화할지를 선언합니다. AWS, GCP, Azure, 심지어 GitHub이나 Datadog도 Provider가 있습니다. Provider는 해당 서비스의 API를 Terraform 리소스로 추상화해 줍니다.

Resource는 실제로 만들 인프라 단위입니다. EC2 인스턴스 하나, S3 버킷 하나, RDS 클러스터 하나가 각각 Resource입니다.

State는 Terraform이 현재 인프라 상태를 추적하는 파일입니다. terraform.tfstate에 저장되며, 이 파일이 Terraform이 실제 클라우드 상태와 코드를 비교하는 기준입니다.

Plan은 코드와 현재 상태를 비교해 무엇이 바뀔지 미리 보여주는 단계입니다. 실제 변경 없이 "이렇게 바뀔 것"을 먼저 확인합니다.

Apply는 Plan을 실제로 실행하는 단계입니다. 이 시점에 실제 클라우드 리소스가 생성, 수정, 삭제됩니다.

HCL로 쓰는 인프라

Terraform은 HCL(HashiCorp Configuration Language)이라는 선언형 언어를 사용합니다. JSON과 유사하지만 사람이 읽기 쉽게 설계되었습니다.

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_instance" "web" {
  ami           = "ami-0c9c942bd7bf113a2"
  instance_type = "t3.micro"

  tags = {
    Name        = "web-server"
    Environment = "production"
  }
}

output "instance_public_ip" {
  value = aws_instance.web.public_ip
}

리소스 블록의 구조는 resource "타입" "이름" 형태입니다. 다른 리소스를 참조할 때는 aws_instance.web.public_ip처럼 점 표기법을 씁니다. Terraform은 이 참조 관계를 분석해 자동으로 생성 순서를 결정합니다.

변수를 활용하면 재사용성이 높아집니다.

variable "environment" {
  type    = string
  default = "staging"
}

resource "aws_s3_bucket" "assets" {
  bucket = "my-app-assets-${var.environment}"
}

상태 관리 — 가장 중요한 부분

State 관리는 Terraform 운영에서 가장 중요하고 가장 많이 실수가 나는 부분입니다. 기본적으로 tfstate 파일은 로컬에 생성됩니다. 혼자 쓸 때는 문제없지만, 팀으로 일하면 누구의 로컬에 "진짜" 상태가 있는지 알 수 없게 됩니다.

해결책은 원격 backend입니다. AWS에서는 S3에 tfstate를 저장하고, DynamoDB로 State 잠금(Lock)을 구현합니다. 두 사람이 동시에 terraform apply를 실행할 때 State가 충돌하는 것을 DynamoDB Lock이 막아 줍니다.

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "ap-northeast-2"
    dynamodb_table = "terraform-lock"
    encrypt        = true
  }
}

State 파일에는 민감한 정보(비밀번호, API 키)가 평문으로 들어갈 수 있습니다. S3 버킷에 암호화와 접근 제어를 반드시 적용해야 합니다.

모듈화 전략

코드가 길어지면 구조화가 필요합니다. Terraform에서는 모듈로 인프라를 묶습니다. 디렉토리 하나가 모듈 하나입니다.

infrastructure/
├── main.tf
├── variables.tf
├── outputs.tf
└── modules/
    ├── vpc/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    └── rds/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

루트 모듈에서 서브 모듈을 호출할 때는 이렇게 씁니다.

module "vpc" {
  source = "./modules/vpc"

  cidr_block  = "10.0.0.0/16"
  environment = var.environment
}

module "rds" {
  source = "./modules/rds"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnet_ids
}

잘 설계된 모듈은 환경(staging/production)을 변수 하나로 전환할 수 있게 해 줍니다.

실전에서 겪은 함정들

실제로 운영하다 보면 문서에서 다루지 않는 어려움들을 만납니다.

State drift가 대표적입니다. 누군가 콘솔에서 직접 리소스를 수정하면 실제 상태와 tfstate가 어긋납니다. 다음 plan에서 Terraform이 변경된 값을 되돌리려 합니다. 이를 방지하려면 콘솔 직접 수정을 팀 규칙으로 금지하고, drift를 감지하는 파이프라인을 주기적으로 돌려야 합니다.

기존 리소스 import 문제도 있습니다. Terraform 도입 전에 만들어진 리소스를 State로 가져와야 할 때는 terraform import 명령을 씁니다. 하지만 import는 State만 가져오고 HCL 코드는 자동 생성하지 않습니다. Terraform 1.5부터 import 블록과 terraform plan -generate-config-out으로 코드 생성도 지원하기 시작했지만, 결과물을 반드시 검토해야 합니다.

의존성 순서도 놓치기 쉽습니다. Terraform은 참조 관계를 보고 순서를 자동으로 결정하지만, 암묵적 의존성은 파악하지 못합니다. IAM 역할이 생성되기 전에 그 역할을 사용하는 Lambda가 먼저 만들어지는 경우가 생길 수 있습니다. 이런 경우 depends_on으로 명시적 의존성을 선언해야 합니다.

인프라도 코드처럼

소프트웨어 개발에서 당연하게 여기는 것들 — 코드 리뷰, 테스트, 린트, CI/CD — 이 모든 것이 인프라에도 적용됩니다. terraform fmt로 코드 포맷을 맞추고, terraform validate로 문법을 확인하고, tflintcheckov로 보안 취약점을 점검합니다. terratest로 실제 리소스를 생성하고 검증하는 통합 테스트도 가능합니다.

인프라 변경은 애플리케이션 배포보다 롤백이 어렵고 영향 범위가 넓습니다. 그렇기 때문에 더더욱 신중하게, 코드처럼 다뤄야 합니다. Pull Request로 검토하고, 파이프라인에서 Plan 결과를 확인하고, 승인 후에 Apply하는 것이 기본입니다.

인프라를 코드로 관리하는 것은 번거로운 작업이 아닙니다. 그것은 인프라를 소프트웨어와 같은 수준의 신뢰성으로 다루겠다는 태도입니다.

글이 도움이 됐다면 GitHub에서 이야기 나눠요.