분기마다 쿠버네티스 마이너 릴리스가 나오면 신기능 구경보다 "우리 클러스터에서 뭐가 깨지나"부터 찾게 된다. 클러스터를 몇 개 굴리다 보니 릴리스 노트에서 정작 오래 붙잡고 읽는 쪽은 하이라이트가 아니라 맨 뒤 폐기 목록이더라. 예전에 정적 Pod를 다룬 글(정적 Pod 소개)을 쓴 적이 있는데, 이번 릴리스에 그 정적 Pod 쪽으로 실제 걸리는 변경이 하나 들어와서 겸사겸사 정리한다.
쿠버네티스 v1.37 코드명 Garhwal(가르왈)이 2026년 8월 26일 공개됐다. 개선사항은 모두 67건이고 Stable 16건, Beta 23건, Alpha 27건, 폐기/제거 1건이다(이 숫자는 추적되는 enhancement 기준 집계이며, 릴리스 노트에 실린 폐기 안내는 이와 별개로 여러 건이다). 운영자가 업그레이드 전에 확인할 것은 SELinux 볼륨 마운트 방식 변경(Stable), Memory QoS의 기본 활성(Beta), 그리고 kube-dns와 kube-proxy ipvs 모드 폐기, 정적 Pod의 Secret/ConfigMap 참조 금지다.
1. 개요와 버전 확인
Garhwal은 인도 우타라칸드주의 히말라야 지역 이름이다. 릴리스 로고에는 설산과 계단식 논, 우타라칸드 주조인 히말라야 모날, 붉은 부란스 꽃이 들어가 있다.
먼저 지금 클러스터가 어느 버전인지부터 확인해보자. 마이너 버전은 건너뛸 수 없으니 1.35에 머물러 있다면 1.36을 거쳐야 한다.
$ kubectl version Client Version: v1.37.0 Kustomize Version: v5.x.y Server Version: v1.36.4 $ kubectl get nodes NAME STATUS ROLES AGE VERSION cp-01 Ready control-plane 214d v1.36.4 worker-01 Ready <none> 214d v1.36.4 worker-02 Ready <none> 214d v1.36.4
패치 버전 숫자는 저장소 상태에 따라 다를 수 있다.
kubeadm 클러스터의 업그레이드 순서는 예전과 같다. 헷갈리기 쉬운 부분은 kubeadm 패키지를 먼저 올리고 kubelet 패키지는 drain 뒤에 올린다는 점이다. 반대로 하면 kubelet이 컨트롤 플레인보다 앞서 나가면서 버전 스큐에 걸린다.
# (1) 첫 컨트롤 플레인 노드에서 kubeadm 패키지를 v1.37.x로 업그레이드
# 아래는 Debian/Ubuntu 예시다. 패키지 명령은 배포판에 맞게 바꿔 쓴다.
$ sudo apt-mark unhold kubeadm && sudo apt-get update \
&& sudo apt-get install -y kubeadm='1.37.x-*' && sudo apt-mark hold kubeadm
$ kubeadm version
# (2) 첫 컨트롤 플레인 노드에서 계획 확인 후 적용
$ sudo kubeadm upgrade plan
$ sudo kubeadm upgrade apply v1.37.x
[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.37.x". Enjoy!
# (3) 첫 컨트롤 플레인 노드도 이어서 (4)~(7)을 수행한다(drain 대상은 cp-01).
# 다른 컨트롤 플레인 노드와 워커 노드는 kubeadm 패키지를 올린 뒤 아래를 실행하고 (4)~(7)을 반복한다.
$ sudo kubeadm upgrade node
# (4) 해당 노드를 비운다
$ kubectl drain worker-01 --ignore-daemonsets --delete-emptydir-data
# (5) kubelet, kubectl 패키지 업그레이드
$ sudo apt-mark unhold kubelet kubectl && sudo apt-get update \
&& sudo apt-get install -y kubelet='1.37.x-*' kubectl='1.37.x-*' && sudo apt-mark hold kubelet kubectl
# (6) kubelet 재시작
$ sudo systemctl daemon-reload && sudo systemctl restart kubelet
# (7) 다시 스케줄 받도록 되돌린다
$ kubectl uncordon worker-01
--delete-emptydir-data는 emptyDir 볼륨에 들어 있던 데이터를 지우고 Pod를 내보낸다는 뜻이다. 캐시가 아니라 지워지면 곤란한 데이터를 emptyDir에 담고 있는 워크로드가 있는지 먼저 확인하자. 각 명령의 옵션과 배포판별 패키지 명령은 공식 문서 kubeadm 업그레이드 페이지에 정리돼 있다.
아래에서는 릴리스팀이 꼽은 하이라이트, Stable로 올라오면서 동작이 바뀌는 항목, 기본값이 켜지는 Beta 항목, 그리고 폐기 목록을 차례로 살펴본다.
2. 릴리스팀이 꼽은 네 가지
watchcache 초기화 방어가 Stable로 완결됐다
watchcache는 kube-apiserver가 etcd 앞에 두는 읽기 캐시다. API 서버가 재기동하면 이 캐시가 비어 있는데, 그 사이 들어온 LIST 요청이 전부 etcd로 그대로 내려가면서 etcd가 무너지는 사고가 종종 있었다.
이 문제를 막는 작업은 두 단계로 진행됐다. 먼저 ResilientWatchCacheInitialization이 1.34에 Stable이 됐고, 이어서 WatchCacheInitializationPostStartHook이 1.36부터 기본 활성으로 돌다가 v1.37에서 Stable로 잠겼다. KEP 4568, SIG API Machinery.
동작은 캐시가 워밍되는 동안 한정된 수의 요청만 etcd로 위임하고 나머지는 HTTP 429로 거절하는 방식이다. 여기서 걸리는 쪽은 API 서버가 아니라 자체 제작 컨트롤러나 오퍼레이터다. 429와 Retry-After를 무시하고 곧장 재시도하는 코드는 이제 눈에 띄게 실패할 수 있다. client-go 기본 rate limiter를 쓰고 있다면 대개 문제없지만, 직접 HTTP 클라이언트를 만들어 쓰는 코드가 있으면 지수 백오프를 넣어야 한다.
HPA scale to zero가 기본 활성 Beta가 됐다
1.16에 처음 소개된 뒤 오래 잠들어 있던 기능인데, v1.37에서 기본 활성 Beta가 됐다(KEP 2021, SIG Autoscaling). 조건이 하나 있다. object 또는 external 메트릭을 쓰는 워크로드만 spec.minReplicas: 0을 설정할 수 있고, CPU나 메모리 메트릭 기반 HPA는 불가하다.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: worker-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: queue-worker
minReplicas: 0
maxReplicas: 20
metrics:
- type: External
external:
metric:
name: queue_messages_ready
target:
type: AverageValue
averageValue: "30"
0으로 내려가면 HPA status에 ScaledToZero 조건이 기록되고, 다시 올라오면 False에 NotScaledToZero 사유가 붙는다. 큐 소비자처럼 놀 때 완전히 비워도 되는 워크로드에 잘 맞는다. 다만 요청이 들어와야만 메트릭이 오르는 구조라면 첫 요청의 지연을 감수해야 한다.
매니페스트 기반 admission 설정
admission webhook과 CEL 정책은 그동안 API 리소스로만 등록할 수 있었다. 다시 말해 etcd가 살아 있어야 정책이 로드된다. v1.37 Beta로 들어온 AdmissionConfiguration의 staticManifestsDir은 이 정책들을 디스크 파일에서 읽는다(KEP 5793).
이렇게 두면 API 서버 기동 시점부터 정책이 적용되고, etcd에 접근할 수 없는 상태에서도 동작한다. API 기반 admission 리소스 자체를 보호하는 정책을 여기에 두는 용도가 제일 그럴듯하다. 디렉터리 파일 변경은 감시해서 자동 리로드하고, 잘못된 갱신이 들어오면 이전 설정을 유지한다.
Pod 단위 checkpoint/restore가 Alpha로 들어왔다
기존 컨테이너 단위 체크포인트를 Pod 단위로 확장했다. CRI에 CheckpointPod와 RestorePod RPC가 추가됐다(KEP 5823, SIG Node). 다만 컨테이너 런타임 쪽에서도 구현해야 실제로 쓸 수 있으니, 당장 뭔가 시도해볼 수 있는 단계는 아니다.
3. Stable 승격 중 동작이 바뀌는 것
Stable 16건 전체 목록은 릴리스 노트에 있고, 여기서는 업그레이드 후 체감이 있는 것만 본다.
SELinux 볼륨 마운트 - 가장 조심할 항목
SELinuxMount와 SELinuxChangePolicy가 Stable이 되면서 기본 활성이다(KEP 1710, SIG Storage). 이제 CSIDriver의 .spec.seLinuxMount: true인 드라이버에 대해 볼륨을 재귀적으로 relabel하지 않고 -o context= 마운트 옵션으로 처리한다. 큰 볼륨에서 파일 하나하나 라벨을 바꾸느라 Pod 기동이 몇 분씩 걸리던 문제를 없애는 변경이다.
부작용이 하나 있다. 같은 노드에서 SELinux 라벨이 서로 다른 Pod들이 같은 볼륨을 공유하면, 마운트 옵션이 충돌해서 나중에 뜨는 Pod가 기동에 실패할 수 있다. 옛 동작이 필요하면 Pod에 명시한다.
apiVersion: v1
kind: Pod
spec:
securityContext:
seLinuxChangePolicy: Recursive
1.38까지는 게이트로 클러스터 전체 비활성도 가능하지만 임시 조치로만 쓰자. SELinux를 쓰지 않는 클러스터(대다수의 Ubuntu/Debian 노드)는 영향이 없다. RHEL 계열에서 enforcing으로 돌리고 있다면 미리 드라이버 설정을 확인해보자.
$ kubectl get csidriver -o custom-columns=NAME:.metadata.name,SELINUXMOUNT:.spec.seLinuxMount NAME SELINUXMOUNT ebs.csi.aws.com true efs.csi.aws.com <none>
metrics.k8s.io가 v1이 됐다
약 9년간 Beta였던 metrics API가 드디어 v1이다(KEP 5207). kubectl top과 HPA가 쓰는 그 API다. v1beta1은 폐기 정책에 따라 전환 기간 동안 유지되므로 metrics-server만 대응 버전으로 올리면 되고, 당장 깨질 일은 없다.
$ kubectl api-versions | grep metrics metrics.k8s.io/v1 metrics.k8s.io/v1beta1
KYAML
KYAML은 쿠버네티스용으로 정의한 YAML 부분집합이다(KEP 5295, SIG CLI). 들여쓰기 실수나 yes, no 같은 값이 예기치 않게 불리언으로 해석되는 YAML의 모호함을 없애려는 목적이고, 모든 KYAML 문서는 그대로 유효한 YAML이다. 1.34 Alpha, 1.35 Beta를 거쳐 v1.37에서 kubectl get -o kyaml이 Stable이 됐다.
$ kubectl get -o kyaml deploy/nginx
출력은 매핑을 { }, 목록을 [ ]로 감싸고 문자열 값에 따옴표를 붙인 형태라, 눈으로 봐도 타입이 분명하다. GitOps 저장소에 커밋할 매니페스트를 뽑아낼 때 쓸 만하다.
DRA 관련 4건
Dynamic Resource Allocation(DRA)은 GPU 같은 특수 장치를 스케줄러가 청구서(ResourceClaim) 단위로 다루는 구조다. v1.37에서 네 건이 Stable로 올라왔다.
- ResourceClaim status(KEP 4817):
.status.devices에 장치 상태를 표준화해 기록한다. 네트워크 디바이스의 IP 같은 정보를 노출할 수 있다. - extended resource를 DRA 드라이버가 처리(KEP 5004):
DeviceClass에 extended resource 이름을 매핑해두면 ResourceClaim 없이abc.example/gpu: 3식으로 기존 방식대로 요청할 수 있다. Pod 매니페스트의 요청 형태는 그대로 둔 채 백엔드만 device plugin에서 DRA 드라이버로 바꿀 때 유용하다. - device taints/tolerations(KEP 5055):
DeviceTaintRule로 문제 있는 장치를 배제한다. - 표준 속성 numaNode(KEP 6072):
resource.kubernetes.io/numaNode속성이 표준화됐다. 별도 게이트가 없다.
Node declared features
Node의 .status.declaredFeatures에 그 노드의 kubelet이 지원하는 기능이 기록된다(KEP 5328). 버전 스큐가 있는 클러스터에서 컨트롤 플레인이 노드별 능력을 판단하는 근거가 된다. 값은 kubelet 기동 시점에 결정되므로, kubelet 설정을 바꿨다면 재시작해야 반영된다.
$ kubectl get node worker-01 -o jsonpath='{.status.declaredFeatures}'
StorageVersionMigration API
storagemigration.k8s.io/v1이 Stable이고 기본 활성이다(KEP 4192). 스토리지 버전을 바꾸거나 etcd 암호화 설정을 바꾼 뒤 기존 리소스를 다시 기록해야 할 때, 그 작업을 선언적 객체로 만들어 맡긴다. 그동안 별도로 돌리던 out-of-tree kube-storage-version-migrator를 대체한다.
Pod Certificates와 ClusterTrustBundles
Pod에 워크로드 아이덴티티용 X.509 인증서를 자동으로 발급하는 구조가 Stable이 됐다(KEP 4317, 3257, SIG Auth). PodCertificateRequest를 감시하는 signer 컨트롤러를 배포해두면, Pod는 podCertificate projected volume으로 자기 인증서를, clusterTrustBundle projected volume으로 신뢰 앵커를 받는다. 사이드카로 인증서를 밀어넣던 구성을 걷어낼 수 있다.
4. Beta 중 기본값이 켜지는 것
Beta는 목록이 길다. 기본 활성으로 바뀌어 업그레이드만 해도 동작이 달라지는 것 위주로 본다.
Memory QoS - 기본 활성
MemoryQoS 게이트가 기본 활성이 됐다(KEP 2570). 켜두면 kubelet이 cgroup v2의 memory.min, memory.low, memory.high를 설정해 메모리 압박 시 워크로드를 보호하거나 한도 전에 스로틀링할 수 있다. 다만 게이트가 켜졌다고 바로 값이 기록되는 것은 아니다. kubelet의 memoryReservationPolicy 기본값은 None이라 memory.min/memory.low를 쓰지 않고, memoryThrottlingFactor도 기본값이 비어 있어 명시하지 않으면 memory.high가 설정되지 않는다.
즉 업그레이드만으로 메모리 동작이 바뀌지는 않는다. 보호나 스로틀링을 실제로 쓰려면 두 설정을 직접 넣어야 하며, 그때는 requests를 실사용보다 낮게 잡은 워크로드가 영향을 받을 수 있으니 지표를 보면서 단계적으로 적용하자. cgroup v2 전용 기능이다.
네이티브 히스토그램 - 기본 활성
네이티브 히스토그램은 버킷 경계를 미리 고정하지 않고 지수 스케일로 자동 생성하는 Prometheus 쪽 히스토그램 형식이다. 버킷 개수를 미리 정하느라 생기던 정밀도와 카디널리티의 절충을 줄인다.
NativeHistograms 게이트(KEP 5808)는 1.36에서는 직접 켜야 했지만 v1.37에서 기본 활성이 됐다. PrometheusProto 형식으로 스크레이프하면 classic 히스토그램과 네이티브 히스토그램이 함께 노출된다.
여기서 점검할 게 하나 있다. 업그레이드만 해도 두 형식이 동시에 나오므로 스크레이프 대상 시계열이 늘어날 수 있다. 수집 측 부하와 저장량을 미리 보고, 필요하면 job별로 어느 쪽을 받을지 정하자. 쿠버네티스 계측 문서 기준으로 수집 자체는 Prometheus 2.40 이상에서 가능하고 3.0 이상을 권하며, Prometheus 쪽 스펙은 job별 scrape_native_histograms 설정과 안정 지원을 3.8부터로 적고 있으니 3.8 이상으로 맞추는 편이 안전하다.
etcd RangeStream과 watch 디코드 병렬화
둘 다 대규모 클러스터의 API 서버 기동 시간을 줄이는 변경이고 kube-apiserver에서 기본 on이다.
EtcdRangeStream(KEP 5966)은 큰 LIST를 스트리밍으로 받는다. etcd 3.7 이상이 필요하고, 구버전 etcd에는 자동으로 기존 unary Range로 폴백하므로 업그레이드가 깨지지는 않는다. etcdctl get --stream으로도 확인할 수 있다.
ConcurrentWatchObjectDecode(KEP 6178)는 1.31부터 Beta였는데 v1.37에서 기본 on이 됐다. 워커 10개로 디코드를 병렬화하고 순서는 보존한다. 릴리스 블로그의 KEP 6178(Concurrent watch object decode) 절에 실린 벤치마크로는 Pod 15만 개 환경에서 캐시 초기화 시간이 약 40% 줄고, RangeStream을 함께 쓰면 약 55%까지 줄어든다.
여기서 한 번 걸릴 수 있는 지점이 CRD 변환 웹훅이다. 디코드가 병렬로 돌면서 동시에 10건의 변환 요청이 몰릴 수 있으니, 웹훅 replica 수와 타임아웃을 미리 보자.
Gang scheduling
Workload API와 PodGroup을 통한 all-or-nothing 스케줄링이 Beta다(KEP 4671). 분산 학습처럼 Pod 전부가 떠야 의미 있는 작업에서 부분 배치 후 교착이 생기던 문제를 겨냥한다. v1.37에는 workload-aware preemption(KEP 5710)과 PodGroup 큐잉이 추가되어 livelock이 해소됐다. DRA 쪽으로는 워크로드 단위 ResourceClaim(KEP 5729)이 함께 Beta다.
PVC 미사용 시각 기록
PersistentVolumeClaimUnusedSinceTime이 기본 활성이다(KEP 5541). PVC에 Unused condition이 붙고 사유는 NoPodsUsingPVC 또는 PodUsingPVC, lastTransitionTime이 미사용이 시작된 시각이다. 쓰지 않는 볼륨을 찾는 데 쓰라는 것이고, 삭제 여부는 관리자가 판단한다. 자동으로 지워주지는 않는다.
기본 off라 별도로 켜야 하는 Beta
- cAdvisor 없는 CRI stats(KEP 2371,
PodAndContainerStatsFromCRI): Beta지만 기본 off다. - Pod 단위 리소스 매니저(KEP 5526,
PodLevelResourceManagers): 기본 off. - watch 기반 route 컨트롤러(KEP 5237,
CloudControllerManagerWatchBasedRoutesReconciliation): 기본 off. 기존 10초 주기 재조정을 대체하고,route_sync_total메트릭에 trigger와 outcome 라벨이 붙는다.
스토리지 스케줄링 관련 두 건
다음 두 건은 공식 feature gate 문서 기준으로 v1.37에서 Beta가 되면서 게이트가 기본 활성(true)으로 바뀌었다. 단 실제 동작은 CSIDriver 쪽 설정을 넣어야 켜지므로 게이트만으로 스케줄링이 달라지지는 않는다.
- Storage capacity scoring(KEP 4049,
StorageCapacityScoring): 폐기된VolumeCapacityPriority를 통합했다.VolumeBindingArgs의 Shape 기본값이 "최대 allocatable 선호"이고, CSIDriver에StorageCapacity: true가 필요하다. - CSI attach limit과 Cluster Autoscaler 연동(KEP 5030,
VolumeLimitScaling): CSIDriver에PreventPodSchedulingIfMissing필드가 생겼다. Karpenter처럼 CSI를 인지하지 않는 오토스케일러는 영향이 없다.
5. Alpha에서 눈여겨볼 것
Alpha는 운영 클러스터에 켜는 게 아니니 이름만 적어둔다.
- StatefulSet Recreate 전략(KEP 3541,
StatefulSetRecreateStrategy) - DRA node allocatable(KEP 5517), CEL 기반 파생 속성(KEP 6080), device compatibility groups(KEP 5963 - MIG와 vGPU를 동시에 쓸 수 없는 조합 같은 걸 스케줄 전에 차단한다)
- in-place resize용 스케줄러 선점(KEP 5836,
InPlacePodVerticalScalingSchedulerPreemption) - 메모리 기반 emptyDir의 sizeLimit 동적 조정(KEP 6030,
InPlacePodVerticalScalingMemoryBackedVolumes,/resize서브리소스) - Node 라이프사이클 조건(KEP 5683):
DrainInProgress,Drained,MaintenancePlanned,MaintenanceInProgress,GracefulNodeShutdownInProgress. 노드 유지보수 자동화를 직접 만들어본 적이 있다면 반가운 항목이다. - Job의 spec.scheduling 필드(KEP 5547): 기본
Basic,Gang선택 시 minCount가 parallelism이 된다. - nftables kube-proxy의 localhost NodePort 지원(KEP 6032):
--nodeport-addresses에 loopback을 포함하면 유저스페이스 프록시가 활성화된다. - CompositePodGroup API(KEP 6012), WAS 컨트롤러 API 프레임워크(KEP 6089)
그 밖에 1.36의 버그(kubernetes#137409)를 고치고 StatefulSet maxUnavailable이 다시 기본 활성이 됐고, kube-proxy nftables 모드가 nft CLI를 실행하는 대신 커널 netlink를 직접 쓰도록 바뀌었다. client-go의 컨텍스트 로깅 전환도 마무리됐다.
6. 폐기와 제거 - 업그레이드 전 확인 목록
kube-dns 폐기
CoreDNS가 1.13부터 기본이었으니 놀랄 일은 아니지만, 이번에 kube-dns가 공식 폐기됐다. kube-dns는 EndpointSlices와 dual-stack을 지원하지 않는다. 1.40 이후로는 새 패키지 빌드가 나오지 않을 것으로 예상된다. node-local-dns는 별도 저장소로 분리되어 계속 유지된다.
$ kubectl -n kube-system get deploy NAME READY UP-TO-DATE AVAILABLE AGE coredns 2/2 2 2 214d
여기에 kube-dns Deployment가 보이면 CoreDNS로 옮기자. Service 이름이 kube-dns인 것은 정상이니 헷갈리지 말자. CoreDNS를 쓰는 클러스터도 Service 이름은 호환을 위해 kube-dns다.
kube-proxy ipvs 모드 폐기
이번 릴리스에서 운영자가 제일 신경 쓸 항목이다. KEP 5495 "Deprecate ipvs mode in kube-proxy"에 따라 ipvs 모드는 v1.35에서 폐기가 선언됐다. v1.37에서 달라진 것은 ipvs 모드로 기동할 때 폐기 경고 로그가 찍히는 것과 KubeProxyIPVS 피처 게이트(기본 true)가 생긴 것으로, 앞으로 이 게이트를 끄는 방식으로 기본 비활성이 진행된다. 자주 인용되는 "The ipvs mode of kube-proxy will not save us"라는 문장은 이 KEP이 아니라 nftables 프록시를 다룬 KEP 3866의 절 제목이니 출처를 섞지 말자.
일정은 1.40 무렵 기본 비활성(게이트로 선택 가능), 1.43 무렵 완전 제거가 예정되어 있다.
$ kubectl -n kube-system get configmap kube-proxy -o jsonpath='{.data.config\.conf}' | grep 'mode:'
mode: "ipvs"
ipvs가 나오면 이전 계획을 세워야 한다. 갈 곳은 nftables다. iptables 모드는 v1.37에서 여전히 기본값이지만 앞으로 nftables로 기본값이 바뀔 예정이므로, 어느 쪽을 쓰든 kube-proxy 설정에 모드를 명시해두자. 업그레이드 중에 백엔드가 조용히 바뀌는 게 제일 곤란하다.
정적 Pod의 Secret/ConfigMap 참조 금지
정적 Pod는 API 서버 없이 kubelet이 디스크의 매니페스트를 읽어 직접 띄우는 Pod다. kube-apiserver나 etcd 자신을 이 방식으로 띄우는 kubeadm 구성이 대표적이다. 자세한 내용은 예전에 쓴 정적 Pod 소개 글을 참고하자.
정적 Pod는 API 객체를 참조할 수 없는데도 ConfigMap이나 Secret을 env나 볼륨으로 물고 있는 매니페스트가 조용히 남아 있는 경우가 있었다. v1.37부터 이 참조가 엄격히 금지되고, 옵트아웃 게이트였던 PreventStaticPodAPIReferences도 제거됐다(kubernetes#140226). 거부 대상은 env/envFrom의 ConfigMap, Secret 참조뿐 아니라 serviceAccountName, ConfigMap/Secret 볼륨, projected 볼륨, PersistentVolumeClaim, ResourceClaim 참조까지 API 객체를 가리키는 것 전부다. 정적 Pod에 허용되는 볼륨은 hostPath와 emptyDir이다. 해당 참조가 남아 있으면 kubelet이 그 정적 Pod를 거절하므로, kube-apiserver나 etcd 매니페스트에 걸리면 컨트롤 플레인이 올라오지 않는다.
$ sudo grep -rnE 'configMap|secret|serviceAccountName|persistentVolumeClaim|resourceClaim|projected|clusterTrustBundle' /etc/kubernetes/manifests/
업그레이드 전에 정적 Pod를 두는 모든 노드에서 한 번 돌려보자. 키워드가 넓어서 주석 같은 오탐도 나오지만 놓치는 것보다 낫다. 값을 인라인 env로 옮기거나 hostPath 볼륨으로 노드의 파일을 읽는 방식으로 바꿔야 한다.
cgroup v1
1.35부터 failCgroupV1이 기본 true라 cgroup v1 노드에서는 kubelet이 아예 기동하지 않는다. 아직 v1 노드가 남아 있다면 v1.37 업그레이드 전에 확인하자.
$ stat -fc %T /sys/fs/cgroup/ cgroup2fs
tmpfs가 나오면 cgroup v1이다. KubeletConfiguration에 failCgroupV1: false를 넣으면 임시로 넘길 수 있지만 이 옵션도 제거 예정이다(KEP 5573). Memory QoS 같은 신기능은 cgroup v2 전용이니 노드 OS를 올리는 쪽이 맞다.
kubectl run --filename
kubectl run의 --filename, -f 옵션이 폐기 예정이다(kubernetes#138671). 이 플래그는 원래 무시되던 옵션이라 붙어 있어도 동작에는 차이가 없었다. CI 스크립트에 남아 있으면 플래그만 지우면 되고, 파일로 리소스를 만들 의도였다면 별도로 kubectl apply -f를 쓰는 것이 맞다.
7. 업그레이드 전 점검 명령 모음
스테이징에서 한 번에 돌려볼 만한 것들을 모았다.
# 1. 현재 버전과 스큐
kubectl get nodes -o wide
# 2. kube-proxy 모드
kubectl -n kube-system get configmap kube-proxy -o jsonpath='{.data.config\.conf}' | grep 'mode:'
# 3. DNS
kubectl -n kube-system get deploy | grep -E 'coredns|kube-dns'
# 4. cgroup 버전 (각 노드에서)
stat -fc %T /sys/fs/cgroup/
# 5. 정적 Pod의 API 참조 (정적 Pod가 있는 모든 노드에서, env와 볼륨 모두)
sudo grep -rnE 'configMap|secret|serviceAccountName|persistentVolumeClaim|resourceClaim|projected' /etc/kubernetes/manifests/
# 6. etcd 버전 (RangeStream은 3.7+)
kubectl -n kube-system get pod -l component=etcd -o jsonpath='{.items[*].spec.containers[*].image}'
# 7. SELinux 사용 여부와 CSI 드라이버 설정
getenforce
kubectl get csidriver -o custom-columns=NAME:.metadata.name,SELINUXMOUNT:.spec.seLinuxMount
# 8. CRD 변환 웹훅 존재 여부 (watch 디코드 병렬화 영향)
kubectl get crd -o custom-columns=NAME:.metadata.name,CONVERSION:.spec.conversion.strategy | grep -c Webhook
8. 정리
v1.37은 대규모 클러스터의 API 서버 안정성(watchcache 방어, RangeStream, 디코드 병렬화)과 DRA 성숙에 무게가 실린 릴리스다. 일반적인 규모의 클러스터를 운영한다면 신기능보다 폐기 목록이 실질적이다.
순서대로 하자면 kube-proxy가 ipvs 모드인지 먼저 확인하고, 정적 Pod 매니페스트에서 Secret/ConfigMap 참조를 걷어내고, SELinux enforcing 클러스터라면 CSI 드라이버의 seLinuxMount 설정과 볼륨 공유 구성을 점검한다. 그다음 스테이징에서 네이티브 히스토그램으로 늘어난 스크레이프량과 디코드 병렬화가 CRD 변환 웹훅에 주는 부하를 며칠 지켜보고 프로덕션에 올리자.
바이너리는 kubernetes.io 다운로드 페이지나 GitHub 릴리스에서 받을 수 있다. 업그레이드 절차의 배포판별 명령, 개별 KEP의 상세 동작과 게이트 기본값은 공식 문서와 릴리스 노트를 확인하자.