배경
저는 현업에서 DevOps 직무로 일하고 있으며, IaC 도구 중에서는 Ansible을 사용해 왔고 최근에는 Terraform을 통해 Cloud 인프라를 코드로 생성하고 관리하고 있습니다.
과거에는 Terraform을 “알고만” 있던 상태였고, 러닝커브와 시간적 비용을 이유로 AWS 콘솔 중심으로 인프라를 생성·관리해 왔습니다.
하지만 운영 환경에서 콘솔 기반 관리의 한계를 실제로 겪은 이후, 인프라는 결국 코드로 남겨야 한다는 판단을 하게 되었고 Terraform을 본격적으로 도입하게 되었습니다.
이 글은 Terraform의 개념을 정리하는 데에도 있지만 제가 직접 겪은 운영 이슈를 기준으로 “Terraform을 쓰지 않으면 왜 문제가 커지는지”와 “그래서 Terraform을 왜 도입해야 하는지”를 제 관점에서 정리하는 데 있습니다.
이 글은 트러블슈팅 자체를 상세 재현하는 글이 아니라, 트러블슈팅 경험이 전달하는 핵심 요지만 먼저 정리하는 글로 구성합니다.
상세한 재현 과정과 실무 코드 예시는 별도 글로 분리하여 작성하고, 이 글에서는 필요한 지점에 참조로 연결하는 방식으로 가독성을 유지하려고 합니다.
개요(이 글에서 다루는 내용과 범위)
- 콘솔 기반 운영에서 어떤 문제가 발생했고, 왜 원인 범위(클라우드/온프렘)를 초기에 좁히기 어려웠는지 정리합니다.
- Terraform이 무엇인지, 무엇을 해결하는 도구인지 제 관점의 정의로 정리합니다.
- Terraform의 기본 흐름(Write/Plan/Apply)과 State가 왜 필요한지 이 글에서 필요한 수준으로만 설명합니다.
- 이후 글에서 반복해서 사용할 핵심 개념(provider, state, backend, remote state, module, variables, output)을 짧게 짚습니다.

Terraform이란 도구가 무엇인지
- Terraform은 인프라 구성을 HCL 파일로 선언하고, 그 선언과 실제 클라우드 상태를 비교해서 어떤 리소스를 만들고/바꾸고/지울지 결정해 주는 도구입니다.
- 이 선언 파일들은 일반 코드처럼 버전 관리할 수 있고, 변경 이력을 남길 수 있기 때문에 “이 값은 원래 이랬다가, 언제부터 이렇게 바뀌었다”를 기록으로 남길 수 있다는 점이 중요합니다.
위처럼 파일로 Cloud의 리소스를 Define 하고 Plan으로 진행사항을 미리 확인하며 이상이 없을 경우 Apply를 진행하여 생성한 결과물이 State의 형태로 남게 되는 과정을 진행해 주는 도구를 Terraform이라고 할 수 있겠습니다.
Terraform에 대해 더 상세히 알아보기 전 Terraform을 왜 사용해야만 하는지를 이해할 수 있는 제 경험을 설명드리겠습니다.
콘솔로 인프라를 관리하며 겪은 문제점(짧은 Trouble shutting)
- 저는 AWS에서 Virtual Private Gateway 기반 Site-to-Site VPN(VPN-A)을 AWS 콘솔로 구축해 운영한 경험이 있습니다.
- 이후 동일한 Virtual Private Gateway에 추가 VPN(VPN-B)을 구성해야 했고, VPN-A 설정과 콘솔 값을 대조하며 VPN-B를 구축했습니다.
- 온프렘 장비가 BGP를 지원하지 않아 두 VPN은 정적 라우팅(Static routing)으로 구성해야 했으며, 초기 구성에서는 VPN-B에 필요한 Routing table 및 목적지 ip 설정 일부가 누락되어 라우팅 테스트가 실패했습니다.
- 이를 보완하는 과정에서 VPN-A의 정적 라우트/VPC Route Table 설정 방식을 그대로 맞추면서 192.168.0.0/16처럼 넓은 목적지 CIDR이 함께 반영되었고, 결과적으로 라우트 범위가 겹치면서 두 VPN의 목적지를 구별하지 못해 VPN이 불안정해졌습니다.
- 문제를 분리하기 위해 VPN-B를 우선 분리한 뒤 원인 추적을 진행했지만, 콘솔 기반 구성만으로는 변경 이력과 차이를 선언된 코드처럼 빠르게 비교하기 어려워 문제 범위를 클라우드/온프렘 중 어디로 먼저 좁혀야 하는지 판단 근거를 만들기 어려웠습니다.
- 최종 원인은 온프렘 라우팅 테이블 설정이었지만, 그 과정에서 “AWS 쪽 구성이 이전과 완전히 동일하다”를 보장할 수단이 없다는 점이 트러블슈팅 비용을 키웠습니다.
- 경험을 길게 설명 드릴 순 없지만 소모됐던 기간과 그에 따른 결론은 하단과 같습니다.
VPN 구성을 처음 만들 때는 5일이 걸렸지만, VPN을 다시 구성할 땐 하루가 걸린다고 생각했지만 콘솔 기반 재구성과 수정, 디버깅의 어려움 때문에 3일이나 소모되고 말았습니다. 이 경험을 통해 처음부터 Terraform으로 선언하고 버전 관리했다면 AWS 설정을 쉽게 비교·복구할 수 있었고, 트러블슈팅 범위도 On-Premise로 더 빨리 좁힐 수 있었겠다는 결론에 이르렀습니다.
자세한 과정은 Trouble Shutting의 성격의 글로 만들어 참조 * 링크로 걸어 문서를 업데이트해두겠습니다.
경험을 통해 알아본 Terraform으로 관리하지 않고 콘솔로 인프라를 관리하면 생기는 핵심 문제점
- 설계 의도가 흩어지며 코드로 남아있지 않습니다.
- 설정 화면 곳곳에 전제가 분산되어 “왜 이렇게 구성했는지”가 문서/코드로 남지 않습니다.
- 시간이 지나면 나 자신도 “원래 이렇게 설계하려고 했던 것이 맞는지”, “중간에 편의상 CIDR을 넓힌 적이 있었는지”에 대한 기억에 의존해야 하며 이는 불확실성을 야기하게 됩니다.
- 변경 이력을 복원하기 어렵습니다.
- 누가 언제 어떤 값을 바꿨는지 추적이 어렵고, 결국 추측으로 원인 분석을 시작하게 됩니다.
- 동일 구성을 재현하기 어렵고 일관성이 깨지기 쉽습니다.
- 환경/리전이 달라지면 동일 리소스에 대해 클릭 순서·옵션 선택이 미세하게 달라질 수 있고 명확한 코드와 달리 문서를 따라가며
콘솔을 사용하거나 불와전한 기억에 의존하는 특성상 일관성을 유지하기가 어렵습니다.
- 환경/리전이 달라지면 동일 리소스에 대해 클릭 순서·옵션 선택이 미세하게 달라질 수 있고 명확한 코드와 달리 문서를 따라가며
- 디버깅 비용이 커진다.
- “이전과 무엇이 달라졌는지”를 선언된 코드 간의 차이로 확인할 수 없기 때문에, 리소스를 하나씩 삭제하고 다시 생성하면서 원인을 추측해야 합니다.
- 실제 원인이 온프렘 방화벽의 라우팅 테이블에 있더라도, AWS 측 구성이 코드로 고정되어 있지 않으면 “내가 콘솔에서 무언가를 잘못 클릭한 것은 아닌가”라는 의심을 먼저 하게 됩니다.
그렇다면 Terraform의 핵심 개념은?

- provider
- Terraform이 외부 시스템(AWS, Azure, GCP, SaaS, 온프렘 API 등)과 통신할 수 있게 해 주는 플러그인 단위입니다.
- provider "aws" {... }처럼 설정하면, 해당 provider가 VPC, Route Table, VPN, ALB 같은 AWS 리소스 타입과 API 호출 방법을 제공합니다.
- resource
- 실제 인프라 객체 한 개를 나타내는 선언 단위입니다. ( ex: aws_vpc, aws_subnet)
- resource "<TYPE>" "<NAME>" {... } 형식으로 쓰고, 어떤 타입을 쓸 수 있는지는 provider가 결정합니다.
- state
- “이 Terraform 설정의 각 resource 인스턴스가 실제로 어떤 원격 객체(예: AWS 리소스 ID)와 연결되어 있는지”를 저장하는 스냅샷 파일이다. 일반적으로 terraform.tfstate라는 JSON 파일로 관리됩니다.
- Terraform은 매번 코드 vs state vs 실제 인프라를 비교해서, 어떤 리소스를 생성/변경/삭제할지 계산한다. 이 매핑이 없으면 Terraform이 정상적으로 동작할 수 없습니다.
- backend
- state 데이터를 어디에, 어떤 방식으로 저장할지를 정의하는 설정 블록입니다.
- root module 안의 terraform { backend "<TYPE>" {... } }에서 정의하며, 예를 들어 backend "s3"를 쓰면 state를 S3 버킷에 저장하고, 필요하면 DynamoDB 락을 써서 동시에 둘이 apply 하는 상황을 막을 수 있습니다.
- remote state
- 의미 1: state 파일을 로컬이 아니라 S3/GCS/HCP Terraform 같은 원격 저장소에 두고, 여러 실행/여러 사람/여러 구성에서 공유하는 패턴 전체를 가리킵니다.
- 의미 2: 다른 Terraform 구성에서, 특정 state의 root module outputs를 읽어오기 위해 쓰는 data "terraform_remote_state" "..." {... } data source를 가리킬 때도 사용합니다,
- module
- 하나의 디렉터리 안에 있는. tf 파일 묶음 전체를 module이라고 부릅니다.
- 간단한 구성도 “root module 1개”인 module이고, 여기서 module "vpn_onprem_a" { source = "./modules/vpn_onprem"... }처럼 child module들을 호출해서 VPC/VPN/ALB 같은 패턴을 재사용할 수 있게 됩니다.
- variables (input variables)
- variable "name" {... } 블록으로 선언하는 입력 인터페이스입니다.
- 루트/child module 어디에나 선언할 수 있고, 실제 값은 *. tfvars, CLI -var, 환경 변수(TF_VAR_...), 상위 module의 module "..." { some_var =... }에서 주입됩니다.
- output (output values)
- output "name" { value =... } 블록으로 선언하며, **모듈 실행 결과 중 “밖으로 보여주고 싶은 값”**을 정의합니다.
- root module의 output:
- terraform apply 후 CLI에 출력되고, remote state를 통해 다른 구성에서 읽어갈 수 있습니다.
- child module의 output:
- 상위 모듈에서 module.vpn_onprem_a.vpn_connection_id 같은 형태로 참조합니다.
Terraform을 사용하기 위해 작성할 각 파일들의 역할에 대해 이해했다면 간략하게 작동 원리도 한번 짚고 넘어가 보도록 하겠습니다.
- Local 코드
- 현재 개발자가 작성/수정하고 있는 코드(. tf의 확장자 등을 가진 모든 파일이며 git으로 관리되는)
- AWS 실제 인프라 :
- 실제로 AWS에 배포되어 운영되고 있는 인프라
- Backend에 저장된 상태
- 가장 최근에 배포한 테라폼 코드 형상(terraform apply를 적용했던 결과의 스냅샷. tfstate 확장자의 파일)
Terraform의 apply destroy plan 등 대부분의 명령어는 Local코드에서 개발자가 선언된 상태를 반영하기 위해 Backend에
저장된 상태와 실제 AWS의 인프라를 매번 비교하며 기능을 수행하고 다시 Backend에 저장하는 것이라고 할 수 있겠습니다.
콘솔의 문제현황을 Terraform으로 진행했다면 어떻게 달라졌을까?
“그때 상황을 Terraform 기준으로 다시 설계하면 어떻게 했을지”를 정리하며 분량상 실제 코드는 담지 않고 구조만 설명드리겠습니다.
각 구조가 코드화로 된다면 어떻게 될지 궁금하다면 하단을 참고 부탁드리며 실무에서의 코드는 새로운 글로 공유드리겠습니다.

- 모듈 구조 상상해 보기
- network 모듈
- VPC, 서브넷, Route Table, Virtual Private Gateway, 기본 Route 정의.
- output:
- vpc_id
- public_route_table_id / private_route_table_id
- vpn_gateway_id
- vpn_onprem_a 모듈
- 첫 번째 온프렘 네트워크용 Customer Gateway, VPN Connection, 정적 Route를 정의.
- variables 예:
- onprem_cidr = "192.168.0.0/24"
- onprem_router_ip = "X.X.X.X"
- output 예:
- vpn_connection_id
- customer_gateway_id
- vpn_onprem_b 모듈
- 두 번째 온프렘 네트워크(192.168.100.0/24)에 대한 Customer Gateway, VPN Connection, 정적 Route를 정의.
- variables 예:
- onprem_cidr = "192.168.100.0/24"
- onprem_router_ip = "Y.Y.Y.Y"
- network 모듈
변경/디버깅 시나리오
VPN 문제가 생기면, 먼저 Terraform 코드에서 최근 커밋들의 변경 이력을 본다.
- CIDR가 /24에서 /16으로 바뀐 흔적이 있는지,
- 새로운 Route가 추가/삭제되었는지,
- Virtual Gateway나 Customer Gateway ID가 바뀌었는지 확인한다.
코드와 state 기준으로 “AWS 쪽 인프라는 이전과 동일하다”라고 판단되면, 초반부터 온프렘 방화벽/라우터 쪽을 범위를 좁힐 수 있습니다.
-
- 기존 구성을 전부 지웠다가 다시 만들 필요가 있어도, 콘솔에서 클릭으로 재구성하는 대신 terraform apply로 같은 구성을 다시 띄울 수 있습니다.
기대 효과
- 동일한 Site-to-Site VPN 구성을 다른 리전에 복제할 때, 모듈 호출만 복사해서 region, CIDR, 라우터 IP 같은 변수 값만 변경하면 됩니다.
- “처음에 5일 걸렸던 작업”을 나중에는 “새 리전에 복제 + 값 몇 개 조정” 수준으로 줄일 수 있습니다.
- 문제가 재발했을 때, “이번에는 어떤 CIDR/어떤 온프렘을 바꾸다가 터졌는지”를 선언 코드의 변경 이력으로 바로 확인할 수 있습니다.
정리/회고
지금까지 Terraform에 대한 개요와 Cloud 환경에서 Terraform을 사용하지 않으면 안 되는 이유를 제 경험을 통해 살펴봤습니다.
Terraform을 사용했다면 무엇이 달라졌을 수 있는지와, 제 관점에서 Terraform을 왜 그리고 어떻게 사용해야 하는지에 대한 기준을
정리하여 설명드리겠습니다.
콘솔로 만든 인프라는 시간이 지나면 복구, 유지보수, 형상관리, 설계 의도가 모두 애매해집니다.
리소스가 단순해 보여도, 운영에 쓰인다면 예외 없이 코드로 관리해야 합니다.
그러니 cloud의 인프라 리소스는 단순해 보여도 예외 없이 콘솔이 아니라 코드로 관리해야 한다라는 것입니다.
Terraform을 이해하는데 도움이 될 수 있다면 Terraform을 사용하는데 도움이 될 수 있는 기준을 고민 끝에 다음과 같이 정리했습니다.
실무에서 Terraform 없이 생겼던 어려웠던 과정들을 겪고 terraform을 사용하며 생긴 기준을 세 가지로 정리했습니다.
새로 만드는 인프라
- VPC, VPN, Route Table, ALB, Security Group 등 cloud의 모든 리소스는 처음부터 Terraform으로만 관리합니다.
- 콘솔은 상태 확인·디버깅용으로만 사용하고 리소스를 생성하고 유지보수 하는 것엔 절대 이용하지 않습니다.
이미 콘솔로 만들어진 인프라
- 콘솔로 만들었지만 실무에서 계속 운영해 왔고 앞으로도 운영할 리소스라면, 가능할 때마다 terraform import로 코드화합니다.
- 리소스를 생성, 수정하는 것은 실제 운영에선 리스크가 크기 때문에 import후 plan으로 상세하게 비교하고 실제 운영에서 apply를 진행해 보기 전 다른 region등에서 테스트를 해보고 진행하여 콘솔의 리소스를 위험도를 없애 코드화로 진행합니다.
설계 의도와 제약
- 콘솔과 terraform으로 리소스를 생성했을 때 해당 리소스가 어떤 프로젝트, 어떤 환경인지 네이밍을 보고 알 수 있게 규칙을 정하고 마찬가지로 태깅을 관리해 리소스들을 분류하고 비용을 관리할 수 있게 정하는 컨벤션을 만들어 일관성을 지키는 것이 중요합니다.
- terraform에서 코드를 생성할 때에도 resource 등의 블록에서 vpc, iam 등의 별칭을 만드는 부분 역시 각 리소스 간의 연계성 리소스의 역할, 나타내는 의미 등을 간략히 나타내 사용하고 remote_state등에서도 쉽게 참조가 가능하도록 구조화하는 것이 중요합니다.
- . tf코드에서 inline으로 정의하지 않고 새로운 블록으로 정의해 각 블록 간 참조로 연결하는 것을 습관화하는 것이 중요할 것 같습니다.
참고 자료 -
- https://developer.hashicorp.com/terraform/language
- https://developer.hashicorp.com/terraform/language/providers
- https://developer.hashicorp.com/terraform/language/state/purpose
- https://developer.hashicorp.com/terraform/language/state
- https://developer.hashicorp.com/terraform/language/backend
- https://developer.hashicorp.com/terraform/language/modules
- https://developer.hashicorp.com/terraform/language/values/variables
- https://developer.hashicorp.com/terraform/language/values/outputs
'Devops' 카테고리의 다른 글
| GitOps – Argo CD 구축 가이드(Helm) (0) | 2026.01.03 |
|---|---|
| Argocd - Application CR 이해하기 (0) | 2026.01.03 |
| Argocd - GitOps 활용하기(App of Apps 패턴) (0) | 2025.11.05 |
| GitOps란? 2부: 정의와 원칙 (5) | 2025.08.15 |
| GitOps란? 1부: 왜 GitOps인가 (4) | 2025.08.12 |