상태: 초안 (2026-10-17), 소스 + 읽기 전용 실측. 기준은 KubeVirt
v1.9.0(커밋79d34c17)입니다. 웹훅 경고 문구는 소스에서 읽었고, 실제 kubectl 화면에서 본 것은 아닙니다(VM 을 새로 만들지 않았습니다).
KubeVirt 소스 코드 읽기 2: API 와 CRD
목차
1. 직관: 세 군데에서 정해진다
VM 의 YAML 한 줄이 받아들여지기까지 세 군데가 관여합니다.
- 타입 —
types.go의 Go 구조체가 필드 이름과 모양을 정합니다. - 스키마 — 그 구조체에서 생성된 OpenAPI 스키마가 CRD 에 실려, kube-apiserver 가 모양이 틀린 요청을 먼저 걸러 냅니다.
- 웹훅 — 모양은 맞아도 뜻이 틀린 요청(둘이 동시에 쓰이면 안 되는 필드, 꺼진 기능 게이트)은 virt-api 의 웹훅이 거릅니다. 거절하지 않고 경고만 붙일 수도 있습니다.
2. 타입: types.go
staging/src/kubevirt.io/api/core/v1/ 아래의 types.go 한 파일이 4,090줄입니다. 먼저 두 구조체만 찾으면 됩니다.
VirtualMachineInstance(types.go:47) —Spec VirtualMachineInstanceSpec와Status VirtualMachineInstanceStatus. 위의+genclient:noStatus마커는 「status 를 따로 고치는 클라이언트 메서드를 만들지 마라」 는 생성기 지시입니다. VMI 의 status 는 virt-controller 와 virt-handler 가 여럿이 나눠 씁니다(3·4편).VirtualMachine(:1942) —Spec VirtualMachineSpec(:2007) 안에 VMI 의 틀(Template)과 켜 둘지(Running또는RunStrategy)가 있습니다.
Running 필드에는 Deprecated: 주석이 붙어 있습니다(:2010).
그래도 지우지 않고 남겨 둔 이유는 메서드 하나에 보입니다. 두 필드를 하나의 실행 전략으로 접는 RunStrategy()
(:1957)입니다.
RunStrategy := RunStrategyHalted
if vm.Spec.Running != nil {
if (*vm.Spec.Running) == true {
RunStrategy = RunStrategyAlways
}
} else if vm.Spec.RunStrategy != nil {
RunStrategy = *vm.Spec.RunStrategy
}
출처: kubevirt/kubevirt v1.9.0 types.go 1961–1968행(Apache-2.0). 컨트롤러는 이 메서드만 부르므로, running: true 는 코드
안에서 곧바로 Always 가 됩니다. 실행 전략 상수는 다섯이 문서에 나오지만 코드에는 여섯 번째 WaitAsReceiver
(:1995)도 있습니다. 클러스터
사이 마이그레이션을 받는 쪽 VM 을 위한 값이고, 기능 게이트가 켜졌을 때만 웹훅이 받아 줍니다(아래 4절).
VMI 의 상(phase) 목록(:1098)은
3·4편을 읽을 때 지도가 됩니다. Scheduled 에 달린 주석이 「여기서 virt-controller 의 책임이 끝나고 virt-handler 가 넘겨받는다」
고 말합니다(:1109).
3. 생성 코드: 손대지 않는 파일들
make generate(Makefile:53)는 빌더 컨테이너 안에서
hack/generate.sh 를 돌립니다. 이 스크립트가 순서대로 부르는 생성기는 이렇습니다.
| 생성기 | 입력 | 출력 | 실측 줄 수 |
|---|---|---|---|
swagger-doc |
types.go 의 주석 |
types_swagger_generated.go(API 문서의 설명) |
1,312 |
deepcopy-gen(generate.sh:27) |
+k8s:deepcopy-gen 마커 |
deepcopy_generated.go |
7,410 |
defaulter-gen |
기본값 함수 | zz_generated.defaults.go |
— |
openapi-gen |
타입 전체 | client-go/api/openapi_generated.go |
40,507 |
client-gen |
+genclient 마커 |
client-go 의 타입 있는 클라이언트 |
— |
controller-gen crd(:139) |
+kubebuilder: 마커 |
CRD YAML → crd-validation-generator(:177) → virt-operator/…/validations_generated.go |
33,701 |
마지막 줄이 특이합니다. KubeVirt 는 CRD YAML 을 저장소에 두지 않고, controller-gen 이 만든 스키마를 Go 문자열로 바꿔
virt-operator 안에 넣습니다. virt-operator 가 설치할 때 NewVirtualMachineCrd()
(crds.go:187)로 CRD 를
코드로 만들고, CRDsValidation["virtualmachine"]
(validations_generated.go:4444)
을 openAPIV3Schema 로 붙입니다. 그래서 필드 하나를 더하는 PR 은 types.go 한 줄에 생성 파일 수백 줄이 따라옵니다. 리뷰어는
생성 파일을 읽지 않고, CI 가 make generate 를 다시 돌려 차이가 없는지만 봅니다(generate-verify). 손으로 고친 생성 파일은
여기서 떨어집니다.
4. 웹훅: 요청이 지나는 다섯 관문
누가 웹훅을 등록하나
웹훅 설정 객체(ValidatingWebhookConfiguration)도 virt-operator 가 코드로 만듭니다.
NewVirtAPIValidatingWebhookConfiguration()(webhooks.go:296)
이 경로마다 웹훅을 하나씩 넣고 failurePolicy 를 Fail 로 둡니다. VM 의 경로는 상수 VMValidatePath = "/virtualmachines-validate"
(:945)입니다.
반대편에서 virt-api 가 같은 상수로 HTTP 처리기를 겁니다(api.go:994).
경로 이름을 한쪽에서만 바꾸면 웹훅이 404 를 받고, Fail 정책 때문에 VM 을 아무도 만들 수 없게 됩니다. 두 파일이 같은 상수를 쓰는 까닭입니다.
VMsAdmitter.Admit() 을 한 줄씩
vms-admitter.go:87
의 Admit() 은 이 순서로 검사합니다.
- 요청 자원이 정말
virtualmachines인지, 스키마에 맞는지(ValidateSchema). - 지우는 중인 VM 이면 바로 허용합니다. 다른 삭제와 경쟁하지 않으려는 것입니다.
- 복사본에 instancetype·preference 를 적용하고 기본값을 채운 뒤 그 결과를 검증합니다. 주석에 「저장하고 싶지 않아서 복사본에 적용한다」 고 적혀 있습니다. 즉 검증은 「실제로 뜰 VMI」 를 기준으로 하지만, etcd 에는 사용자가 보낸 그대로 들어갑니다.
- 만들 때(
Create)만: 기능 게이트가 꺼진 필드를 쓰지 않았는지, 네트워크 설정이 맞는지. ValidateVirtualMachineSpec()— 템플릿, 메타데이터, VMI 스펙 전체, DataVolume 틀, 실행 전략, 실시간 갱신 기능.- 스토리지 쪽 검사(
storageadmitters.Admit)와 볼륨 요청. - 통과하면 경고를 모아 허용합니다.
마지막 단계가 spec.running 경고가 나오는 곳입니다(:175–178).
warnings := warnDeprecatedAPIs(&vm.Spec.Template.Spec, admitter.ClusterConfig)
if vm.Spec.Running != nil {
warnings = append(warnings, "spec.running is deprecated, please use spec.runStrategy instead.")
}
출처: kubevirt/kubevirt v1.9.0 vms-admitter.go 175–178행(Apache-2.0). 거절이 아니라 AdmissionResponse.Warnings 라서 VM 은 그대로
만들어지고, kubectl 은 응답 헤더의 경고를 Warning: 줄로 보여 줍니다. warnDeprecatedAPIs()
(vmi-create-admitter.go:141)는
사용 중단 상태가 된 기능 게이트를 스펙이 쓰고 있을 때 경고를 더합니다.
실행 전략 검사
validateRunStrategy()(:231)는
세 가지를 봅니다. running 과 runStrategy 를 함께 쓰면 거절, 둘 다 없으면 「RunStrategy must be specified」 로 거절, 그리고
runStrategy 값이 허용 목록 validRunStrategies(:53)
다섯 중 하나인지입니다. WaitAsReceiver 는 이 목록에 없고, DecentralizedLiveMigration 기능 게이트가 켜졌을 때만 따로 통과합니다.
웹훅 말고 또 하나의 입구: 서브리소스
virt-api 는 웹훅 서버이면서 집계 API 서버(v1.subresources.kubevirt.io)이기도 합니다. virtctl start 는 VM 을 직접 고치지 않고
…/virtualmachines/<이름>/start 를 부릅니다. 처리기 StartVMRequestHandler()
(lifecycle.go:43)는 실행 전략을 보고
Halted 면 VM 의 spec.runStrategy(또는 옛 spec.running)를 JSON 패치로 바꾸고, Manual·RerunOnFailure 면 status 에 시작 요청을
넣습니다. Always 처럼 이미 켜져 있어야 하는 전략에는 의미가 없다고 주석에 적혀 있습니다. 결국 서브리소스도 VM 객체를 고칠 뿐이고,
실제로 VMI 를 만드는 일은 3편의 VM 컨트롤러가 합니다.
5. 실측
v1.9.0 이 깔린 작은 클러스터에서 읽었습니다(2026-10-17).
- 웹훅 설정.
virt-api-validator에 검증 웹훅 23개,virt-api-mutator에 변형 웹훅 4개가 있고 27개 모두failurePolicy: Fail입니다. VM 의 것은virtualmachine-validator.kubevirt.io→/virtualmachines-validate, 연산CREATE·UPDATE로, 위 상수와 같습니다. VMI 는 만들기(/virtualmachineinstances-validate-create)와 고치기(…-update)가 따로 있습니다. 같은 클러스터의 CDI 도 웹훅 설정 넷을 따로 둡니다. - CRD 의 판.
virtualmachines.kubevirt.io와virtualmachineinstances.kubevirt.io모두v1·v1alpha3두 판을 내주고, 저장 판 (status.storedVersions)은v1하나입니다. - 서브리소스.
/apis/subresources.kubevirt.io/v1이 31개를 내줍니다. VM 쪽은start·stop·restart·migrate등, VMI 쪽은console·vnc·pause·freeze·addvolume등입니다. 「학생 VM 이 뜨기까지」 2편에서 본 대로exec는 없습니다. running: true로 만든 VM. 실습 백엔드가 만든 VM 하나는 아직spec.running: true를 씁니다. 그 VM 의status.runStrategy는Always였습니다. 위RunStrategy()의 접기가 status 까지 이어진 것입니다(status 에 적는 곳은 vm.go:2478). 만들 때 받은 경고는 백엔드가 기록하지 않아 미실측입니다.
6. 한계
- 경고 문구는 소스에서 읽었습니다. 같은 VM 을 서버 쪽 dry-run 으로 보내면 VM 을 만들지 않고도 경고를 볼 수 있지만, 이번에는 클러스터에 쓰는 요청을 하나도 보내지 않기로 해서 하지 않았습니다.
- 변형 웹훅(
vm-mutator.go)이 채우는 기본값 목록은 따라가지 않았습니다. 기본값의 대부분은 VMI 쪽(vmi-mutator.go,pkg/defaults)에 있습니다. - 생성기의 판과 실행 시간은 미실측입니다(
make generate를 돌리지 않았습니다).
티티 한마디. 필드 하나 더하는 PR 이 수백 줄인 걸 보고 놀라지 마세요. 진짜로 사람이 쓴 건 types.go 몇 줄이고 나머지는 기계가 쓴 거예요. 리뷰할 때는 types.go 와 웹훅, 그리고 시험만 보면 돼요!
참고
모두 2026-10-17 에 열람했습니다.
types.go: https://github.com/kubevirt/kubevirt/blob/v1.9.0/staging/src/kubevirt.io/api/core/v1/types.govms-admitter.go: https://github.com/kubevirt/kubevirt/blob/v1.9.0/pkg/virt-api/webhooks/validating-webhook/admitters/vms-admitter.gohack/generate.sh: https://github.com/kubevirt/kubevirt/blob/v1.9.0/hack/generate.sh- 쿠버네티스 문서, 동적 어드미션 제어(경고 포함): https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
- 자매 시리즈 「학생 VM 이 뜨기까지」 2편: KubeVirt 안쪽