|
|||
| $ whoami --team ❯ kt cloud Cloud플랫폼팀 유문상 님 |
클라우드 플랫폼을 "어떤 환경이든 동일하게" 배달하기 위해, 우리는 가상화 레이어부터 직접 쌓고 그 위에 모든 인프라를 코드로 올렸습니다. 이 글은 그 주춧돌을 놓는 과정에서 했던 기술 선택과 고민, 그리고 운영하며 다듬은 것들을 공유합니다.
kt cloud PLATFORM는 '유연하고 확장적인' 아키텍처를 지향합니다. 특정 제품이나 제한된 환경에 종속되지 않으려면, 결국 우리가 가상화 레이어부터 직접 세울 수 있어야 합니다.
본 포스트에서는 ControlPlane을 가상화하기 위해 Proxmox VE를 선택한 이유, 그 위의 인프라를 Pulumi로 코드화한 설계, 그리고 그 코드를 운영 환경에서 안전하게 적용하기 위해 만든 CI/CD 승인 파이프라인까지의 여정을 단계별로 풀어 보겠습니다.
1. 배경: 왜 가상화 레이어부터 직접 쌓는가

클라우드를 고객 환경에 올리다 보면, 모든 현장이 다릅니다. 보유한 하드웨어도, 네트워크 정책도, 인프라 성숙도도 제각각입니다. 이런 상황에서 플랫폼이 "이 환경에는 안 됩니다" 라고 말하는 순간 경쟁력은 사라집니다. 우리에게 필요한 답은 "어떤 환경이든 띄울 수 있습니다" 였습니다.
그러려면 가상화가 전제 조건이었습니다. 정리하면 네 가지 이유입니다.
- 환경의 다양성 흡수 — 가용한 물리 서버 위에 ControlPlane·워크로드·DB·모니터링까지 kt cloud PLATFORM 기술 스택 전체가 올라가야 합니다. 가상화 없이는 한 서버에 여러 컴포넌트를 안전하게 공존시킬 수 없습니다.
- 재현성과 이식성 — 환경 A에서 검증한 구성을 환경 B에 똑같이 재현할 수 있어야 합니다. VM 단위의 추상화가 하드웨어 차이를 흡수해 줍니다.
- 격리 — 한 물리 서버에 여러 컴포넌트가 올라가도 VM 경계로 장애·보안 도메인이 분리됩니다.
- 설치형 클라우드의 조건 — 결국 "현재 가진 하드웨어 위에 동일한 스택을 재현해서 배달"하는 것이 핵심이고, 그 출발점이 가상화입니다.
가상화는 kt cloud PLATFORM를 "어떤 환경이든 동일하게 배달"하기 위한 주춧돌 입니다. 주춧돌은 기둥의 하중을 지반에 전달하고 건물의 수평 기준이 되는 돌입니다. 화려하지 않지만, 이게 틀어지면 그 위의 모든 것이 흔들립니다.
2. 왜 Proxmox VE인가
가상화 엔진은 처음부터 검증된 것을 쓰고 싶었습니다. 그래서 기반은 KVM(Kernel-based Virtual Machine) 으로 정했습니다. KVM은 리눅스 커널에 내장된 Type 1(베어메탈) 하이퍼바이저로, 최신 CPU의 가상화 전용 기능을 직접 활용해 네이티브에 가까운 성능을 냅니다. 무엇보다 Google Cloud·OpenStack·Red Hat 등 대형 클라우드가 모두 채택한, 이미 충분히 검증된 엔진입니다.
문제는 "엔진"만으로는 운영이 안 된다는 점입니다. 클러스터 관리, 웹 UI, HA, 스토리지·네트워크 통합이 필요합니다. 여기서 Proxmox VE를 선택했습니다.
Proxmox VE는 Debian Linux 위에 KVM(VM)과 LXC(Container)를 통합한 오픈소스 가상화 관리 플랫폼입니다. 자동차로 비유하면, 이미 검증된 엔진(KVM)에 잘 만든 차체와 대시보드(Proxmox)를 씌운 셈입니다.
- 통합 관리 UI — 단일 웹 UI에서 클러스터·노드·VM/CT·스토리지·네트워크·백업·권한·방화벽까지 모두 관리합니다. 별도 관리 서버(vCenter 같은)가 필요 없습니다.
- API 일관성 — 동일 기능을 REST API + CLI + 웹 UI가 모두 동등하게 제공합니다. 자동화에 친화적입니다. (이 점이 뒤에서 IaC와 맞물립니다.)
- 클러스터링 + HA — 여러 노드를 한 클러스터로 묶고, 노드 장애 시 VM 자동 페일오버, 라이브 마이그레이션으로 무중단 노드 이동이 가능합니다.
- 스토리지 다양성 — ZFS, Ceph 분산 스토리지 내장, NFS, iSCSI, LVM 등 대부분의 백엔드를 지원합니다.
그래서 왜 VMware도 OpenStack도 아닌 Proxmox였나
| 항목 | Proxmox VE | VMware vSphere | OpenStack |
| 라이선스 | AGPL-3.0 (무료) | 상용 (고가) | Apache 2.0 (무료) |
| 구축 난이도 | 낮음 | 중간 | 높음 |
| 클러스터 관리 UI | 통합 웹 UI | vCenter (별도) | Horizon (별도) |
| HA 지원 | 기본 내장 | 기본 내장 | 별도 구성 필요 |
| 운영 인력 요구 | 낮음 | 중간 | 높음 |
선택의 근거는 두 가지였습니다.
첫째, 운영 복잡도 대비 기능 완결성. OpenStack은 기능적으로 가장 풍부하지만 서비스 컴포넌트 수가 많아, 초대형 VM 환경이 아니라면 운영 부담이 큽니다. Proxmox VE는 단일 패키지 설치로 하이퍼바이저·클러스터링·HA·스토리지 관리를 모두 제공해, 소수 인원으로도 충분히 운영할 수 있는 수준이었습니다.
둘째, 비용 효율성. 엔터프라이즈 구독 없이 커뮤니티 저장소로 모든 기능을 쓸 수 있습니다. 상용 솔루션 대비 라이선스 비용을 크게 절감하고, 그 예산을 하드웨어 스펙 향상에 투자할 수 있었습니다.
3. 인프라도 코드로: 왜 IaC이고, 왜 Pulumi인가
Proxmox VE는 가상화 플랫폼을 잘 제공해 줍니다. 하지만 그 위에 만들어지는 인프라 자체를 추적·관리하지 못하면 가상화의 장점은 절반만 살아납니다.
웹 UI와 CLI만으로 운영할 때의 한계는 분명했습니다.
- 변경의 의도가 안 남습니다. audit log는 "누가 클릭했는지"만 남기지, 왜 그렇게 했는지는 사라집니다.
- 환경 간 동일성을 사람이 보장해야 합니다. dev 구성을 prod에 똑같이 재현하려면 기억이나 문서에 의존하게 되고, 사람이 끼면 drift가 생깁니다.
- 신규 환경마다 같은 클릭을 처음부터 반복합니다. 시간도 실수도 늘어납니다.
- 단일 진실의 원천(Single Source of Truth)이 없습니다. "지금 이 환경의 정확한 상태"를 알려면 UI를 들여다봐야 합니다.
답은 인프라도 코드로 관리(IaC) 하는 것이었습니다. 애플리케이션 코드에서 이미 하고 있는 협업 모델 — Git, PR 리뷰, CI/CD, 동일 정의로 다중 환경 — 을 인프라에 그대로 가져오는 것입니다.
왜 Terraform이 아니라 Pulumi였나
IaC 도구로는 Pulumi를 택했습니다. 핵심 이유는 "진짜 프로그래밍 언어로 인프라를 짤 수 있다" 는 점입니다.
| 구분 | Terraform | Pulumi |
| 언어 | HCL (전용 DSL) | Python, TypeScript, Go 등 실제 언어 |
| 타입 체크 | 제한적 | 언어 네이티브 (예: Python type hints) |
| 테스트 | 별도 도구 필요 | pytest 등 기존 프레임워크 그대로 |
| 코드 재사용 | Module 문법 | 함수/클래스 그대로 |
| 루프·조건문 | 제한적 문법 | 네이티브 for, if |
팀이 이미 Python에 익숙했기에 학습 비용이 낮았고, pytest로 진짜 인프라 없이 단위 테스트를 작성할 수 있었으며, 모든 인프라 변경이 Python 파일 커밋으로 표현되어 PR 리뷰 → 머지 → 배포 흐름이 자연스러웠습니다.
이 글의 설계적 하이라이트: 어댑터 패턴으로 짠 3-Layer
Pulumi의 진짜 가치는, 인프라 코드를 애플리케이션 코드처럼 설계할 수 있다는 데 있었습니다. 우리는 VM 정의를 세 계층으로 나눴습니다.
- Layer 1 — 도메인 스펙 정의. 우리 코드가 다룰 VM 스펙(이름·노드·코어·메모리·디스크·IP 등)을 우리만의 언어로 선언합니다.
- Layer 2 — 어댑터(변환). Layer 1의 도메인 스펙을 Proxmox Provider SDK가 이해하는 형태로 변환합니다.
- Layer 3 — 사용. 사용자는 원하는 VM 스펙만 입력합니다. 아래에서 어떤 Provider가 어떻게 도는지 신경 쓸 필요가 없습니다.
# Layer 3 — 사용자는 "무엇을 원하는가"만 선언한다
VmEntry("web-01", node=BM01, cores=8, memory_mb=16384, disk_gb=200, ip="...")
Provider의 외부 인터페이스와 우리 환경이 기대하는 도메인 인터페이스 사이를 Layer 2가 변환해 준다는 점에서, 이는 전형적인 어댑터(Adapter) 디자인 패턴입니다. 해외여행용 멀티 어댑터를 떠올리면 됩니다 — 우리 플러그(도메인 스펙)는 고정해 두고, 각 나라 콘센트(Provider) 규격에 맞춰 끼웁니다. 우리가 추구한 '유연성'에 정확히 들어맞는 구조였습니다.
중요한 건 구체적인 코드가 아니라, GoF의 객체지향 디자인 패턴을 인프라 코드에 그대로 적용할 수 있었다는 사실입니다. HCL 같은 DSL이었다면 디자인 패턴 적용 자체가 어려웠을 것입니다. Python이라서 자연스럽게 가능했습니다. 인프라가 진정으로 '코딩의 영역'으로 들어온 순간이었습니다.
4. 가장 큰 고민: 어떻게 '안전하게' 자동화할 것인가
코드로 인프라를 정의했으니, 이제 그 코드를 실제로 적용하는 자동화가 필요했습니다. 그런데 여기서부터가 진짜 어려운 부분이었습니다. 일반적인 애플리케이션 CI/CD는 "머지하면 자동 배포"가 미덕이지만, 인프라는 잘못된 diff 하나가 운영 VM을 통째로 destroy로 직결시킬 수 있습니다. 자동화의 편리함과 운영의 안전성을 동시에 잡아야 했습니다.
왜 Pulumi Operator를 쓰지 않았나
Pulumi는 공식적으로 Kubernetes Operator를 제공합니다. 자연스러운 후보처럼 보였지만 기각했습니다.
- 순환 의존성 — Kubernetes 자체가 Pulumi로 관리되는 시점이 오면, "k8s를 만들려면 k8s가 먼저 있어야 하는" 모순이 생깁니다.
- 복구 불가 리스크 — k8s가 다운되면 인프라 변경·복구 자체가 불가능해집니다.
- 레이어 역전 — 우리 시나리오에서 Proxmox는 k8s보다 하위 레이어입니다. 상위 레이어(k8s)에 하위 레이어를 관리하는 Operator를 두는 것은 구조적으로 어색합니다.
폐쇄망 + 전용 실행 호스트
Proxmox API는 내부망에만 열려 있어, 외부의 관리형 SaaS나 GitHub 호스팅 runner로는 접근할 수 없는 폐쇄망 환경이었습니다. 그래서 Proxmox와 같은 인프라 레이어에 위치한 전용 VM 하나를 self-hosted runner로 두었습니다. 별도 컨트롤 플레인(k8s/Operator) 의존 없이 Ubuntu + runner + Pulumi CLI 만으로 동작하는, "IaC 실행 전용 호스트 + Git 이벤트 트리거" 모델입니다.
이 단순한 구조에는 분명한 이점이 있었습니다. SSH로 들어가 state·로그·캐시를 직접 확인할 수 있어 문제 추적이 한 단계로 끝나고, 모든 Pulumi 실행이 한 VM에서 직렬화되므로 분산 잠금 시스템 없이도 state lock이 안정적입니다.
"머지 ≠ 적용"이라는 원칙
가장 중요한 결정은, Git 머지와 실제 적용을 분리한 것입니다. 머지는 "이렇게 하고 싶다"는 합의일 뿐, 적용은 사람이 무엇이 생기고·바뀌고·파괴되는지를 눈으로 확인하고 승인한 뒤에만 일어나야 합니다. 이 한 줄의 원칙이 이후 파이프라인 전체 설계를 결정했습니다.
5. 파이프라인 동작 원리
운영 클러스터의 VM은 전부 코드로 관리됩니다. 클러스터별 VM 정의 파일 하나가 그 클러스터 VM 목록의 ground truth 입니다. 파이프라인은 다섯 개의 잡으로 구성됩니다.
| 잡 | 트리거 | 하는 일 |
| test | 모든 push/PR | 전체 테스트 (VM 정의 가드 테스트 포함 — 1차 안전망) |
| detect | 모든 push/PR | 변경 경로를 분석해 영향받은 클러스터만 산출 |
| pr-preview | PR + 영향 클러스터 있음 | 클러스터별 pulumi preview 결과를 PR 코멘트로 |
| plan | main 머지 + 영향 클러스터 있음 | --expect-no-changes로 실제 변경 여부 판정 → 변경분만 추림 |
| up | plan이 변경 감지 시 | 승인 대기 → 사람이 승인해야 pulumi up 실행 |
핵심은 안전 모델 3원칙입니다.
- 영향 클러스터만 — detect가 변경 경로를 기준으로 대상을 좁힙니다. 공용 컴포넌트를 건드리면 전 클러스터, 특정 클러스터 폴더만 건드리면 그 클러스터만 대상이 됩니다.
- 실변경만 — plan의 expect-no-changes 판정으로, 실제로 바뀌는 게 없으면 승인 잡 자체가 뜨지 않습니다. "변경도 없는데 습관적으로 승인"하는 승인 피로를 없앴습니다.
- 사람 승인 후에만 — up은 승인 게이트를 통과한 뒤에만 실행됩니다. 승인 화면은 클러스터별로 분리되어 있어, 체크한 클러스터만 적용되고 나머지는 계속 대기합니다. 재부팅이 필요한 변경이라면 같은 화면에 경고가 함께 표시됩니다.
결과적으로 개발자의 작업 흐름은 이렇게 정리됩니다.
브랜치 → VM 정의 수정 → 로컬 테스트 → PR → preview 코멘트 확인
→ 머지 → plan → (변경 감지 시) 승인 → 적용 → 결과 확인
6. 운영하며 다듬은 디테일
설계가 끝이 아니었습니다. 실제로 굴려 보니, 사람이 헷갈리거나 위험할 수 있는 지점들이 드러났고 거기에 맞춰 다듬었습니다.
VM 한 대 만드는데 왜 +5 to create가 뜨나요?
preview를 처음 본 사람이 가장 많이 놀라는 부분입니다. 실제 "머신"은 1대뿐이고, 나머지 넷은 그 VM을 손으로 만들 때 했어야 할 작업들이 리소스로 명시된 것입니다.
| # | preview의 리소스 | 실체 |
| 1 | VM 리소스 | 실제 VM 1대 (템플릿 full clone) |
| 2~4 | 파일 리소스 × 3 | cloud-init 텍스트 파일 (네트워크 / 계정·호스트명 / 공통 부트 설정) |
| 5 | 컴포넌트 | 실물 없음 — 위 넷을 "VM 한 대" 단위로 묶는 논리 묶음 |
손으로 VM을 만들 때 "클론하고 → IP 잡고 → 계정 만들고 → SSH 여는" 작업이 전부 코드 리소스로 명시된 것입니다. 삭제할 때도 동일하게 5개가 함께 정리되므로, 수동 작업에서 잊기 쉬운 설정 파일 청소까지 코드가 함께 합니다. preview를 읽는 요령은 타입 컬럼입니다 — VM 타입은 신중하게, 파일 타입은 가볍게, 컴포넌트는 실물 없는 묶음으로 읽으면 됩니다.
시크릿은 리포에 커밋돼도 안전하게
VM 계정 비밀번호 같은 민감 값은 Pulumi의 passphrase 기반 secrets provider로 다룹니다. 암호문은 설정 파일에 그대로 커밋되지만, 그것을 풀 수 있는 passphrase는 CI 시크릿으로만 존재합니다.
passphrase(CI 시크릿) + 설정 파일의 salt
→ PBKDF2-SHA256 → AES-256 키 → 암호문 복호
리포만 가져가서는(암호문 + salt) 복호할 수 없고, 리포 + passphrase 둘 다 있어야 풀립니다. 복호된 값도 Pulumi가 secret으로 오염 추적하여 로그·preview에는 마스킹된 형태로만 표시됩니다.
왜 공식 워크플로를 그대로 쓰지 않고 직접 만들었나
오해를 피하자면, 공개 부품은 그대로 씁니다. 외부 의존은 공식 checkout 액션, Pulumi 공식 액션(preview/up 실행기), 커뮤니티 Proxmox provider뿐입니다. 우리가 직접 만든 것은 그 부품들을 운영 환경에서 안전하게 돌리는 오케스트레이션이고, 이건 어떤 도구도 대신 주지 않습니다.
| 공식 표준 패턴의 전제 | 우리 현실 | 그래서 추가한 것 |
| 머지 = 자동 up | diff 하나가 운영 VM destroy 직결 | plan → 사람 승인 게이트 → up |
| 스택 1개 | 여러 클러스터 모노레포 | detect — 변경 경로 → 영향 클러스터만 |
| 변경 유무 무관 up | 무변경 승인 = 승인 피로 | expect-no-changes — 무변경이면 승인 자체가 안 뜸 |
| 관리형 SaaS + 호스팅 runner | 폐쇄망 | self-hosted runner + self-managed backend |
한 줄로 요약하면, "공식 액션은 엔진까지만 — 운영 안전모델(브레이크와 게이트)은 환경마다 직접 설계해야 하는 부분" 이라는 것입니다.
7. 마치며, 그리고 로드맵
가상화 엔진 선택(Proxmox/KVM)에서 출발해, 인프라를 코드로 정의하고(Pulumi 어댑터 패턴), 그 코드를 운영 환경에 안전하게 흘려보내는 파이프라인(승인 게이트)까지 — ControlPlane 가상화의 한 사이클을 만들었습니다.
핵심 교훈을 세 줄로 남깁니다.
- 가상화는 "어떤 환경이든 동일하게 배달"하기 위한 주춧돌이다. 검증된 엔진(KVM) 위에 운영 편의(Proxmox)를 얹는 조합이 소수 인원 운영에 최적이었습니다.
- 인프라를 진짜 언어로 짜면 코드가 된다. Python 덕분에 어댑터 패턴 같은 객체지향 설계를 인프라에 그대로 적용할 수 있었습니다.
- 인프라 자동화의 본질은 속도가 아니라 안전이다. "머지 ≠ 적용", 영향 범위 최소화, 실변경만 승인 — 이 브레이크들이 자동화를 믿고 쓸 수 있게 만들었습니다.
ControlPlane을 떠받치는 이 주춧돌 위에서, 앞으로 고도화될 kt cloud PLATFORM의 모습에도 많은 관심 부탁드립니다.
?› 자주 묻는 질문 FAQ
'Tech Story > DevOps & Container' 카테고리의 다른 글
| [사례연구] 사내 개인용 개발환경 이미지 실험기 1부: git push로 업데이트되는 OpenStack 샌드박스 만들기 (1) | 2026.06.23 |
|---|---|
| [기술분석] Kubernetes Gateway API에서 트래픽을 세밀하게 제어하는 Policy 객체 파헤치기 (0) | 2026.06.05 |
| [전환가이드] ArgoCD·FluxCD GitOps 배포를 HelmRelease로 전환하는 방법 (0) | 2026.06.05 |
| [운영가이드] Kubernetes 기반 Fault-Tolerant GPU 클러스터 유지 관리 (0) | 2026.06.04 |
| [기술 분석] kubernetes Ingress API의 중단. 그 뒤를 잇는 Gateway API 파헤치기 (0) | 2026.05.22 |