- Kubernetes의
pv_controller.go는 PV/PVC 바인딩을 동기화하는 컨트롤러로, 파일 상단부터 “단순화하지 말고 space shuttle style을 유지하라”고 못박음 - 이 스타일은 모든
if에 대응하는else를 두고 명백해 보이는 조건도 주석으로 남겨, 검토된 분기와 의도를 코드 안에 드러내려는 방식임 - 설계의 중심은
pvc.Spec.VolumeName과pv.Spec.ClaimRef로 이어지는 양방향 포인터이며, 트랜잭션 없는 환경에서 경쟁·삭제·사용자 수정·동시 바인딩을 복구 가능하게 다룸 - 컨트롤러는 PV/PVC 변경 감시, 내부 캐시, 단일 워커 큐, 이벤트 기록, 동적 프로비저닝, CSI 마이그레이션 인터페이스를 엮어 바인딩 상태 전이를 관리함
- 장황한 분기와 주석은 동작의 업무 지식과 실패 복구 맥락을 보존하기 위한 장치라서, 향후 변경도 같은 스타일을 따라야 함
pv_controller.go의 역할과 작성 원칙
pv_controller.go는 Kubernetespersistentvolume패키지의 PersistentVolumeController 구현 파일임- 이 컨트롤러는
PersistentVolumeClaim과PersistentVolume의 상태를 맞춤PersistentVolume변경을 감시하는 캐시 컨트롤러PersistentVolumeClaim변경을 감시하는 캐시 컨트롤러- 두 객체의 변경 이벤트를 바탕으로 PV/PVC 상태 동기화
- 파일 상단 주석은 이 코드를 단순화하지 말라고 반복해서 경고함
- 스타일 이름은
space shuttle style - 모든
if문에 대응하는else를 두는 방식임 - 단순 에러 체크를 제외하고 모든 분기를 명시하려는 목적임
- 명백해 보이는 동작도 주석으로 적어 유지보수자가 바인딩 복잡성을 추적할 수 있게 함
- 스타일 이름은
space shuttle style을 유지하는 이유
- 이 컨트롤러는 원래 세 개의 컨트롤러에 나뉘어 있던 작업을 하나로 합친 결과물임
- PV 서브시스템을 단순화하는 과정에서, 모든 조건을 코드에서 명시적으로 다루는 방식이 필요해짐
- 그 결과 코드가 장황하고 주석과 분기가 많아 보일 수 있음
- 이 장황함은 바인딩 동작의 업무 지식과 맥락을 코드에 남기기 위한 장치임
- 이 파일을 바꿀 때는
space shuttle style을 보존하고, 필요한 경우 같은 방식으로 분기와 주석을 추가해야 함
핵심 설계: PV와 PVC의 양방향 포인터
- 설계의 중심에는 PV와 PVC 사이의 양방향 포인터가 있음
- PVC 쪽 포인터:
pvc.Spec.VolumeName - PV 쪽 포인터:
pv.Spec.ClaimRef
- PVC 쪽 포인터:
- 이 양방향성은 트랜잭션이 없는 시스템에서 다루기 어렵지만, 장애 상황에서도 정상 동작을 보장하기 위해 필요함
- rogue HA controller instance가 경쟁 상태를 만들면, 구분할 수 없는 여러 바인딩이 생겨 데이터 손실 가능성이 생길 수 있음
- 컨트롤러는 기본적으로 active-passive 고가용성 모드에서 동작하도록 설계됨
- 객체 전이는 active-active HA에서도 동작할 수 있게 설계됨
- 다만 두 active 컨트롤러가 자주 충돌하면 성능이 낮아질 수 있음
바인딩 방식과 복구 조건
- 컨트롤러는 양방향 pre-bound 객체를 지원함
- 특정 PV를 원하는 PVC
- 특정 PVC를 위해 예약된 PV
- 바인딩은 두 단계로 진행됨
- 먼저
PV.Spec.ClaimRef를 수정함 - 다음으로
PVC.Spec.VolumeName을 수정함
- 먼저
- 이 과정의 어느 시점에서도 PV나 PVC가 사용자 또는 다른 컨트롤러에 의해 수정·삭제될 수 있음
- 두 개 이상의 컨트롤러가 서로 다른 볼륨과 클레임을 동시에 바인딩하려 할 수도 있음
- 컨트롤러는 이런 충돌 상황을 복구할 수 있어야 함
컨트롤러 구조체의 주요 구성
PersistentVolumeController는 PV/PVC 동기화에 필요한 lister, informer sync 함수, Kubernetes 클라이언트, 이벤트 기록기, 볼륨 플러그인 관리자 등을 가짐- 마지막으로 알려진 PV/PVC 버전은 내부 캐시에 저장됨
volumes persistentVolumeOrderedIndexclaims cache.Store
- 이 캐시는 API 서버에 저장한 최신 버전과 etcd 이벤트로 들어온 버전을 함께 반영함
- 바인딩 하나는 대략 네 개의 이벤트를 만들 수 있음
volume.Spec업데이트volume.Status업데이트claim.Spec업데이트claim.Status업데이트
- 내부 캐시가 없으면 informer가 오래된 상태를 들고 있을 때 이미 완료된 바인딩을 다시 고치려 할 수 있음
- 이때 API 서버에 다시 쓰기를 시도하면 이미 저장된 객체와 버전 충돌이 발생할 수 있음
워크 큐와 동시성 제약
- 컨트롤러는 claim과 volume 처리를 위한 별도 workqueue를 가짐
claimQueuevolumeQueue
- 각 큐는 정확히 하나의 worker thread만 가져야 함
- 특히
syncClaim()은 재진입 가능하지 않음 - 두 개의
syncClaim()이 동시에 실행되면 다음 문제가 생길 수 있음- 서로 다른 두 claim을 같은 volume에 바인딩
- 하나의 claim을 두 volume에 바인딩
- 컨트롤러는 API 서버의 버전 에러와 자체 검사로 이런 상황을 복구할 수 있지만, multi-worker 방식은 전체 속도를 낮출 수 있음
syncClaim: PVC 동기화의 진입점
syncClaim은 claim이 생성, 업데이트, 주기 동기화될 때 호출되는 주요 메서드임- 이 메서드는 이벤트 종류를 구분하지 않음
- 먼저 PVC에 올바른 migration annotation을 설정하고, 필요하면 API 서버에 업데이트함
- 이후
AnnBindCompletedannotation 유무에 따라 분기함- annotation이 없으면
syncUnboundClaim - annotation이 있으면
syncBoundClaim
- annotation이 없으면
- 실제 처리는 가독성을 위해 unbound claim과 bound claim 메서드로 나뉨
checkVolumeSatisfyClaim: PV 요구사항 검사
checkVolumeSatisfyClaim은 요청된 PV가 PVC 요구사항을 만족하는지 확인함- 검사 조건은 코드에 명시적으로 나열됨
- PV에
DeletionTimestamp가 있으면 오류 - PV 용량이 PVC 요청 용량보다 작으면 오류
storageClassName이 다르면 오류VolumeAttributesClassfeature gate가 켜져 있으면VolumeAttributesClassName일치 여부를 검사- feature gate가 꺼져 있는데 claim이나 volume에
VolumeAttributesClassName이 있으면 오류 volumeMode가 호환되지 않으면 오류- access mode가 호환되지 않으면 오류
- PV에
- 모든 조건을 통과하면
nil을 반환함
지연 바인딩 PVC의 이벤트 처리
emitEventForUnboundDelayBindingClaim은 지연 바인딩 모드의 미바인딩 claim에 정보를 주는 이벤트를 생성함- 기본 reason은
WaitForFirstConsumer - 기본 메시지는 첫 consumer가 생성되기 전까지 바인딩을 기다린다는 내용임
- 해당 PVC를 참조하는 아직 스케줄되지 않은 Pod가 있으면 reason이
WaitForPodScheduled로 바뀜- Pod가 여러 개면 모든 Pod 이름을 메시지에 포함함
- volume scheduling에서는 하나의 Pod만 고려되지만 어떤 Pod가 사용되는지 알 수 없어 모든 Pod를 포함함
syncUnboundClaim: 아직 바인딩되지 않은 PVC 처리
claim.Spec.VolumeName이 비어 있으면 사용자가 특정 PV를 요구하지 않은 상태임- 이 경우 컨트롤러는 claim의 지연 바인딩 모드를 확인하고,
findBestMatchForClaim으로 가장 적합한 PV를 찾음 - 적합한 PV가 없으면 다음 순서로 처리함
- 기본 StorageClass를 할당할 수 있으면 PVC를 업데이트하고 동기화를 종료함
- 지연 바인딩이고 아직 provisioning 상태가 아니면 대기 이벤트를 생성함
- claim에 StorageClass가 있으면
provisionClaim으로 동적 프로비저닝을 시도함 - 그 외에는 사용 가능한 PV도 없고 StorageClass도 없다는
FailedBinding이벤트를 기록함
- 적합한 PV가 있으면
bind를 호출해 PV와 PVC를 바인딩함- 성공 시 provision + binding 작업의 metric을 기록하고 timestamp 캐시를 정리함
- 저장 중 오류가 나면 이후
syncClaim이 바인딩을 마무리함
특정 PV를 요구하는 PVC 처리
claim.Spec.VolumeName이 비어 있지 않으면 사용자가 특정 PV를 요청한 상태임- 요청한 PV가 캐시에 없으면 PVC 상태를
Pending으로 업데이트하고 나중에 재시도함 - 요청한 PV가 있고
volume.Spec.ClaimRef가 없으면, PV가 아직 claim되지 않은 상태임checkVolumeSatisfyClaim으로 요구사항을 검사함- 요구사항을 만족하지 못하면
VolumeMismatch이벤트를 기록하고 PVC를Pending으로 유지함 - 요구사항을 만족하면
bind를 호출함
- 요청한 PV가 이미 이 PVC에 claim되어 있으면
bind를 호출해 바인딩을 마무리함 - 요청한 PV가 다른 claim에 묶여 있으면 다음처럼 처리함
- claim이 controller에 의해 바인딩된 annotation을 갖고 있지 않으면
FailedBinding이벤트를 기록하고Pending으로 둠 - controller가 바인딩한 것으로 보이는데 다른 claim에 묶여 있으면 “should never happen” 상태로 오류를 반환함
- claim이 controller에 의해 바인딩된 annotation을 갖고 있지 않으면
syncBoundClaim: 이미 바인딩된 PVC 처리
syncBoundClaim은AnnBindCompletedannotation이 있는 PVC를 처리함- 이미 바인딩된 claim인데
claim.Spec.VolumeName이 비어 있으면 claim 상태를ClaimLost로 변경함- 이벤트 메시지는 bound claim이 PV 참조를 잃었고 volume의 데이터가 손실됐다는 내용임
- claim이 가리키는 PV가 존재하지 않으면 역시
ClaimLost로 변경함- 이벤트 메시지는 bound claim이 PersistentVolume을 잃었고 데이터가 손실됐다는 내용임
- PV가 존재하지만
volume.Spec.ClaimRef가 없으면 volume이 unbound 상태가 된 것으로 보고 다시bind를 호출함 - PV의
ClaimRef.UID가 claim의 UID와 같으면 정상 바인딩 상태로 보고bind를 호출함- 대부분의 경우 아무 작업도 하지 않는 호출임
- PV가 다른 claimant를 가리키면 claim phase를 terminal 상태인
Lost로 설정함
syncVolume: PV 동기화의 진입점
syncVolume은 volume 생성, 업데이트, 주기 동기화 시 호출되는 주요 메서드임- 이벤트 종류는 구분하지 않음
- 먼저 PV에 올바른 migration annotation과 finalizer를 설정하고 필요하면 API 서버에 업데이트함
volume.Spec.ClaimRef가 없으면 사용되지 않는 volume으로 보고 phase를Available로 설정함ClaimRef가 있지만 UID가 비어 있으면 특정 PVC에 예약된 PV로 보고 phase를Available로 설정함- 해당 PVC가 아직 이 PV에 바인딩되지 않은 상태이며, PVC sync가 처리함
claim을 찾지 못한 PV 처리
- PV가 claim에 바인딩되어 있으면 컨트롤러는
ClaimRef의 namespace/name으로 PVC를 찾음 - 캐시에서 PVC를 찾지 못한 경우, 특정 조건에서 추가 확인을 수행함
- informer cache에서 다시 확인
- API 서버에서 다시 확인
- 외부 PV provisioner나 외부 PV binder가 만든 PV에서는 부하가 큰 상황에서 PVC가 아직 로컬 캐시에 동기화되지 않았을 수 있음
- PVC를 잘못 reclaim하지 않기 위해 이중 확인을 수행함
- claim이 없다고 판단되면 volume phase를
Released로 바꾸고reclaimVolume을 실행함- 기존 phase가
Failed이면 덮어쓰지 않음 - reclaim policy가
Retain이면 존재하지 않는 claim을 참조하는 PV라는 로그를 남김
- 기존 phase가
PV와 PVC 연결이 어긋난 경우
- claim이 존재하지만
claim.Spec.VolumeName이 비어 있으면 PVC가 아직 PV 이름을 갖지 않은 상태임 volumeMode가 맞지 않으면 PV와 PVC 양쪽에VolumeMismatch이벤트를 기록하고syncClaim을 건너뜀- mismatch가 아니면 claim을
claimQueue에 추가해syncClaim이 곧 호출되도록 함- 이 방식은 provisioned volume의 바인딩을 빠르게 함
- claim의
Spec.VolumeName이 현재 volume 이름과 같으면 정상 바인딩으로 보고 volume phase를Bound로 업데이트함 - claim이 다른 volume에 바인딩되어 있으면 상황에 따라 처리함
- 동적으로 provisioned된 volume이고 reclaim policy가
Delete이면Released로 표시하고reclaimVolume을 실행함 - controller가 바인딩한 volume이면
unbindVolume로 정리함 - 사용자가 만든 포인터라면 그대로 두되
unbindVolume을 호출해 phase를 업데이트하고ClaimRef.UID를 지움
- 동적으로 provisioned된 volume이고 reclaim policy가
상태 업데이트와 이벤트 발행
updateClaimStatus는 PVC status를 API 서버에 저장함- phase 변경
- volume이 없을 때
AccessModes,Capacity,CurrentVolumeAttributesClassName초기화 - volume이 있을 때 access mode, capacity, current volume attributes class 이름 업데이트
- claim이
Bound가 되는 순간에만 capacity를 갱신하는 조건이 있음- PVC filesystem size와 PV block device size의 차이가 의도적일 수 있어 이미 bound인 claim의 capacity를 덮어쓰지 않음
VolumeAttributesClassfeature gate가 켜져 있으면, pending에서 bound로 바뀌는 동안CurrentVolumeAttributesClassName을 설정함- 이후에는 resizer나 admin override가 다뤄야 하며, controller가 계속 설정하면 race condition 가능성이 있음
updateClaimStatusWithEvent와updateVolumePhaseWithEvent는 실제 status/phase가 바뀐 경우에만 이벤트를 발행함
기본 StorageClass 할당
assignDefaultStorageClass는 claim에 storage class가 없을 때 기본 StorageClass를 찾아 할당함- 이미 storage class가 있는 claim은 무시함
- 기본 class가 없으면 업데이트하지 않고
false를 반환함 - 기본 class가 있으면
claim.Spec.StorageClassName에 class 이름을 설정하고 API 서버에 업데이트함
파일 범위와 명시적 한계
- GitHub 페이지에 표시된 파일 메타데이터 기준으로
pv_controller.go는 2038줄, 1864 LOC, 91 KB임 - 제공된 본문에는 파일 앞부분부터
bindVolumeToClaim함수 시작부까지만 포함되어 있고, 나머지는 raw view 링크로 이어짐 - 따라서 이 요약은 제공된 코드 본문에 드러난 컨트롤러 구조, 설계 주석, 주요 동기화 분기, 상태 업데이트 로직에 한정됨