테라폼 의존성 에러 총정리: Cycle, depends_on 오용, known after apply
테라폼은 리소스 참조로 의존성 그래프를 만들고 그 순서대로 실행합니다. 이 그래프에서 나오는 문제는 세 갈래로 정리됩니다. 그래프가 순환해서 순서를 정할 수 없는 Error: Cycle, 의존을 사람이 과잉 선언해서 생기는 depends_on 남용, 그리고 그래프는 만들어졌지만 값이 계산 전이라 개수를 정할 수 없는 known after apply 문제입니다. 셋 다 에러 메시지는 다르지만 뿌리가 같으므로 한 글로 묶어 다룹니다. 의존성과 반복의 기본 문법은 기초 #6에서 다뤘습니다.
Error: Cycle: 그래프가 순환할 때 #
에러 메시지에 Cycle이라는 단어와 함께 리소스 목록이 나오면, 그 목록이 서로를 참조하는 고리를 이루고 있다는 뜻입니다. 테라폼은 순환 그래프의 실행 순서를 정할 수 없으므로 plan 단계에서 멈춥니다.
실무 대표 사례는 보안 그룹 상호 참조입니다. 웹 SG의 인바운드 규칙이 앱 SG를 참조하고, 앱 SG의 아웃바운드 규칙이 웹 SG를 참조하는 구성을 인라인 규칙(ingress·egress 블록)으로 쓰면 두 리소스가 서로를 필요로 하는 순환이 됩니다. 해법은 규칙을 별도 리소스로 분리하는 것입니다. SG 두 개를 규칙 없이 먼저 만들고, aws_vpc_security_group_ingress_rule 같은 분리 리소스가 두 SG를 참조하게 하면 그래프가 SG → 규칙의 한 방향이 되어 순환이 사라집니다. SG 체이닝을 처음부터 분리 리소스로 설계하는 이유는 실전 #3 EC2와 보안 그룹에서 다룬 대로이고, Cycle 에러는 그 설계 원칙을 지키지 않았을 때 만나는 결과 쪽입니다.
모듈 단위의 순환도 같은 구조입니다. 모듈 A의 입력이 모듈 B의 출력을, B의 입력이 A의 출력을 받으면 모듈 전체가 고리가 됩니다. 이 경우 해법은 규칙 분리의 모듈판입니다. 양쪽이 서로 필요로 하는 값을 제3의 선행 리소스(또는 상위 스택)로 끌어올려 A와 B가 둘 다 그것을 참조하게 만듭니다. 진단이 어려운 큰 그래프는 terraform graph | dot -Tsvg > graph.svg로 그려 보면 고리가 시각적으로 드러나지만, 대부분은 에러 메시지의 사이클 목록을 위에서 아래로 읽으며 “누가 누구를 왜 참조하는가"를 따라가는 것으로 충분합니다.
depends_on: 기본은 암묵 의존, 선언은 예외입니다 #
테라폼의 의존성은 참조에서 자동으로 나옵니다. subnet_id = aws_subnet.main.id라고 쓰는 순간 서브넷 → 인스턴스 순서는 확정되고, 별도 선언이 필요 없습니다. depends_on은 참조로 표현되지 않는 숨은 의존에만 쓰는 예외 도구입니다. 전형적인 정당한 용도는 IAM 정책 연결이 끝난 뒤에 그 권한을 쓰는 리소스를 만들어야 하는 경우처럼, 데이터 흐름은 없지만 시간 순서는 있는 관계입니다.
문제는 “혹시 몰라서” 붙이는 습관입니다. 남용의 부작용은 두 가지입니다. 첫째, 그래프가 필요 이상으로 직렬화되어 병렬 실행 폭이 줄고 plan·apply가 느려집니다. 둘째, 더 조용한 부작용으로, depends_on 대상의 변경이 그것을 선언한 리소스의 재계산을 유발해 바꾸지 않은 리소스에 diff나 재생성 연쇄가 나타날 수 있습니다. 특히 모듈에 건 depends_on은 모듈 안 모든 리소스에 그 의존을 강제하므로 파급이 큽니다. 리뷰에서 depends_on을 만나면 “이 의존이 참조로 표현될 수는 없는가"를 먼저 묻고, 참조로 바꿀 수 있으면 바꾸는 것이 정리 방향입니다.
known after apply: 개수는 plan 때 정해져야 합니다 #
plan 출력의 (known after apply)는 에러가 아닙니다. 새로 만들 리소스의 ID처럼 apply가 끝나야 정해지는 값의 정상 표기입니다. 문제가 되는 것은 이 값이 count나 for_each의 키로 들어갈 때입니다. 테라폼은 plan 시점에 리소스가 몇 개인지 확정해야 하는데, 개수를 정하는 값이 계산 전이면 “Invalid count argument"나 “Invalid for_each argument” 에러로 멈춥니다.
전형 사례는 새로 만드는 리소스의 속성 목록으로 for_each를 돌리는 코드입니다. 해법은 키를 계산 전 값에서 정적 값으로 바꾸는 것입니다. 만들 서브넷의 ID(계산 후 값)가 아니라 코드에 이미 적혀 있는 AZ 이름·이름표 같은 입력 값(정적 값)을 키로 삼으면, 개수는 plan 때 확정되고 계산 후 값은 각 항목의 속성 쪽에서만 쓰이게 됩니다. 기초 #6에서 for_each 키를 안정된 문자열로 잡으라고 한 원칙과 같은 이야기입니다.
구조를 바꿀 수 없는 경우의 우회는 두 단계 apply입니다. 선행 리소스를 먼저 apply해 값을 확정시킨 뒤 나머지를 apply하는 방식인데, 이는 -target과 마찬가지로 부분 적용 상태를 만드는 임시 수단이지 설계가 아닙니다. 두 단계가 상시로 필요하다면 그 지점이 스택 분할 경계라는 신호로 읽는 것이 맞습니다.
정리 #
- 세 문제의 뿌리는 하나입니다. 테라폼은 참조로 그래프를 만들고, 그래프는 순환이 없어야 하며, 개수는 plan 때 확정되어야 합니다.
- Error: Cycle의 대표는 SG 인라인 규칙 상호 참조입니다. 규칙을 분리 리소스로 빼면 그래프가 한 방향이 됩니다. 모듈 순환은 공유 값을 상위로 끌어올려 끊습니다.
- depends_on은 참조로 표현 안 되는 숨은 의존 전용입니다. 남용하면 실행이 직렬화되고, 무관한 리소스에 diff·재생성 연쇄가 번집니다.
- known after apply 값이 count·for_each 키에 들어가면 에러입니다. 키를 정적 입력 값으로 바꾸는 것이 해법이고, 두 단계 apply는 임시 수단입니다.
- 진단 도구는 에러 메시지의 사이클 목록과 terraform graph입니다. 상시로 우회가 필요해지는 지점은 스택 분할 신호로 읽습니다.