- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며 — 클러스터가 하나였던 시대는 끝났다
- 기존 방식은 왜 한계에 부딪히는가
- Cluster API의 핵심 철학 — 쿠버네티스로 쿠버네티스를 관리한다
- 아키텍처 해부 — management cluster와 workload cluster
- 프로바이더 모델 — 추상화의 실체
- 실습 — CAPD로 노트북에서 클러스터 공장 돌려보기
- 운영 심화 — 업그레이드, 스케일링, 자동 복구
- GitOps 결합 — 클러스터 플릿을 Git으로 다스리기
- pivot, 백업/DR, 버전 스큐 — 관리 클러스터 자체의 운영
- 한계와 현실 — 도입 전에 알아야 할 것들
- 트러블슈팅 — 머신이 멈췄을 때 보는 순서
- 도입 체크리스트
- 마치며
- 참고 자료
들어가며 — 클러스터가 하나였던 시대는 끝났다
쿠버네티스 도입 초기에는 조직당 클러스터가 하나, 많아야 두세 개였습니다. 그런데 지금은 상황이 완전히 다릅니다. 환경별 분리(dev/stage/prod), 리전별 분리, 규제 도메인별 분리(망분리 환경의 금융권이라면 더더욱), 테넌트별 전용 클러스터, 그리고 매장·공장·차량에 들어가는 엣지 클러스터까지. 수십 개는 기본이고, 통신사나 리테일 엣지를 운영하는 조직은 수백에서 수천 개의 클러스터를 굴립니다.
클러스터가 10개를 넘어가는 순간, 다음 질문들이 일상이 됩니다.
- 새 클러스터 하나 만드는 데 며칠이 걸리는가? 그 절차는 문서화되어 있는가, 아니면 특정 엔지니어의 머릿속에만 있는가?
- 클러스터 37번의 쿠버네티스 버전은 몇인가? 전체 클러스터의 버전 분포를 한 화면에서 볼 수 있는가?
- 컨트롤플레인 노드가 죽으면 누가, 언제, 어떻게 복구하는가?
- 전체 클러스터를 v1.31에서 v1.32로 올리는 데 몇 주가 걸리는가?
이 질문들에 자신 있게 답하지 못한다면, 클러스터 라이프사이클 관리가 조직의 병목이라는 뜻입니다. 이 글에서 다루는 Cluster API(줄여서 CAPI)는 이 문제에 대한 쿠버네티스 커뮤니티의 공식 답안입니다. 핵심 아이디어는 단순하면서 급진적입니다. 클러스터 자체를 쿠버네티스 리소스로 선언하고, 컨트롤러가 그 선언을 현실로 만들게 한다.
기존 방식은 왜 한계에 부딪히는가
콘솔 클릭(ClickOps)
클라우드 콘솔에서 버튼을 눌러 클러스터를 만드는 방식은 한 개를 만들 때는 가장 빠릅니다. 그러나 재현이 불가능합니다. 3개월 뒤 같은 구성의 클러스터를 만들려면 스크린샷과 기억에 의존해야 하고, 감사(audit) 추적도 어렵습니다. 클러스터 간 설정 드리프트는 필연이며, "이 클러스터만 왜 다르지?"의 원인을 아무도 모르게 됩니다.
Terraform / OpenTofu
코드형 인프라(IaC)로서 Terraform은 훌륭한 도구지만, 클러스터 라이프사이클에는 구조적 약점이 있습니다.
- 실행 시점에만 동작합니다. terraform apply가 끝나면 상태 감시도 끝납니다. 누가 콘솔에서 노드그룹을 지워도 다음 apply 전까지는 아무도 모릅니다. 지속적 reconcile이 없습니다.
- 상태 파일이 단일 장애점입니다. state 잠금, 백엔드 관리, state 손상 복구는 그 자체로 운영 부담입니다.
- 노드 수준 라이프사이클 표현이 약합니다. "이 노드가 10분간 NotReady면 교체하라" 같은 선언은 Terraform의 언어로 표현하기 어렵습니다.
- 수백 클러스터 규모가 되면 워크스페이스/모듈 분리, plan 시간, drift 탐지가 모두 고통이 됩니다.
kubeadm 스크립트
kubeadm은 클러스터 부트스트랩의 사실상 표준 저수준 도구지만, 어디까지나 "한 노드를 클러스터에 넣는" 도구입니다. VM 프로비저닝, LB 구성, 인증서 갱신, 노드 교체, 버전 업그레이드 순서 제어는 모두 사용자의 셸 스크립트 몫입니다. 이 스크립트들은 시간이 지나면 아무도 손대지 못하는 비밀 의식(ritual)이 됩니다.
세 방식의 공통 결함은 하나입니다. 선언과 현실을 지속적으로 일치시키는 주체가 없다는 것. 쿠버네티스가 파드에 대해 해결한 바로 그 문제가, 클러스터 자체에 대해서는 미해결로 남아 있던 셈입니다.
Cluster API의 핵심 철학 — 쿠버네티스로 쿠버네티스를 관리한다
Cluster API는 쿠버네티스 SIG Cluster Lifecycle이 만드는 공식 서브프로젝트입니다. 철학은 세 문장으로 요약됩니다.
- 선언적 API. 클러스터, 머신, 컨트롤플레인을 전부 CRD(Custom Resource)로 정의합니다. "워커 3대, 버전 v1.32.2"라고 선언하면 끝입니다.
- 컨트롤러 reconcile 루프. Deployment 컨트롤러가 파드 수를 맞추듯, CAPI 컨트롤러는 머신 수와 버전을 맞춥니다. 현실이 선언에서 벗어나면(노드 장애, 수동 삭제) 자동으로 수렴시킵니다.
- 프로바이더 추상화. AWS든 vSphere든 베어메탈이든, 인프라별 차이는 프로바이더 플러그인 뒤로 숨깁니다. 사용자 경험은 동일한 kubectl과 YAML입니다.
비유하자면 이렇습니다. 파드에게 Deployment가 있다면, 클러스터에게는 Cluster API가 있습니다. ReplicaSet이 파드를 찍어내듯 MachineSet이 머신(노드 VM)을 찍어내고, Deployment가 파드를 롤링 업데이트하듯 MachineDeployment가 노드를 롤링 교체합니다. 쿠버네티스 운영자가 이미 아는 멘탈 모델을 클러스터 차원으로 끌어올린 것입니다.
또 하나 중요한 원칙이 **불변 인프라(immutable infrastructure)**입니다. CAPI는 머신을 "고치지" 않습니다. 설정이나 버전이 바뀌면 새 머신을 만들고 옛 머신을 폐기합니다. 살아 있는 노드에 SSH로 들어가 패키지를 업그레이드하는 일은 모델 바깥의 행위입니다.
아키텍처 해부 — management cluster와 workload cluster
CAPI 세계에는 두 종류의 클러스터가 있습니다.
- Management cluster(관리 클러스터): CAPI 컨트롤러와 CRD가 설치된 클러스터. 다른 클러스터들의 선언(YAML)이 여기 저장되고 reconcile됩니다.
- Workload cluster(워크로드 클러스터): 관리 클러스터가 만들어내는 대상 클러스터. 실제 애플리케이션이 여기서 돕니다. 워크로드 클러스터 자신은 CAPI의 존재를 모릅니다.
+----------------------------------------------------------------------+
| Management Cluster |
| |
| +----------------+ +----------------+ +------------------------+ |
| | CAPI Core | | Bootstrap | | Control Plane | |
| | Controller | | Provider | | Provider | |
| | (Cluster, | | (CABPK: | | (KCP: KubeadmControl- | |
| | Machine, MS, | | KubeadmConfig)| | Plane controller) | |
| | MD, MHC) | +----------------+ +------------------------+ |
| +----------------+ |
| +-------------------------------+ |
| | Infrastructure Provider | etcd에 저장된 선언: |
| | (CAPA/CAPZ/CAPV/CAPD/Metal3) | Cluster, MachineDeployment, |
| +-------------------------------+ KubeadmControlPlane ... |
+----------------------------------------------------------------------+
| | |
| 프로비저닝/reconcile | |
v v v
+----------------+ +----------------+ +----------------+
| Workload | | Workload | | Workload |
| Cluster A | | Cluster B | | Cluster C |
| (prod-seoul) | | (prod-tokyo) | | (edge-store-7) |
+----------------+ +----------------+ +----------------+
핵심 CRD 관계도
CAPI의 CRD는 역할별로 깔끔하게 나뉩니다. 관계를 먼저 그림으로 보겠습니다.
+-----------+
| Cluster | 클러스터 전체의 우산 리소스
+-----+-----+
|
+------------------+-------------------+
| controlPlaneRef | infrastructureRef
v v
+----------------------+ +---------------------------+
| KubeadmControlPlane | | (Infra)Cluster |
| (replicas, version) | | e.g. DockerCluster, |
+----------+-----------+ | AWSCluster: VPC/LB등 |
| +---------------------------+
| 생성/관리
v
+---------+ 1:1 +------------------------+
| Machine |------------- | (Infra)Machine |
+---------+ | e.g. DockerMachine, |
^ | AWSMachine: VM |
| +------------------------+
| 생성 ^ 1:1
+--------+-------+ |
| MachineSet | <-- 템플릿 참조: (Infra)MachineTemplate
+--------+-------+ KubeadmConfigTemplate
^
| 생성/롤링 교체
+--------+----------------+ +---------------------+
| MachineDeployment | | MachineHealthCheck |
| (replicas, version, | | 비정상 머신 탐지 → |
| rolling strategy) | | remediation(교체) |
+-------------------------+ +---------------------+
각 리소스의 책임을 정리하면 다음과 같습니다.
| 리소스 | 비유 (파드 세계) | 책임 |
|---|---|---|
| Cluster | Namespace 비슷한 우산 | 클러스터 네트워크 CIDR, 컨트롤플레인/인프라 참조를 묶는 최상위 리소스 |
| Machine | Pod | 노드 하나에 대한 선언. 불변 — 스펙이 바뀌면 교체 |
| MachineSet | ReplicaSet | 동일 스펙 머신의 복제본 수 유지 |
| MachineDeployment | Deployment | 머신셋의 롤링 업데이트 오케스트레이션 |
| KubeadmControlPlane | StatefulSet에 가까움 | 컨트롤플레인 머신들과 etcd 멤버십, 인증서, 버전 관리 |
| KubeadmConfig | cloud-init 생성기 | 머신 부팅 시 실행할 kubeadm init/join 설정 생성 |
| MachineHealthCheck | livenessProbe + 자동 교체 | 비정상 노드 탐지 후 remediation 트리거 |
KubeadmControlPlane(KCP)은 특히 중요합니다. 컨트롤플레인은 etcd 정족수(quorum) 때문에 일반 워커처럼 막 갈아끼울 수 없습니다. KCP 컨트롤러는 업그레이드 시 새 컨트롤플레인 머신을 하나 추가하고, etcd 멤버로 합류시키고, 옛 머신을 etcd에서 안전하게 제거한 뒤 폐기하는 절차를 자동화합니다. 수동으로 하면 식은땀 나는 작업이 선언 한 줄로 끝납니다.
프로바이더 모델 — 추상화의 실체
CAPI 본체는 인프라를 전혀 모릅니다. 실제 VM 생성은 인프라 프로바이더가, 노드 초기화 스크립트 생성은 부트스트랩 프로바이더가, 컨트롤플레인 오케스트레이션은 컨트롤플레인 프로바이더가 담당합니다.
인프라 프로바이더
| 프로바이더 | 대상 인프라 | 성숙도/비고 |
|---|---|---|
| CAPA | AWS (EC2, EKS) | 성숙. EKS managed control plane 지원 |
| CAPZ | Azure (VM, AKS) | 성숙. AKS managed topology 지원 |
| CAPG | GCP (GCE, GKE) | 안정적이나 CAPA/CAPZ보다 기능 폭 좁음 |
| CAPV | vSphere | 온프레미스 가상화의 사실상 표준 선택지 |
| CAPO | OpenStack | 통신사/프라이빗 클라우드에서 활발 |
| CAPD | Docker 컨테이너 | 개발/테스트/CI 전용. 프로덕션 금지 |
| Metal3 | 베어메탈 (Ironic 기반) | BMC로 물리 서버 전원/이미지 제어 |
| BYOH | 기존 호스트 재활용 | 이미 OS가 깔린 호스트를 노드로 편입 |
부트스트랩 / 컨트롤플레인 프로바이더
kubeadm이 기본값(CABPK + KCP)이지만 생태계는 더 넓습니다.
| 프로바이더 | 배포판 | 특징 |
|---|---|---|
| kubeadm (기본) | 바닐라 쿠버네티스 | 가장 성숙, 레퍼런스 구현 |
| k3s | k3s | 경량 엣지 환경. k3s-io 커뮤니티가 유지 |
| RKE2 | RKE2 | Rancher 계열, 보안 강화 배포판 |
| Talos | Talos Linux | SSH 없는 불변 OS, API로만 관리. Sidero Labs |
| 관리형 CP | EKS/AKS/GKE | 인프라 프로바이더에 내장된 managed control plane 리소스 |
관리형 쿠버네티스와의 관계는 뒤에서 따로 다루지만, 요점만 말하면 CAPA의 AWSManagedControlPlane 같은 리소스로 EKS 컨트롤플레인조차 CAPI 선언의 대상이 될 수 있습니다.
실습 — CAPD로 노트북에서 클러스터 공장 돌려보기
CAPD(Cluster API Provider Docker)는 Docker 컨테이너를 "머신"으로 취급하므로, 클라우드 계정 없이 노트북에서 전체 흐름을 체험할 수 있습니다. kind, Docker, clusterctl, kubectl이 필요합니다.
1단계 — management cluster 준비와 clusterctl init
# CAPD가 Docker 소켓을 쓸 수 있도록 kind 클러스터 생성
cat > kind-mgmt.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: capi-mgmt
nodes:
- role: control-plane
extraMounts:
- hostPath: /var/run/docker.sock
containerPath: /var/run/docker.sock
EOF
kind create cluster --config kind-mgmt.yaml
# ClusterClass 기능 활성화 후 CAPI + CAPD 설치
export CLUSTER_TOPOLOGY=true
clusterctl init --infrastructure docker
clusterctl init은 cert-manager, CAPI 코어, kubeadm 부트스트랩/컨트롤플레인 프로바이더, CAPD를 management cluster에 설치합니다. 설치 확인:
kubectl get pods -A | grep -E "capi|capd|cert-manager"
# capd-system / capi-kubeadm-bootstrap-system /
# capi-kubeadm-control-plane-system / capi-system 이 Running이면 정상
2단계 — 워크로드 클러스터 선언 YAML 전문
clusterctl generate cluster로 템플릿을 뽑을 수도 있지만, 학습을 위해 핵심 리소스를 직접 봅니다. 아래는 컨트롤플레인 1대 + 워커 2대짜리 클러스터의 전문입니다.
# dev-cluster-01.yaml — Cluster: 최상위 우산
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: dev-cluster-01
namespace: default
labels:
env: dev
region: local
spec:
clusterNetwork:
pods:
cidrBlocks: ["192.168.0.0/16"]
serviceDomain: cluster.local
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: dev-cluster-01-cp
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerCluster
name: dev-cluster-01
---
# 인프라 클러스터: CAPD에서는 LB 컨테이너 등을 표현
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerCluster
metadata:
name: dev-cluster-01
namespace: default
---
# 컨트롤플레인 머신의 인프라 템플릿
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
metadata:
name: dev-cluster-01-cp
namespace: default
spec:
template:
spec:
extraMounts:
- containerPath: /var/run/docker.sock
hostPath: /var/run/docker.sock
---
# KubeadmControlPlane: 컨트롤플레인 선언
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: dev-cluster-01-cp
namespace: default
spec:
replicas: 1
version: v1.31.4
machineTemplate:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
name: dev-cluster-01-cp
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
certSANs: [localhost, 127.0.0.1, 0.0.0.0, host.docker.internal]
initConfiguration:
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
joinConfiguration:
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
---
# 워커 머신의 인프라 템플릿
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
metadata:
name: dev-cluster-01-md-0
namespace: default
spec:
template:
spec: {}
---
# 워커 머신의 부트스트랩(join) 설정 템플릿
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
metadata:
name: dev-cluster-01-md-0
namespace: default
spec:
template:
spec:
joinConfiguration:
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
---
# MachineDeployment: 워커 풀 선언
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: dev-cluster-01-md-0
namespace: default
spec:
clusterName: dev-cluster-01
replicas: 2
selector:
matchLabels: null
template:
spec:
clusterName: dev-cluster-01
version: v1.31.4
bootstrap:
configRef:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: dev-cluster-01-md-0
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
name: dev-cluster-01-md-0
참고로 이 글의 예제는 널리 배포된 v1beta1 API 기준입니다. 최신 CAPI 릴리스 라인에서는 v1beta2로의 전환이 진행 중이므로, 적용 전 사용 중인 릴리스의 API 버전을 확인하시기 바랍니다.
kubectl apply -f dev-cluster-01.yaml
3단계 — 수렴 과정 관찰
# 트리 형태로 전체 상태 보기 (가장 유용한 명령)
clusterctl describe cluster dev-cluster-01
# 개별 리소스 추적
kubectl get cluster,machinedeployment,machineset,machine
kubectl get kubeadmcontrolplane
docker ps # CAPD가 만든 "머신" 컨테이너들이 보입니다
Machine이 Pending → Provisioning → Provisioned → Running으로 전이하는 모습은 파드 라이프사이클과 똑같은 감각입니다.
4단계 — kubeconfig 획득과 CNI 설치
새 클러스터의 노드는 CNI가 없어서 NotReady 상태입니다. 이것은 정상입니다.
clusterctl get kubeconfig dev-cluster-01 > dev-01.kubeconfig
kubectl --kubeconfig dev-01.kubeconfig get nodes
# STATUS NotReady — CNI 설치 전이므로 정상
# Calico 설치 (Cilium, Flannel 등 무엇이든 가능)
kubectl --kubeconfig dev-01.kubeconfig apply -f \
https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/calico.yaml
kubectl --kubeconfig dev-01.kubeconfig get nodes
# 잠시 후 모든 노드 Ready
이 시점에서 깨달음이 옵니다. 방금 클러스터 하나를 kubectl apply 한 번으로 만들었습니다. 100개를 만들려면? YAML 100벌을 apply하면 됩니다. 그리고 그 YAML은 Git에 들어갈 수 있습니다.
운영 심화 — 업그레이드, 스케일링, 자동 복구
롤링 업그레이드의 원리 — 노드는 패치하지 않고 교체한다
CAPI의 업그레이드는 OS 패치형이 아니라 머신 교체형입니다. 버전 필드를 바꾸면 컨트롤러가 새 버전 머신을 만들어 합류시키고 옛 머신을 drain 후 폐기합니다.
# 컨트롤플레인 업그레이드: KCP의 version만 변경
kubectl patch kubeadmcontrolplane dev-cluster-01-cp --type merge \
-p '{"spec":{"version":"v1.32.2"}}'
# 워커 업그레이드: MachineDeployment의 version 변경
kubectl patch machinedeployment dev-cluster-01-md-0 --type merge \
-p '{"spec":{"template":{"spec":{"version":"v1.32.2"}}}}'
내부 동작 순서는 다음과 같습니다.
KCP 업그레이드 (replicas=3 기준)
1. 새 v1.32 컨트롤플레인 머신 1대 생성 (총 4대)
2. kubeadm join --control-plane 으로 합류, etcd 멤버 4
3. 옛 v1.31 머신 1대 선택 → etcd member remove → drain → 삭제 (총 3대)
4. 1~3을 옛 머신이 없어질 때까지 반복
5. 항상 quorum 유지: 3 → 4 → 3 → 4 → 3
MachineDeployment 업그레이드 (RollingUpdate 전략)
1. 새 버전 MachineSet 생성
2. maxSurge/maxUnavailable에 따라 새 머신 추가, 옛 머신 drain 후 삭제
3. 옛 MachineSet의 replicas가 0이 될 때까지 반복
업그레이드 순서 규칙도 쿠버네티스 버전 스큐 정책 그대로입니다. 컨트롤플레인 먼저, 워커는 나중. kubelet은 kube-apiserver보다 최대 3개 마이너 버전까지 낮을 수 있으므로 워커 업그레이드를 단계적으로 진행할 여유가 있습니다.
스케일링
# 워커 2대 → 5대
kubectl scale machinedeployment dev-cluster-01-md-0 --replicas=5
# 컨트롤플레인 1대 → 3대 (HA 전환)
kubectl scale kubeadmcontrolplane dev-cluster-01-cp --replicas=3
오토스케일링이 필요하면 cluster-autoscaler가 CAPI 프로바이더 모드를 지원합니다. 노드그룹 대신 MachineDeployment를 스케일 대상으로 삼는 방식입니다.
MachineHealthCheck — 노드 장애 자동 복구
MachineHealthCheck(MHC)는 노드 상태를 감시하다가 비정상 조건이 지속되면 해당 머신을 삭제(=새 머신으로 교체)합니다. 이것이 remediation입니다.
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineHealthCheck
metadata:
name: dev-cluster-01-worker-mhc
namespace: default
spec:
clusterName: dev-cluster-01
# 동시에 너무 많은 머신을 교체하지 않도록 안전판
maxUnhealthy: 40%
# 부팅 후 이 시간 안에 노드로 합류하지 못하면 비정상
nodeStartupTimeout: 10m
selector:
matchLabels:
cluster.x-k8s.io/deployment-name: dev-cluster-01-md-0
unhealthyConditions:
- type: Ready
status: Unknown
timeout: 300s
- type: Ready
status: "False"
timeout: 300s
maxUnhealthy는 매우 중요한 안전장치입니다. 예를 들어 CNI 장애로 전체 노드가 NotReady가 됐을 때, MHC가 모든 머신을 갈아치우는 대참사를 막아 줍니다. 비정상 비율이 임계치를 넘으면 remediation을 멈추고 사람을 기다립니다.
ClusterClass — 클러스터의 템플릿화
클러스터 1개당 YAML 7벌을 관리하는 것은 10개만 돼도 고역입니다. ClusterClass는 클러스터의 "클래스(템플릿)"를 정의하고, 개별 클러스터는 변수만 채우는 얇은 선언으로 만듭니다.
apiVersion: cluster.x-k8s.io/v1beta1
kind: ClusterClass
metadata:
name: standard-dev
namespace: default
spec:
controlPlane:
ref:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlaneTemplate
name: standard-dev-cp
machineInfrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
name: standard-dev-cp-machine
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerClusterTemplate
name: standard-dev-infra
workers:
machineDeployments:
- class: default-worker
template:
bootstrap:
ref:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: standard-dev-worker-bootstrap
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
name: standard-dev-worker-machine
variables:
- name: workerReplicas
required: true
schema:
openAPIV3Schema:
type: integer
default: 2
이제 클러스터 하나는 이렇게 짧아집니다. topology 필드가 핵심입니다.
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: dev-cluster-02
namespace: default
spec:
clusterNetwork:
pods:
cidrBlocks: ["192.168.0.0/16"]
topology:
class: standard-dev
version: v1.31.4
controlPlane:
replicas: 1
workers:
machineDeployments:
- class: default-worker
name: md-0
replicas: 3
variables:
- name: workerReplicas
value: 3
managed topology의 진가는 클래스 변경의 전파입니다. ClusterClass의 머신 템플릿을 수정하면, 그 클래스를 쓰는 모든 클러스터가 (정책에 따라) 롤링 교체로 따라옵니다. 수백 클러스터의 표준화가 이 메커니즘 위에서 굴러갑니다.
머신 이미지 관리 — image-builder
프로덕션에서는 "kubeadm과 kubelet이 미리 구워진" 골든 이미지를 씁니다. 쿠버네티스 SIG가 관리하는 image-builder 프로젝트가 Packer + Ansible 기반으로 AWS AMI, Azure 이미지, vSphere OVA, Raw/QCOW2(베어메탈) 등을 빌드해 줍니다.
git clone https://github.com/kubernetes-sigs/image-builder.git
cd image-builder/images/capi
# 예: vSphere용 Ubuntu 22.04 + K8s v1.31 OVA 빌드
make build-node-ova-vsphere-ubuntu-2204
이미지 버전 관리가 곧 노드 버전 관리입니다. "K8s 버전 + OS 패치 수준"을 이미지 태그로 고정하면, 클러스터의 모든 노드가 비트 단위로 동일해집니다. 불변 인프라의 토대입니다.
GitOps 결합 — 클러스터 플릿을 Git으로 다스리기
CAPI 리소스는 결국 평범한 쿠버네티스 YAML이므로, Argo CD나 Flux로 관리 클러스터에 sync하면 클러스터의 GitOps가 완성됩니다.
+-----------+ push +-----------+ sync +---------------------+
| Platform | ------------> | Git | ------------> | Management Cluster |
| Team | PR 리뷰 | fleet- | Argo CD / | CAPI 컨트롤러가 |
| | | repo | Flux | reconcile |
+-----------+ +-----------+ +----------+----------+
|
생성/업그레이드/복구 v
+---------------------------+
| Workload Clusters (플릿) |
+---------------------------+
저장소 구조 예시는 다음과 같습니다.
fleet-repo/
├── clusterclasses/
│ ├── standard-prod.yaml
│ └── standard-edge.yaml
├── clusters/
│ ├── prod/
│ │ ├── prod-seoul-01.yaml # topology 기반 얇은 선언
│ │ └── prod-tokyo-01.yaml
│ ├── stage/
│ │ └── stage-seoul-01.yaml
│ └── edge/
│ ├── store-0001.yaml
│ └── store-0002.yaml
└── addons/
├── cni/ # 신규 클러스터에 깔릴 기본 애드온
└── monitoring/
이 패턴의 운영 효과는 강력합니다.
- 클러스터 생성/변경이 PR 리뷰를 통과해야 합니다. 감사 추적이 Git 히스토리 그 자체입니다.
- 버전 업그레이드가 "YAML의 version 필드를 바꾸는 PR" 하나로 표준화됩니다.
- 신규 클러스터에 CNI·모니터링 등 애드온을 자동 배포하려면 Argo CD ApplicationSet의 cluster generator, 혹은 CAPI의 ClusterResourceSet을 사용합니다.
플릿 규모의 네이밍과 라벨 전략
수십~수백 클러스터에서는 일관된 네이밍과 라벨이 자동화의 손잡이가 됩니다.
네이밍 규칙 예: <용도>-<리전>-<일련번호> (프로스가 아닌 규칙 문서엔 OK)
prod-seoul-01, stage-tokyo-01, edge-store-0042
권장 라벨 (Cluster 리소스에 부여):
env: prod | stage | dev
region: seoul | tokyo | ...
tier: core | edge
team: payments | search | ...
upgrade-wave: "1" | "2" | "3" # 업그레이드 물결 제어
upgrade-wave 라벨은 특히 유용합니다. 1번 물결(내부용 클러스터)에 먼저 새 버전을 적용해 며칠 굽고, 문제없으면 2번, 3번 물결로 확산하는 운영을 라벨 셀렉터 기반 자동화로 만들 수 있습니다.
pivot, 백업/DR, 버전 스큐 — 관리 클러스터 자체의 운영
닭과 달걀 — management cluster는 누가 만드나
흔한 부트스트랩 패턴은 이렇습니다.
- 로컬에 임시 kind 클러스터를 만들고 clusterctl init
- 임시 클러스터에서 "진짜" management cluster(클라우드/온프렘)를 워크로드 클러스터로 생성
- clusterctl move로 모든 CAPI 리소스를 새 클러스터로 이전(pivot)
- 이후 management cluster가 자기 자신을 관리(self-hosted)
# 새 클러스터에 프로바이더 설치 후, 리소스 이전
clusterctl init --kubeconfig mgmt-real.kubeconfig --infrastructure aws
clusterctl move --to-kubeconfig mgmt-real.kubeconfig
clusterctl move는 Cluster를 비롯한 소유 체인의 객체와 시크릿(kubeconfig, CA, etcd 인증서)을 통째로 옮깁니다. 이전 중에는 reconcile이 일시 중지(pause)되므로 워크로드 클러스터는 영향을 받지 않습니다.
management cluster가 죽으면 워크로드도 죽는가
아닙니다. 이것이 CAPI 설계의 중요한 미덕입니다. 워크로드 클러스터는 management cluster 없이도 완전히 독립적으로 동작합니다. 잃는 것은 라이프사이클 작업(생성/업그레이드/자동복구)뿐입니다. 그래도 DR 계획은 필요합니다.
- CAPI 리소스의 원본이 Git에 있다면(GitOps), 새 management cluster를 만들고 다시 sync하는 것이 1차 복구 경로입니다. 단, 클러스터 시크릿(CA 등)은 Git에 없으므로 백업이 필수입니다.
- Velero 등으로 management cluster의 CAPI 네임스페이스(리소스+시크릿)를 주기 백업합니다.
- 복구 리허설을 반드시 해 봐야 합니다. "백업이 있다"와 "복구가 된다"는 다른 명제입니다.
버전 스큐와 업그레이드 순서
관리 체계 자체의 버전도 관리 대상입니다.
업그레이드 순서 (위에서 아래로)
1. management cluster의 쿠버네티스 버전
(CAPI 릴리스가 지원하는 범위 확인)
2. clusterctl 바이너리
3. CAPI 코어 + 프로바이더: clusterctl upgrade plan / apply
4. 워크로드 클러스터들의 K8s 버전 (wave 단위 롤링)
- 각 클러스터 내부: 컨트롤플레인 → 워커
- 마이너 버전 건너뛰기 금지 (1.30 → 1.32 불가, 1.31 경유)
CAPI의 contract 버전(인프라 프로바이더와의 호환 계약)도 함께 확인해야 합니다. 코어만 올리고 프로바이더를 방치하면 reconcile이 멈추는 사고가 납니다. clusterctl upgrade plan이 호환 매트릭스를 계산해 주므로 반드시 plan을 먼저 봅니다.
한계와 현실 — 도입 전에 알아야 할 것들
러닝커브와 운영 성숙도 요구
CAPI는 추상화 계층이 많습니다. 문제가 생기면 Cluster → KCP → Machine → InfraMachine → cloud-init 로그까지 내려가는 디버깅 체인을 탈 줄 알아야 합니다. 쿠버네티스 컨트롤러 패턴(owner reference, condition, finalizer)에 익숙하지 않은 팀에게는 가파른 언덕입니다.
프로바이더 성숙도 편차
CAPA/CAPZ/CAPV는 대규모 프로덕션 사례가 많지만, 일부 프로바이더는 기능 폭과 문서가 얇습니다. 도입 전 해당 프로바이더의 릴리스 주기, 이슈 응답 속도, ClusterClass 지원 여부를 반드시 확인하시기 바랍니다.
관리형 쿠버네티스(EKS/AKS/GKE)와의 관계
"어차피 EKS 쓰는데 CAPI가 필요한가?"는 정당한 질문입니다. 답은 조직 형태에 따라 다릅니다.
- EKS 클러스터 3개만 쓴다면 Terraform이나 eksctl로 충분할 가능성이 큽니다.
- 그러나 EKS + 온프레미스 vSphere + 엣지 베어메탈이 섞인 플릿이라면, CAPI는 단일 API로 전부를 덮는 유일한 선택지에 가깝습니다. CAPA의 AWSManagedControlPlane, CAPZ의 AKS 지원처럼 관리형 컨트롤플레인도 CAPI 리소스로 선언할 수 있기 때문입니다.
Crossplane / Terraform과의 비교
| 관점 | Cluster API | Crossplane | Terraform |
|---|---|---|---|
| 주 목적 | K8s 클러스터 라이프사이클 전용 | 범용 클라우드 리소스 조립 | 범용 IaC |
| 동작 모델 | 컨트롤러 상시 reconcile | 컨트롤러 상시 reconcile | 실행 시점 apply |
| 노드/머신 추상화 | 1급 개념 (Machine 등) | 없음 (관리형 K8s 위주) | 모듈로 간접 표현 |
| 컨트롤플레인 오케스트레이션 | KCP가 etcd까지 자동화 | 관리형 CP에 위임 | 직접 구현 필요 |
| 자동 복구 | MachineHealthCheck 내장 | 리소스 드리프트 보정 수준 | 없음 |
| 클러스터 외 리소스(DB 등) | 범위 밖 | 강점 | 강점 |
| 상태 저장소 | etcd (K8s 네이티브) | etcd | state 파일 |
셋은 적이 아니라 분업 관계인 경우가 많습니다. 실제로 "VPC/IAM은 Terraform, 클러스터는 CAPI, 클러스터가 쓰는 DB는 Crossplane"처럼 레이어를 나누는 조직이 흔합니다.
어떤 조직에 맞는가 — 의사결정 가이드
질문 1. 관리하는 클러스터가 5개 미만이고 늘 계획이 없다
→ CAPI는 과합니다. 관리형 K8s + IaC로 충분.
질문 2. 클러스터가 10개 이상이거나, 테넌트/엣지로 늘어날 예정이다
→ CAPI 강력 후보. 특히 클러스터 생성이 셀프서비스가 되어야 한다면.
질문 3. 온프레미스(vSphere/베어메탈)나 멀티클라우드가 섞여 있다
→ CAPI의 가장 강한 사용처. 단일 선언 모델의 가치가 극대화.
질문 4. 팀에 K8s 컨트롤러/CRD 운영 경험이 있는가
→ 없다면 먼저 작은 규모(CAPD 실습, dev 클러스터)로 근육을 만들 것.
질문 5. 플랫폼 팀이 존재하는가
→ CAPI는 플랫폼 팀의 도구입니다. 전담 주인이 없으면 방치된 채 썩습니다.
트러블슈팅 — 머신이 멈췄을 때 보는 순서
머신이 Provisioning에서 멈추는 것은 CAPI 운영에서 가장 흔한 증상입니다. 진단 순서를 체화해 두면 대부분 20분 안에 원인을 찾습니다.
# 1. 전체 그림: 어디서 멈췄는지 트리로 확인
clusterctl describe cluster dev-cluster-01 --show-conditions all
# 2. Machine의 conditions와 이벤트
kubectl describe machine dev-cluster-01-md-0-xxxxx
kubectl get events --field-selector involvedObject.kind=Machine
# 3. 인프라 머신(프로바이더 쪽) 상태 — VM이 실제로 떴는가
kubectl describe dockermachine dev-cluster-01-md-0-xxxxx
# AWS라면: kubectl describe awsmachine ...
# 4. 부트스트랩 시크릿이 생성됐는가 (cloud-init 데이터)
kubectl get secret | grep dev-cluster-01-md-0
# 5. 컨트롤러 로그 — 레이어별로
kubectl logs -n capi-system deploy/capi-controller-manager
kubectl logs -n capi-kubeadm-bootstrap-system \
deploy/capi-kubeadm-bootstrap-controller-manager
kubectl logs -n capi-kubeadm-control-plane-system \
deploy/capi-kubeadm-control-plane-controller-manager
kubectl logs -n capd-system deploy/capd-controller-manager
# 6. 머신 안의 cloud-init 로그 (인프라별 접근 방법 상이)
# CAPD: docker exec로 컨테이너 진입 후
# cat /var/log/cloud-init-output.log (또는 journalctl -u kubelet)
자주 만나는 원인 목록입니다.
| 증상 | 흔한 원인 |
|---|---|
| InfraMachine이 안 생김 | 프로바이더 미설치, credential 시크릿 오류, 쿼터 초과 |
| VM은 떴는데 join 안 됨 | 이미지에 kubeadm/kubelet 없음, CP 엔드포인트로의 네트워크 차단 |
| 첫 CP 머신에서 멈춤 | LB/엔드포인트 미생성, certSANs 불일치, etcd 기동 실패 |
| 노드 NotReady 지속 | CNI 미설치(정상 단계), CNI 설정 오류 |
| 업그레이드 중 멈춤 | PDB 때문에 drain 불가, maxSurge 자원 부족 |
| 삭제가 안 끝남 | finalizer 대기 — 인프라 삭제 실패를 프로바이더 로그에서 확인 |
특히 PDB(PodDisruptionBudget)로 인한 drain 교착은 업그레이드 단골 이슈입니다. minAvailable이 레플리카 수와 같은 PDB가 있으면 영원히 drain이 끝나지 않습니다. nodeDrainTimeout을 설정해 두면 일정 시간 후 강제 진행시킬 수 있습니다.
도입 체크리스트
[ ] 관리 대상 클러스터 수/증가 계획이 CAPI 도입을 정당화하는가
[ ] 인프라 프로바이더(CAPA/CAPV/Metal3 등)의 성숙도를 검증했는가
[ ] CAPD 기반 로컬 실습으로 팀 전원이 전체 흐름을 경험했는가
[ ] 골든 이미지 파이프라인(image-builder)을 구축했는가
[ ] ClusterClass로 표준 클러스터 형상을 템플릿화했는가
[ ] MachineHealthCheck의 maxUnhealthy 안전판을 설정했는가
[ ] GitOps 저장소 구조와 PR 리뷰 정책을 정의했는가
[ ] 네이밍/라벨/업그레이드 wave 전략을 문서화했는가
[ ] management cluster 백업(Velero 등)과 복구 리허설을 마쳤는가
[ ] clusterctl upgrade plan 기반의 정기 업그레이드 캘린더가 있는가
[ ] 머신 멈춤 트러블슈팅 런북을 작성했는가
[ ] PDB/nodeDrainTimeout 정책을 워크로드 팀과 합의했는가
마치며
Cluster API의 본질은 "클러스터 생성 도구"가 아니라 클러스터에 대한 운영 모델의 교체입니다. 콘솔 클릭과 스크립트의 세계에서 클러스터는 손으로 빚는 공예품이었습니다. CAPI의 세계에서 클러스터는 Deployment가 찍어내는 파드처럼, 선언으로 정의되고 컨트롤러가 지키는 평범한 리소스입니다. 노드가 죽으면 교체되고, 버전을 올리면 롤링으로 수렴하고, 모든 변경이 Git 히스토리에 남습니다.
물론 공짜가 아닙니다. 추상화 계층의 러닝커브, 프로바이더 생태계에 대한 안목, 그리고 management cluster라는 새 운영 대상이 생깁니다. 클러스터 다섯 개 이하의 조직에는 과한 장비일 수 있습니다. 그러나 클러스터가 곧 수십 개가 될 운명이라면 — 그리고 대부분의 플랫폼 조직은 그 운명을 피하지 못합니다 — 일찍 배워 둘수록 이자가 붙는 기술입니다. 오늘 노트북에서 kind와 CAPD로 30분짜리 실습부터 시작해 보시기 바랍니다.
참고 자료
- Cluster API 공식 문서(The Cluster API Book): https://cluster-api.sigs.k8s.io/
- Cluster API GitHub 저장소: https://github.com/kubernetes-sigs/cluster-api
- Quick Start(CAPD 실습): https://cluster-api.sigs.k8s.io/user/quick-start
- ClusterClass 개념 문서: https://cluster-api.sigs.k8s.io/tasks/experimental-features/cluster-class/
- clusterctl 레퍼런스: https://cluster-api.sigs.k8s.io/clusterctl/overview
- Cluster API Provider AWS(CAPA): https://cluster-api-aws.sigs.k8s.io/
- Cluster API Provider Azure(CAPZ): https://capz.sigs.k8s.io/
- Cluster API Provider GCP(CAPG): https://github.com/kubernetes-sigs/cluster-api-provider-gcp
- Cluster API Provider vSphere(CAPV): https://github.com/kubernetes-sigs/cluster-api-provider-vsphere
- Cluster API Provider OpenStack(CAPO): https://github.com/kubernetes-sigs/cluster-api-provider-openstack
- Metal3(베어메탈 프로바이더): https://metal3.io/
- Kubernetes image-builder: https://image-builder.sigs.k8s.io/
- kubeadm 공식 문서: https://kubernetes.io/docs/reference/setup-tools/kubeadm/
- 쿠버네티스 버전 스큐 정책: https://kubernetes.io/releases/version-skew-policy/
- kind 공식 문서: https://kind.sigs.k8s.io/
- Argo CD 공식 문서: https://argo-cd.readthedocs.io/
- Flux 공식 문서: https://fluxcd.io/
- Crossplane 공식 사이트: https://www.crossplane.io/