테라폼

3-0. 테라폼 - 리소스의 분류 및 구현

pininini 2026. 5. 5. 15:46

테라폼 - 리소스의 분류 및 구현

의존성 문제를 해결하기 위한 생성 관점 모델


이번 글에서는 Terraform으로 인프라 리소스를 구현할 때 코드를 어떻게 작성하는지 간단히 살펴보고, 의존성 문제를 해결하기 위해 필자가 분류해본 리소스 계층 모델을 정리해보려 한다.

다만 이 글에서 설명하는 분류 방식은 개인적으로 정리한 방식일 뿐, 절대적인 기준은 아니다. 더 좋은 방법이 있거나 틀린 점, 보완할 점이 있다면 피드백을 환영한다.

본 글은 AWS 기반으로 설명한다.


목차

  • 1. 문제에서 시작
  • 2. 해결 접근
  • 3. 계층 (Layer)
  • 4. 속성 (Property)
  • 5. 헷갈리는 개념 정리
  • 6. 리소스 vs 서비스/시스템
  • 7. 서비스/시스템을 Terraform resource 단위로 분해하기
  • 8. 체크리스트
  • 9. 활용 방법
  • 10. 마무리

1. 문제에서 시작

테라폼으로 리소스들을 구현하다 보면 배포 시 몇 가지 문제가 생긴다.

가장 대표적인 것이 의존성 문제다.

a라는 리소스를 만들기 위해 b라는 리소스의 ARN이 필요하다고 가정해보자.

ARN은 AWS Resource Name의 약자로, AWS 리소스를 식별하기 위한 고유한 이름 정도로 이해하면 된다.

이 상황에서 단순하게 생각하면 다음과 같다.

a를 먼저 만들고
b를 만들면 되지 않을까?

하지만 논리적으로는 그렇지 않다.

b가 없으면 a는 생성 자체가 불가능하다

즉, 올바른 생성 순서는 다음과 같다.

b → a

다만 Terraform은 이런 기본적인 문제를 자동으로 해결해준다.

리소스 간의 참조 관계를 분석해서 내부적으로 생성 순서를 재정렬하기 때문이다.

예를 들어 Subnet이 VPC를 참조하고 있다면, Terraform은 VPC를 먼저 만들고 이후 Subnet을 생성한다.

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
}

위 코드에서는 Subnet이 aws_vpc.main.id를 참조하고 있기 때문에, Terraform은 VPC가 먼저 필요하다는 것을 알 수 있다.

Terraform이 자동으로 순서를 맞춰주기 때문에 겉으로는 문제가 없어 보일 수 있다. 하지만 이 동작은 구조를 이해하지 않아도 된다는 의미는 아니다.

복잡한 구조에서는 다음과 같은 문제가 발생한다.

  • 순환 참조
  • 생성 후 대기 문제
  • 연결 타이밍 문제
  • 실행 시점 문제

이러한 문제는 Terraform의 기본 dependency 처리만으로 해결되지 않는 경우가 많다.


2. 해결 접근

이 문제를 해결하기 위해 필자는 리소스를 다음 기준으로 분류해보았다.

1. 독립적으로 생성 가능한가
2. 다른 리소스를 참조해야 생성 가능한가
3. 생성 후 연결이 필요한가

이 기준은 일반적인 아키텍처 계층이 아니라, Terraform 배포와 생성 과정을 이해하기 위한 기준이다.

즉, 리소스를 “무슨 서비스인가?”가 아니라 “어떤 조건에서 생성되고 동작하는가?”로 바라보는 방식이다.


3. 계층 (Layer)

계층은 리소스의 생성 구조를 의미한다.

Independent → Dependent → Binding

각 계층은 다음과 같이 구분할 수 있다.


3.1 Independent

Independent는 외부 리소스 없이 독립적으로 생성 가능한 리소스를 의미한다.

특징은 다음과 같다.

- 참조할 리소스가 없음
- 생성 조건이 단순함
- 보통 가장 먼저 생성됨

예시는 다음과 같다.

  • VPC
  • IAM Role
  • S3 Bucket

예를 들어 VPC는 다른 AWS 리소스의 ID나 ARN이 없어도 생성할 수 있다.

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "main-vpc"
  }
}

물론 실제 운영에서는 VPC와 함께 Subnet, Route Table, Internet Gateway 등이 함께 구성된다. 하지만 VPC 리소스 자체는 독립적으로 생성 가능하다.


3.2 Dependent

Dependent는 다른 리소스를 참조해야 생성 가능한 리소스를 의미한다.

특징은 다음과 같다.

- ID 또는 ARN이 필요함
- 참조 대상이 없으면 생성 실패
- Terraform dependency graph의 영향을 받음

예시는 다음과 같다.

  • Subnet (VPC 필요)
  • Security Group (VPC 필요)
  • Target Group (VPC 필요)

Subnet은 반드시 VPC 안에 생성되어야 한다. 따라서 VPC ID가 필요하다.

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true

  tags = {
    Name = "public-subnet"
  }
}

이 경우 Subnet은 VPC를 참조해야만 생성할 수 있으므로 Dependent에 해당한다.

참조는 생성 조건이다.

3.3 Binding

Binding은 생성 후 다른 리소스와 연결되어야 의미가 있는 리소스 또는 관계를 의미한다.

특징은 다음과 같다.

- 생성만으로는 서비스가 완성되지 않음
- 연결 이후 의미가 생김
- 연결 타이밍이 중요함

예시는 다음과 같다.

  • ECS Service ↔ Target Group
  • CloudFront ↔ Origin
  • Lambda ↔ EventBridge

예를 들어 ECS Service는 Cluster와 Task Definition만으로도 생성할 수 있다. 하지만 외부 트래픽을 받으려면 Target Group이나 Load Balancer와 연결되어야 한다.

resource "aws_ecs_service" "app" {
  name            = "app-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn

  desired_count = 0
}

여기서 중요한 점은 처음부터 실행 상태로 만들지 않고, 연결이 완료된 뒤 scale up 하는 방식이 더 안전할 수 있다는 것이다.

연결은 실행 조건이다.

4. 속성 (Property)

계층과 별개로 리소스는 추가적인 동작 특성을 가질 수 있다.

이 글에서는 계층과 속성을 다음처럼 구분한다.

Layer는 하나만 가진다
Property는 여러 개 가질 수 있다

예를 들어 어떤 리소스는 Dependent이면서 동시에 Ready-sensitive일 수 있다. 또 어떤 리소스는 Binding이면서 Runtime-config 성격을 가질 수도 있다.


4.1 Optional-dependent

Optional-dependent는 옵션에 따라 의존성이 발생하는 경우를 의미한다.

기본적으로는 독립적으로 생성 가능하지만, 특정 옵션을 활성화하면 다른 리소스가 필요해지는 경우다.

예시는 다음과 같다.

  • ECS Service: load_balancer 사용 시 Target Group 필요
  • Lambda: VPC 설정 시 Subnet, Security Group 필요
resource "aws_lambda_function" "app" {
  function_name = "app"
  role          = aws_iam_role.lambda.arn
  handler       = "index.handler"
  runtime       = "nodejs20.x"

  # 이 설정을 사용하면 Subnet과 Security Group이 필요해진다.
  vpc_config {
    subnet_ids         = [aws_subnet.private.id]
    security_group_ids = [aws_security_group.lambda.id]
  }
}

4.2 Ready-sensitive

Ready-sensitive는 생성 후 바로 사용할 수 없는 리소스를 의미한다.

Terraform 입장에서는 리소스 생성이 완료되었지만, 실제 서비스 관점에서는 아직 준비되지 않은 상태일 수 있다.

created != usable

예시는 다음과 같다.

  • RDS
  • CloudFront
  • ACM

RDS는 생성되더라도 실제 접속 가능 상태가 되기까지 시간이 걸릴 수 있다. CloudFront 역시 Distribution 생성 후 전 세계 엣지 로케이션에 배포되기까지 시간이 필요하다.


4.3 Manual-step

Manual-step은 자동화 이후에도 수동 작업이 필요한 경우를 의미한다.

예시는 다음과 같다.

  • ACM DNS 검증
  • 외부 도메인 연결
  • 외부 DNS 제공자 설정

Route53을 함께 사용하면 ACM DNS 검증도 Terraform으로 자동화할 수 있다. 하지만 외부 DNS를 사용하거나 도메인 소유권 설정이 필요한 경우에는 수동 작업이 필요할 수 있다.


4.4 Async

Async는 생성은 되었지만 실제 반영까지 시간이 필요한 경우를 의미한다.

예시는 다음과 같다.

  • CloudFront 배포
  • IAM 정책 전파
  • DNS propagation

이 경우 단순히 Terraform apply가 끝났다고 해서 즉시 다음 작업을 수행하면 실패할 수 있다.


4.5 Runtime-config

Runtime-config는 실행 시점에 변경되거나 CI/CD에서 관리하는 것이 더 적절한 설정을 의미한다.

예시는 다음과 같다.

  • ECS desired_count
  • Lambda 환경 변수
  • 스케줄러 enable/disable

예를 들어 ECS의 desired_count는 Terraform에서 고정하기보다 CI/CD나 Auto Scaling이 관리하도록 분리하는 것이 더 나을 수 있다.

lifecycle {
  ignore_changes = [desired_count]
}

4.6 External-dependency

External-dependency는 Terraform 밖의 외부 시스템에 의존하는 경우를 의미한다.

예시는 다음과 같다.

  • 외부 DNS
  • SaaS API
  • 외부 인증 시스템

5. 헷갈리는 개념 정리

Dependent vs Binding

Dependent와 Binding은 가장 헷갈리기 쉽다. 둘 다 다른 리소스가 필요하기 때문이다.

하지만 기준은 명확하다.

참조 = 생성에 필요
연결 = 실행에 필요

판단 기준은 다음과 같다.

이 리소스 없이 생성 가능한가?

가능 → Binding
불가능 → Dependent

예를 들어 Subnet은 VPC 없이 생성할 수 없다. 따라서 Dependent다.

반면 ECS Service는 Load Balancer 연결 없이도 생성 자체는 가능하다. 하지만 외부 트래픽을 받으려면 Target Group과 연결되어야 한다. 이 경우 Load Balancer 연결은 Binding 성격을 가진다.


6. 리소스 vs 서비스/시스템

이 모델에서 가장 중요한 주의점은 리소스와 서비스/시스템을 구분하는 것이다.

ECS나 CloudFront는 하나의 리소스처럼 말하지만, 실제 Terraform 코드에서는 여러 리소스가 결합된 시스템에 가깝다.

예를 들어 ECS를 구성하려면 보통 다음 리소스들이 필요하다.

ECS
- aws_ecs_cluster
- aws_ecs_task_definition
- aws_ecs_service
- aws_iam_role
- aws_lb_target_group
- aws_lb
- aws_lb_listener

CloudFront도 마찬가지다.

CloudFront
- aws_cloudfront_distribution
- aws_acm_certificate
- aws_route53_record
- aws_wafv2_web_acl
- aws_wafv2_web_acl_association

따라서 이 모델에서 분류는 서비스 단위가 아니라 Terraform resource 단위로 해야 한다.

서비스 단위 X
Terraform resource 단위 O

즉, ECS 전체를 Independent / Dependent / Binding 중 하나로 분류하려고 하면 안 된다. ECS를 구성하는 각각의 Terraform resource를 분리해서 판단해야 한다.


7. 서비스/시스템을 Terraform resource 단위로 분해하기

앞에서 설명한 것처럼 ECS나 CloudFront 같은 서비스는 단일 리소스가 아니다. 따라서 전체 서비스를 하나의 계층으로 분류하지 않고, 내부 Terraform resource 단위로 분해해서 바라보는 것이 더 정확하다.


7.1 ECS 구성 분해

Terraform Resource 분류 이유
aws_ecs_cluster Independent 외부 리소스 없이 생성 가능
aws_iam_role Independent 역할 자체는 독립 생성 가능
aws_ecs_task_definition Dependent 일반적으로 execution role, task role 등을 참조
aws_lb_target_group Dependent VPC ID 필요
aws_ecs_service Dependent Cluster, Task Definition 필요
load_balancer block Binding ECS Service와 Target Group을 연결
desired_count Runtime-config 실행 수량은 배포/스케일링 단계에서 관리 가능

이렇게 보면 ECS는 하나의 계층으로 분류되는 것이 아니라, 여러 리소스와 속성이 결합된 시스템이라는 점이 명확해진다.

ECS 전체 = 분류 대상 아님

aws_ecs_cluster         → Independent
aws_iam_role            → Independent
aws_ecs_task_definition → Dependent
aws_lb_target_group     → Dependent
aws_ecs_service         → Dependent
load_balancer block     → Binding
desired_count           → Runtime-config

7.2 CloudFront 구성 분해

Terraform Resource 분류 이유
aws_acm_certificate Independent 인증서 요청 자체는 독립적으로 가능
aws_acm_certificate_validation Ready-sensitive / Manual-step DNS 검증 완료가 필요
aws_cloudfront_distribution Dependent Origin, 인증서 등을 참조
aws_cloudfront_distribution Async 배포 완료까지 시간이 걸림
aws_route53_record Binding 도메인을 CloudFront에 연결
aws_wafv2_web_acl_association Binding WAF를 CloudFront에 연결

CloudFront도 마찬가지로 하나의 계층으로 분류하기 어렵다. Distribution, Origin, ACM, DNS, WAF가 결합되어 하나의 CDN 시스템을 구성하기 때문이다.

CloudFront 전체 = 분류 대상 아님

aws_acm_certificate            → Independent
aws_acm_certificate_validation → Ready-sensitive / Manual-step
aws_cloudfront_distribution    → Dependent / Async
aws_route53_record             → Binding
aws_wafv2_web_acl_association  → Binding

따라서 이 모델을 사용할 때는 항상 다음 순서를 지키는 것이 좋다.

1. 먼저 서비스/시스템을 식별한다.
2. 그 시스템을 Terraform resource 단위로 분해한다.
3. 각 resource를 Layer와 Property로 분류한다.

8. 체크리스트

리소스를 분류할 때는 다음 질문을 순서대로 던져보면 된다.

1. 독립 생성 가능한가?
   → Independent

2. 다른 리소스 없으면 생성 불가한가?
   → Dependent

3. 생성 후 연결이 필요한가?
   → Binding

4. 생성 후 바로 사용 가능한가?
   → Ready-sensitive

5. 옵션에 따라 의존성이 생기는가?
   → Optional-dependent

6. 수동 작업이 필요한가?
   → Manual-step

7. 반영까지 시간이 걸리는가?
   → Async

8. 실행 시점에 바뀌는 설정인가?
   → Runtime-config

9. 활용 방법

이 모델은 실제 배포 과정에도 그대로 적용할 수 있다.

Independent → 먼저 생성
Dependent → 이후 생성
Ready-sensitive → 대기 처리
Binding → 연결 단계 분리
Runtime-config → CI/CD에서 처리

정리하면 다음과 같다.

Layer → Terraform 구조 설계에 반영
Property → CI/CD 단계에서 처리

즉, Terraform은 리소스의 구조를 만들고, CI/CD는 Ready, Binding, Runtime-config 같은 동작 단계를 보완하는 역할을 할 수 있다.


10. 마무리

이 모델은 일반적인 아키텍처 계층이 아니다.

인프라 리소스를 배포와 생성 관점에서 이해하기 위한 모델이다.

Terraform을 사용하다 보면 단순히 코드를 작성하는 것보다 더 중요한 것이 있다.

바로 리소스가 어떤 조건에서 생성되고, 언제 사용 가능하며, 어떤 시점에 연결되어야 하는지를 이해하는 것이다.

리소스를 단순히 나열하는 것이 아니라 생성 조건과 동작 특성으로 분류하면, 의존성 문제나 배포 실패 원인을 훨씬 명확하게 파악할 수 있다.


한 줄 정리

리소스를 생성 조건으로 나누는 순간 인프라 구조가 보인다.