[인프라를 소프트웨어처럼 4/5] plan은 동작을 모른다: 인프라를 테스트·재현한다는 것

환경을 브랜치로 찍어내려면, 그 아래가 단단해야 한다
지난 글에서 우리는 환경을 브랜치로 태어나게 했습니다. dev-variant/{이름} 브랜치를 push하면 격리된 환경이 통째로 생깁니다. 멋진 그림이지만, 한 가지 전제가 있습니다. 환경을 브랜치 찍듯 가볍게 만들고 버리려면, 그 환경을 떠받치는 인프라 자체가 테스트 가능하고 재현 가능해야 합니다. 그렇지 않으면 우리는 "쉽게 찍어내지만 매번 다르게 깨지는" 환경을 양산하게 됩니다.
그래서 이번 글은 한 층 아래로 내려갑니다. 환경을 만드는 인프라 코드를, 우리가 어떻게 apply하기 전에 믿게 되었는가에 관한 이야기입니다.
plan과 apply 사이의 낭떠러지
인프라 변경의 가장 불안한 순간은 plan이 초록불인데 apply가 빨간불일 때입니다. diff는 깨끗했는데, 막상 적용하니 동작하지 않습니다. 코드 세계라면 진작 단위 테스트가 잡았을 일이, 인프라 세계에서는 운영에 올리고 나서야 드러납니다.
이유는 단순합니다. plan은 "선언이 이렇게 바뀐다"를 비교할 뿐, "그래서 이 환경이 살아 움직인다"를 실행해 보지 않기 때문입니다. plan은 변경의 모양은 알지만 변경의 동작은 모릅니다. 우리는 이 낭떠러지를 테스트 가능성과 재현 가능성 두 가지로 건넜습니다.
plan만 보면 동작 실패가 운영에서야 드러납니다. 노트북의 kind·vcluster에 실제로 띄워 동작을 확인한 뒤 클라우드로 보내면, 낭떠러지가 검증 가능한 한 걸음으로 바뀝니다.
backend 연재가 "spec=Port"라고 했을 때
backend 연재의 「IaC에도 헥사고날이 관통한다」는 인프라에도 Port와 Adapter가 있다고 했습니다. 변경의 명세(spec)가 Port고, 클라우드 구현이 Adapter입니다. 아름다운 설계입니다. 그런데 운영팀에게는 그다음 질문이 남습니다 — 그 설계가 맞는지를, apply하기 전에 어떻게 확인하죠?
설계가 헥사고날이라는 건 곧 "구현을 갈아 끼울 수 있다"는 뜻입니다. 그렇다면 진짜 클라우드 대신 로컬에서 띄운 가벼운 클러스터를 Adapter 자리에 끼워 보면 됩니다. 설계의 미덕(교체 가능성)을 그대로 검증의 도구로 바꾸는 것입니다. 우리는 새 인프라를 Pulumi와 Kotlin으로 작성하면서, 이 spec=Port를 진짜 Kotlin 인터페이스로 못 박았습니다.
// spec = Port — "무엇을 원하는가"만 선언한다
interface VirtualNetwork {
val cidr: String
val subnets: List<Subnet>
fun provision(): NetworkId // 구현은 Adapter가
}
// aws Adapter / ncp Adapter 가 같은 Port를 각자 구현
class AwsVpc(...) : VirtualNetwork { ... }인프라를 진짜 프로그래밍 언어로 다루면 컴파일러가 타입을 잡아 주고, 익숙한 테스트 프레임워크를 그대로 쓸 수 있고, 무엇보다 "선언"을 "실행"으로 옮기는 거리가 짧아집니다. backend 연재가 말한 "컴파일 타임 검증의 보상"을, 운영 검증의 영역까지 끌고 온 셈입니다.
Pulumi·Kotlin으로 작성한 헥사고날 IaC. spec이 Port가 되고, aws·ncp 클라우드 구현이 Adapter가 됩니다. 같은 Port에 로컬 Adapter(kind/vcluster)를 끼우면 곧 검증 장치가 됩니다.
apply 전에, 일단 띄워 본다
그래서 우리는 인프라 변경을 kind와 vcluster 위에서 먼저 띄워 봅니다. 노트북 안의 가벼운 쿠버네티스에 실제로 적용해, 리소스가 뜨고, 의존성이 풀리고, 선언한 대로 실제 상태가 맞춰지는지를 눈으로 확인한 뒤에야 진짜 클라우드로 보냅니다. 새 환경으로 떠나기 전에 핵심 컴포넌트들이 로컬 클러스터 위에서 실제로 기동하는지를 먼저 확인한 것도 이 흐름의 일부였습니다. plan이 아니라 실행을 보는 것입니다.
이것이 헥사고날 IaC가 주는 실용적 보상입니다. PoC 단계에서 우리가 던진 문장은 이거였습니다 — "plan은 바뀌는지는 보여주지만 동작하는지는 모른다." 로컬 Adapter는 바로 그 빈칸, "동작하는지"를 apply 전에 채워 줍니다.
같은 선언, 두 개의 클라우드
두 번째 다리는 재현 가능성입니다. 그리고 그 시험대는 꽤 가혹했습니다. flex의 스택을 AWS 바깥, 다른 클라우드의 그린필드 위에 처음부터 다시 세우는 일이었기 때문입니다.
기존 AWS 환경은 여러 계정에 걸쳐 수십 개 모듈, 수많은 Terraform 파일로 관리됩니다. 사실 정확한 숫자는 중요하지 않습니다. 핵심은 모든 환경과 인프라 선언이 예외 없이 전부 Terraform으로 되어 있다는 점입니다. 그래서 이걸 통째로 Pulumi로 옮기는 건 답이 아닙니다. 잘 도는 기존 Terraform은 그대로 두고, 그린필드부터 Pulumi로 시작했습니다. 대신 우리는 스택을 network·cluster·identity·storage·bootstrap이라는 같은 구조의 다섯 모듈로 선언하고, 그 선언을 새 클라우드의 빈 땅에서 다시 세웠습니다. 클라우드가 달라도 Cross-Cloud Identity Federation으로 신원의 경계를 같은 방식으로 넘습니다. 같은 사고방식의 선언이 다른 클라우드에서 같은 모양의 환경을 만들어내는 것 — 이것이 재현 가능성입니다.
잘 도는 기존 AWS의 Terraform은 그대로 둡니다. 같은 다섯 모듈 구조(network·cluster·identity·storage·bootstrap)를 다른 클라우드의 그린필드에 Pulumi로 재현해, 클라우드가 달라도 같은 모양의 환경이 서게 했습니다.
이 글의 무대는 클라우드입니다. backend 연재가 product 모듈을 불변으로 두고 Cloud Adapter를 갈아 끼웠다면, 우리는 그 Adapter 자리에 로컬 검증 환경까지 끼워 넣어 검증의 접점을 하나 더 만들었습니다. 같은 스택 구조를 두고 aws Adapter, ncp Adapter, 그리고 로컬 Adapter를 갈아 끼우는 셈입니다. 그리고 같은 그림을 환경을 만드는 일로 확장한 것이 3화의 Environment Variant입니다.
용어 — 이 글의 variant는 그 variant가 아닙니다
backend 연재 「Variant와 스냅샷 캐시」의 variant는 테스트 컨테이너 계열을 식별하는 값 객체(테스트 축)였습니다. 이 연재의 Environment Variant는 브랜치로 만드는 격리된 실행 환경(공간 축의 운영 확장)입니다. 이름은 같고 축은 다릅니다.
pulumi up이 끝이 아니다
흥미로운 건 검증을 한 번 더 한다는 점입니다. pulumi up이 성공했다고 해서 환경이 의도대로 도는 건 아닙니다. 인프라는 떴지만 그 위의 애플리케이션이 아직 다 뜨지 않았을 수 있습니다. 그래서 우리는 pulumi up 다음에 ArgoCD가 선언대로 클러스터를 다 맞춰 놓을 때까지(모든 앱이 Synced·Healthy가 될 때까지)를 한 스크립트로 확인합니다. 선언한 상태와 실제 클러스터가 일치하는지까지 봐야 비로소 "동작한다"고 말합니다.
"선언했다"와 "동작한다" 사이에 검증을 한 겹 더 끼우는 것 — 인프라를 소프트웨어처럼 다룬다는 말의 가장 구체적인 모습입니다. 빌드가 끝났다고 배포가 끝난 게 아니듯, apply가 끝났다고 환경이 끝난 게 아닙니다.
pulumi up은 시작일 뿐입니다. ArgoCD가 모든 앱을 Synced·Healthy로 맞춰 놓을 때까지를 한 스크립트가 폴링하며, "선언했다"가 "동작한다"가 될 때까지 검증 루프를 돕니다.
테스트는 안전을, 재현은 속도를 만든다
두 다리는 서로 다른 것을 줍니다. 테스트 가능성은 변경을 안전하게 만듭니다 — apply 전에 로컬에서 깨져 주니, 운영에서 깨질 일이 줄어듭니다. 재현 가능성은 변경을 빠르게 만듭니다 — 같은 선언이 어디서든 같은 결과를 낸다면, 환경을 두려움 없이 새로 세우고 버릴 수 있습니다.
그리고 이 둘은 지난 글로 곧장 이어집니다. 3화의 Environment Variant가 "브랜치 하나로 환경 전체를 찍어내는" 일을 두렵지 않게 할 수 있었던 건, 바로 그 아래 인프라가 재현 가능했기 때문입니다. 재현되지 않는 인프라 위에서 환경을 양산하면, 그건 환경이 아니라 서로 다른 사고를 양산하는 것입니다. 다섯 축이 "코드가 환경을 모르게" 했다면, 이 두 다리는 그 환경을 믿을 수 있게 만듭니다.
다음 화 ▸ 다섯 축의 운영 총합, 그리고 AI 시대의 플랫폼팀 — 여섯 개의 축이 한 시스템으로 도는 그림과, 사람을 위해 깎아 둔 이 규율이 어떻게 AI 에이전트에게도 그대로 작동하는지.
🚀플렉스팀 채용페이지 바로가기
☕flex Private Talk 신청하기