DevYGwan
Terraform vs Pulumi — 멀티 클라우드(AWS + NCP) 소규모 팀의 IaC 도구 선정기 본문
저희는 관리형 K8s(EKS/NKS) 없이 인스턴스 기반 K8s 노드 3개를 직접 운영하고 있습니다. 관리형 k8s을 사용하지 않는 이유는
- 관리형 서비스의 업데이트 주기에 맞춰 저희도 관리해야한다.
- AWS 뿐만아니라 NCP, Azure 등 여러 cloud 서비스에서 운영해야 되는데 그때마다 관리형 서비스를 각각 개별적으로 관리해야한다. (현재는 AWS, NCP만 사용)
- Onprem 서비스로 직접 k8s 노드 3개로 직접 서비스를 운영해야한다.
등이 있습니다. 물론 비용적인 측면에서도 관리형 서비스를 사용하는 것보다 직접 운영하는게 더 저렴하기 때문에도 있습니다. 따라서 이렇게 여러 환경에서 직접 K8s 노드로 운영하다보니 동일한 구성을 AWS와 NCP(네이버 클라우드) 등 여러 클라우드 서비스에서| 반복해야 한다는 것. 콘솔 클릭으로 두 번씩 만들다 보면 환경 간 드리프트는 시간 문제라, IaC 도입을 결정하기로 결정했습니다.
Iac에는 여러 종류가 있지만 크게 Terraform과 Pulumi를 비교했습니다. 결론부터 말하자면, Terraform(OpenTofu) 을 선택했습니다. 흥미로운 건 "Pulumi가 기술적으로 더 못해서"가 아니라, 우리 제약 조건 세 가지가 전부 Terraform 쪽을 가리켰기 때문이라는 점입니다. 같은 비교라도 제약이 다르면 결론은 뒤집힐 수 있습니다.
IaC(Infrastructure as Code)
- 서버·네트워크·로드밸런서 같은 인프라를 코드로 선언하고, 도구가 그 선언대로 실제 인프라를 만들어 주는 방식
- 인프라 구성이 코드가 되는 순간 버전 관리·코드 리뷰·재현(같은 코드 → 같은 환경)이 가능해짐
1. 비교 전에 제약 조건부터 — 판단 기준을 고정하기
두개의 기술에 대한 비교 글은 많았지만, 대부분 일반론이라 제 상황에 대입하기는 어려웠습니다.. 그래서 비교 전에 우리 환경의 제약을 먼저 고정했습니다.
| 제약 | 내용 |
| 클라우드 | AWS + NCP 등 멀티 클라우드 |
| 팀 | 소규모 (인프라 전담 인력 없음) |
| 환경 | Private — 외부 SaaS 의존 최소화 |
| 대상 | 인스턴스 기반 K8s 노드 3개 + NLB (관리형 K8s 미사용) |
핵심 워크플로우는 단순합니다. 인스턴스 3개 생성 → NLB 생성 → Target Group 연결 → (필요 시) Security Group/Subnet 구성. 이걸 두 클라우드에서 반복한다.
2. Terraform vs Pulmi
Terraform과 Pulumi 는 대표적인 Iac 툴 중 하나입니다. 간단하게 이것에 대해서 설명드리자면 이 2개 모두 선언적 모델과 State를 기반으로 동작합니다. Terraform과 Pulumi는 둘 다 "인프라가 어떤 상태여야 하는가(desired state)"를 정의하면, 엔진이 현재 상태와의 차이(diff)를 계산해 필요한 변경만 실행하는 선언형 모델입니다.. 이를 위해 두 도구 모두:
- State 파일: 도구가 관리 중인 실제 인프라의 스냅샷. "코드 ↔ 실제 인프라"를 대조하는 기준점이라 유실되면 안 되고, 팀이 공유하려면 원격 저장소(예: S3)와 동시 실행 잠금(locking)이 필요하다.
- 미리보기 → 적용 워크플로우: Terraform은 terraform plan → apply, Pulumi는 pulumi preview → up. 무엇이 생성/변경/삭제될지 먼저 보여주고 실행한다.
- Provider 플러그인: 각 클라우드 API를 감싸는 어댑터. 도구 본체가 아니라 Provider가 "AWS를 아는가, NCP를 아는가"를 결정한다 — 그래서 이 글의 비교에서 Provider 지원 품질이 핵심 축이 된다.
- Terraform
- HashiCorp가 2014년 공개한 IaC의 사실상 표준.
- 인프라를 HCL(HashiCorp Configuration Language) 이라는 전용 선언형 DSL로 기술
- "무엇을 원하는지"를 설정 파일처럼 적는 방식이라 배우기 쉽고 읽기 쉬운 대신, 프로그래밍 언어가 아니라서 복잡한 로직 표현엔 한계가 있다.
- 2023년 라이선스가 BSL로 전환되면서 커뮤니티가 오픈소스 포크인 OpenTofu를 만들었고, 기존 Terraform 코드와 호환된다.
- Pulumi
- 2018년 등장한 후발 주자로, 핵심 아이디어는 "인프라를 전용 DSL이 아니라 TypeScript/Python/Go/C# 같은 범용 프로그래밍 언어로 정의하자" 는 것.
- 반복문·조건문·함수·타입·테스트 프레임워크 등 언어 생태계를 그대로 인프라 코드에 쓸 수 있다. (코드가 명령형처럼 보여도 실행 모델은 Terraform과 같은 선언형이다.)
- 프로그램을 실행하면 리소스 그래프(desired state)가 만들어지고, 엔진이 diff를 계산해 적용한다. forEach로 인스턴스를 만들었다고 매번 새로 생성되는 게 아니라, "인스턴스 3개가 있어야 한다"는 선언이 되는 것이다.
- Provider 생태계는 자체 네이티브 Provider와 함께, Terraform Provider를 감싸서 쓰는 브릿지 방식으로 커버리지를 확보한다 — 뒤에서 보겠지만 이 구조가 NCP 같은 마이너 클라우드에서 양날의 검이 된다.
두 기술 모두 실행 모델(선언형 + State)은 같고, 본질적 차이는 "인프라를 무슨 언어로 정의하는가"(전용 DSL vs 범용 언어)와 그에 따른 생태계 구조입니다.
그렇다면, 간단히 제가 이 2개의 모델 중 Terraform을 선택한 이유에 대해서 설명드리도록 하겠습니다.
3. Terraform — "공식 Provider"와 "State 자체 통제"의 힘
※ NCP 공식 Provider가 존재합니다.
AWS Provider야 가장 성숙한 Provider니 논외고, 결정적인 건 NCP입니다. Terraform 공식 Provider Registry에는 각 클라우드/서비스 벤더의 공식 Provider가 등록돼 관리되는데, NCP의 navercloudplatform Provider도 여기에 공식 제공합니다. 문서·예제가 Terraform 기준으로 작성돼 있으며, NCP + Terraform 조합의 한국어 트러블슈팅 자료까지 존재합니다. 멀티 클라우드 IaC에서는 "마이너한 쪽 클라우드의 지원 품질"이 실질적 병목이 되기 때문에 공식적으로 제공하고 자료도 많은 장점이 있습니다.
※ State를 완전히 자체 통제할 수 있다.
Private 환경이라 State를 외부 SaaS에 둘 수 없는데, S3 Backend로 내부에서 처리가 가능합니다. 전통적으로는 동시 실행 잠금(locking)을 위해 S3 + DynamoDB 조합이 필수였지만 현재는 deprecated 예정이고, Terraform 1.10부터는 S3의 Conditional Writes를 활용한 네이티브 locking이 도입돼 DynamoDB 없이 S3만으로 잠금까지 해결됩니다. use_lockfile = true 한 줄이면 처리가 완료돼 쉽게 환경 설정이 가능하다는 장점이 있습니다.
terraform {
backend "s3" {
bucket = "my-tfstate"
key = "prod/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
use_lockfile = true # Terraform 1.10+ — DynamoDB 불필요
}
}
※ 소규모 팀에는 HCL의 단순함이 오히려 장점이다.
선언적 DSL이라 학습 곡선이 낮고, 인프라 전담이 아닌 팀원도 코드 리뷰가 가능한 수준의 가독성이 나옵니다. 별도 런타임(Node.js/Python) 설치도 불필요하다. 공통 패턴(K8s 노드 3개 + NLB)은 클라우드별 모듈로 분리해 재사용 할 수 있습니다.
infra/
├── aws/ # aws-k8s-cluster 모듈 호출
├── ncp/ # ncp-k8s-cluster 모듈 호출
└── modules/
├── aws-k8s-cluster/
└── ncp-k8s-cluster/
물론 약점도 명확합니다. 복잡한 조건 로직에서 HCL은 금방 한계가 오고(count/for_each/dynamic 중첩 시 가독성 급락), State에 민감 정보가 평문으로 저장되며, BSL 라이선스 변경 우려가 있습니다. 하지만 저희의 인프라 구조 상 기본적인 HCL로도 충분히 가능하고 암호화 처리로 인한 평문 저장 문제를 해결할 수 있고 OpenTofu 대체를 통해 라이센스 변경 우려도 해결할 수 있기 때문에 큰 문제가 되진 않았습니다.
3. Pulumi — 좋은 도구지만, 우리 제약과 어긋난 지점
- 범용 언어의 표현력: 노드별 역할(master/worker)·환경별 설정 분기를 TypeScript의 조건문/반복문으로 깔끔하게 처리할 수 있었습니다.
const nodeConfigs = [
{ role: "master", type: "t3.medium" },
{ role: "worker", type: "t3.small" },
{ role: "worker", type: "t3.small" },
];
nodeConfigs.forEach((cfg, i) => {
new aws.ec2.Instance(`node-${i}`, {
instanceType: cfg.type,
tags: { Role: cfg.role },
});
});
- 테스트 용이성: jest/pytest 같은 기존 프레임워크로 인프라 코드를 Mock 기반 유닛 테스트할 수 있다.
- 민감 정보 자동 암호화: pulumi.secret() 선언만으로 State에 암호화 저장 — 보안 기본값은 Terraform보다 낫다.
- 타입 시스템: 잘못된 속성명·타입 불일치를 plan 전에 IDE에서 잡는다.
그러나 우리 제약과 충돌하는 지점이 셋 있었다.
- NCP 지원이 브릿지 Provider — Terraform NCP Provider를 브릿지해 쓰는 구조라 문서가 부족하고 업데이트가 늦을 수 있다. NCP 이슈 발생 시 레퍼런스를 찾기 어렵다. 이게 현재 상황에서 가장 큰 리스크 였습니다.
- State 관리의 트레이드오프 — Pulumi Cloud(SaaS)는 Private 환경 정책과 충돌하고, 셀프 호스팅(S3)은 동시 접근 잠금(locking)을 별도로 풀어야 한다.
- 런타임 의존성과 커뮤니티 — CI/CD까지 Node.js 런타임이 필요하고, NCP + Pulumi 조합 사례는 거의 없다.
4. 비교 요약
| 평가 항목 | Terraform | Pulumi |
| NCP 지원 안정성 | ✅ 공식 Provider | ⚠️ 브릿지 (리스크) |
| AWS 지원 | ✅ 최고 수준 | ✅ 우수 |
| 학습 곡선 | 낮음 (HCL) | 중간 (언어는 쉬우나 IaC 패턴 적응 필요) |
| 복잡한 로직 | 제한적 | 자유로움 |
| State 자체 관리 | ✅ S3 + DynamoDB | ⚠️ SaaS or 셀프 호스팅 (locking 주의) |
| Private 환경 적합성 | ✅ 외부 의존 없음 | ⚠️ Pulumi Cloud 사용 시 외부 통신 |
| 테스트 | 제한적 | 우수 |
| 민감 정보 보안 | ⚠️ 평문 (암호화 Backend 필요) | ✅ 자동 암호화 |
| 커뮤니티/레퍼런스 | ✅ 압도적 (NCP 포함) | ❌ 부족 (특히 NCP) |
| 소규모 팀 운영 부담 | 낮음 | 중간 |
5. 결론 — 그리고 결론이 뒤집힐 조건
Terraform(OpenTofu)을 했습니다. 근거는 크게 네 가지로,
- NCP 공식 Provider(멀티 클라우드 양쪽 안정 지원은 Terraform뿐)
- Private 환경에서 State 완전 자체 관리
- 소규모 팀엔 HCL의 단순함이 장점
- NCP 조합의 한국어 레퍼런스 존재.
중요한 건 이 결론이 조건부라는 점이다. 다음 조건이 바뀌면 Pulumi를 재검토 할 수도 있습니다.
- 팀에 개발자가 늘고 인프라 로직이 복잡해질 때 (범용 언어의 표현력·테스트가 이득으로 전환)
- NCP 비중이 줄고 AWS 중심으로 재편될 때 (브릿지 Provider 리스크 소멸)
- Pulumi의 NCP Provider가 성숙해졌을 때