<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Kubernetes V1.37 on jyukki's Blog</title><link>https://jyukki.com/tags/kubernetes-v1.37/</link><description>Recent content in Kubernetes V1.37 on jyukki's Blog</description><generator>Hugo -- 0.147.0</generator><language>ko-KR</language><lastBuildDate>Mon, 14 Sep 2026 10:06:00 +0900</lastBuildDate><atom:link href="https://jyukki.com/tags/kubernetes-v1.37/index.xml" rel="self" type="application/rss+xml"/><item><title>2026 개발 트렌드: Kubernetes v1.37 In-Place Resize Preemption, 무중단 확장보다 퇴출 예산을 먼저 설계하자</title><link>https://jyukki.com/posts/2026-09-14-kubernetes-inplace-resize-preemption-budget-trend/</link><pubDate>Mon, 14 Sep 2026 10:06:00 +0900</pubDate><guid>https://jyukki.com/posts/2026-09-14-kubernetes-inplace-resize-preemption-budget-trend/</guid><description>Kubernetes v1.37의 in-place Pod resize scheduler preemption Alpha를 계기로, restart 없는 scale-up을 목표로 삼지 않고 어떤 workload를 언제 퇴출해도 되는지 priority·PDB·capacity·rollback 예산으로 설계하는 기준을 정리합니다.</description><content:encoded><![CDATA[<p>Pod의 CPU나 memory request가 부족해졌을 때 기존 선택지는 불편했다. Pod를 재생성해 더 큰 노드에 배치하거나, 노드에 여유가 생길 때까지 기다리거나, VPA 권고를 사람이 해석해 rollout했다. Kubernetes의 in-place Pod resize는 실행 중인 container 자원을 조정할 수 있게 하면서 이 간극을 줄였다. 하지만 노드의 allocatable headroom이 부족하면 kubelet은 resize를 <code>Deferred</code>로 남긴다. Pod는 살아 있지만 필요한 자원을 얻지 못한 채 기다린다.</p>
<p>Kubernetes v1.37은 이 deferred 상태를 위한 <strong>scheduler preemption for in-place Pod resize</strong>를 Alpha로 추가했다. <code>InPlacePodVerticalScalingSchedulerPreemption</code> feature gate를 켜면 scheduler가 높은 우선순위 Pod의 resize를 만족시키기 위해 같은 node의 낮은 우선순위 Pod를 preempt할 수 있다. 공식 발표의 핵심도 “resize가 자동으로 안전해졌다”가 아니라, 정적 배치 이후 생기는 자원 부족을 scheduler가 해소할 수 있는 경로가 열렸다는 것이다.</p>
<p>이 글은 <a href="/learning/deep-dive/deep-dive-kubernetes-rollouts/">Kubernetes Rollout 전략</a>, <a href="/learning/deep-dive/deep-dive-capacity-planning-littles-law-saturation/">Capacity Planning·Little&rsquo;s Law·Saturation</a>, <a href="/learning/deep-dive/deep-dive-priority-load-shedding-bulkhead/">Priority Load Shedding·Bulkhead</a>, <a href="/posts/2026-09-11-kubernetes-node-lifecycle-conditions-trend/">Node Lifecycle Conditions</a>를 resize 관점으로 연결한다. 중요한 질문은 “API Pod가 restart 없이 커질 수 있는가”가 아니라, <strong>어느 workload를 얼마나 자주, 어떤 복구 증거가 있을 때 중단시켜도 되는가</strong>다.</p>
<p>공식 자료는 <a href="https://kubernetes.io/blog/2026/09/10/kubernetes-v1-37-scheduler-preemption-for-in-place-pod-resize-alpha/">Kubernetes v1.37의 Scheduler Preemption for In-Place Pod Resize 발표</a>, <a href="https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/">container resource resize 문서</a>, <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/">Pod Priority and Preemption 문서</a>, <a href="https://kubernetes.io/docs/tasks/run-application/configure-pdb/">PodDisruptionBudget 문서</a>를 기준으로 확인했다. Alpha 기능의 세부 동작과 feature gate는 이후 release에서 달라질 수 있으므로, 문서의 example을 production 정책으로 복사하지 않는다.</p>
<h2 id="이-글에서-얻는-것">이 글에서 얻는 것</h2>
<ul>
<li><code>ResizeDeferred</code>가 의미하는 node headroom 문제와, v1.37이 scheduler preemption으로 메우려는 범위를 이해합니다.</li>
<li>resize를 받는 high-priority Pod와 preempt되는 low-priority Pod의 가용성 비용을 분리해서 계산합니다.</li>
<li>PriorityClass, PDB, termination grace, queue lease, checkpoint를 하나의 <strong>퇴출 예산(eviction budget)</strong> 으로 묶는 기준을 얻습니다.</li>
<li>staging → 단일 pool → 제한 canary로 승격하기 위한 지표와 rollback 조건을 숫자로 정합니다.</li>
</ul>
<h2 id="핵심-개념이슈">핵심 개념/이슈</h2>
<h3 id="1-deferred-resize는-더-큰-request가-아니라-node-local-capacity-문제다">1) Deferred resize는 &ldquo;더 큰 request&quot;가 아니라 node-local capacity 문제다</h3>
<p>실행 중인 Pod의 resource request를 늘리면 kubelet은 해당 node에 추가 allocation을 수용할 여유가 있는지 본다. 여유가 없으면 resize 요청은 <code>ResizeDeferred</code> 상태가 될 수 있다. 이 상태가 곧 애플리케이션이 실패했다는 뜻은 아니지만, latency가 이미 악화됐거나 OOM 위험을 줄이려는 resize라면 기다리는 시간 자체가 SLO 비용이다.</p>
<p>v1.37의 Alpha 기능은 scheduler가 이 신호를 보고 lower-priority Pod를 preempt해 공간을 만든 뒤, deferred resize가 진행되도록 한다. 이것은 새 Pod를 우선 배치할 때의 preemption과 비슷해 보이지만 이미 실행 중인 workload가 resource를 키우는 상황이라는 점이 다르다. high-priority Pod에는 restart 없는 resize일 수 있어도 low-priority Pod에는 명시적인 중단·재시작·재처리 비용이 생긴다.</p>
<p>따라서 용량 문제를 다음처럼 나눠 관찰해야 한다.</p>
<table>
  <thead>
      <tr>
          <th>관측값</th>
          <th>해석</th>
          <th>먼저 확인할 것</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>ResizeDeferred</code> 횟수</td>
          <td>해당 node의 resize headroom 부족</td>
          <td>request 증가량, node allocatable, 기존 reserved request</td>
      </tr>
      <tr>
          <td>deferred 지속 시간</td>
          <td>scheduler/용량이 부족한 시간</td>
          <td>scale-out 가능 여부, autoscaler delay, victim 후보</td>
      </tr>
      <tr>
          <td>preemption 횟수</td>
          <td>다른 workload에 전가된 가용성 비용</td>
          <td>victim class, PDB, retry/중복 처리 결과</td>
      </tr>
      <tr>
          <td>resize 완료 뒤 p99/오류율</td>
          <td>resize가 실제로 SLO를 회복했는지</td>
          <td>application bottleneck이 CPU/memory였는지</td>
      </tr>
  </tbody>
</table>
<p>CPU utilization 40%라는 평균만 보고 “여유가 있다”고 결론 내리면 안 된다. scheduler는 실제 request와 node-local placement를 본다. 한 node에 특정 request가 몰렸거나 fragmentation이 크면 평균 사용률과 상관없이 deferred가 발생한다.</p>
<h3 id="2-priorityclass는-권리의-순서이지-희생의-안전-보증이-아니다">2) PriorityClass는 권리의 순서이지 희생의 안전 보증이 아니다</h3>
<p>PriorityClass는 scheduler에게 상대적 중요도를 알린다. 하지만 <code>priority=1000</code>인 Pod가 <code>priority=100</code>인 Pod를 밀어낼 수 있다는 사실은 낮은 priority Pod가 안전하게 종료된다는 증명이 아니다. preemption 후보에는 최소한 아래 다섯 사실이 있어야 한다.</p>
<ol>
<li>작업이 멈춰도 고객 데이터나 외부 부작용이 중복되지 않는다.</li>
<li><code>terminationGracePeriodSeconds</code> 안에 checkpoint, offset commit, lease release 또는 safe retry가 끝난다.</li>
<li>PDB가 보호해야 할 replica 수와 충돌하지 않는다.</li>
<li>다시 배치될 node/pool 또는 queue 처리 여력이 있다.</li>
<li>owner가 event와 job outcome을 보고 복구 여부를 판단할 수 있다.</li>
</ol>
<p>예를 들어 image thumbnail 생성 worker는 object key 기반 멱등성이 있고 결과를 overwrite할 수 있다면 좋은 초기 후보다. 반면 결제 capture worker, DB primary, migration job, in-memory session owner는 낮은 priority여도 자동 preemption 후보가 아니다. 비용을 줄이려고 priority를 낮게 준 것이 “강제 종료 승인”으로 해석되면 안 된다.</p>
<h3 id="3-pdb는-모든-중단을-막는-안전망이-아니다">3) PDB는 모든 중단을 막는 안전망이 아니다</h3>
<p>PodDisruptionBudget은 voluntary disruption에 대한 availability budget을 표현한다. scheduler preemption, node failure, application crash처럼 모든 경로에 동일하게 적용된다고 가정하면 위험하다. 기능을 켜기 전에 cluster 버전과 실제 preemption 흐름에서 PDB가 어떻게 관찰되는지 staging으로 검증하고, PDB만으로 stateful workload의 안전을 선언하지 않는다.</p>
<p>특히 <code>minAvailable</code>이나 <code>maxUnavailable</code> 값을 정할 때 replica 수만 세면 부족하다. traffic drain, readiness recovery, cache warm-up, leader election, downstream rate limit까지 포함한 <strong>실제 회복 시간</strong>을 budget에 넣어야 한다. 3 replica API에서 한 Pod가 종료된 뒤 readiness까지 p95 75초가 걸린다면, 30초마다 resize preemption이 반복되는 정책은 replicas가 3개여도 서비스에 압박을 준다.</p>
<h3 id="4-feature-gate-일관성은-최소-조건이다">4) feature gate 일관성은 최소 조건이다</h3>
<p>공식 안내에 따르면 이 기능은 Kubernetes v1.37 이상과 함께 control plane 구성요소(<code>kube-apiserver</code>, <code>kube-scheduler</code>) 및 kubelet에 feature gate를 켜야 한다. 일부 node만 다르거나 rollout 중 설정이 섞이면 resize가 특정 node에서만 다르게 보일 수 있다. Gate가 enabled라는 것은 scheduler action이 가능하다는 뜻이지, workload policy가 준비됐다는 뜻이 아니다.</p>
<p>기술 검증과 운영 승인 기준을 분리한다.</p>
<table>
  <thead>
      <tr>
          <th>층위</th>
          <th>통과 질문</th>
          <th>통과해도 아직 하지 말 일</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>기능성</td>
          <td>Deferred 뒤 scheduler event와 resize complete가 보이는가</td>
          <td>모든 namespace에 gate 적용</td>
      </tr>
      <tr>
          <td>안전성</td>
          <td>victim이 idempotent하게 재시작되고 PDB/SLO 위반이 없는가</td>
          <td>stateful/결제 workload 포함</td>
      </tr>
      <tr>
          <td>효과</td>
          <td>high-priority p99와 error budget이 실제 개선되는가</td>
          <td>capacity 부족을 preemption으로만 해결</td>
      </tr>
      <tr>
          <td>운영성</td>
          <td>on-call이 victim·reason·rollback을 10분 안에 판단하는가</td>
          <td>수동 승인 없는 전면 자동화</td>
      </tr>
  </tbody>
</table>
<h2 id="실무-적용">실무 적용</h2>
<h3 id="1-resize와-퇴출을-같은-capacity-budget으로-모델링한다">1) resize와 퇴출을 같은 capacity budget으로 모델링한다</h3>
<p>resize request를 추가 CPU·memory, victim을 해제 가능한 request, node에 남겨야 할 headroom으로 표현한다. 예를 들어 node allocatable이 8 CPU이고, high-priority Pod가 4→6 CPU로 +2 CPU resize를 원하며 현재 headroom이 1 CPU라면 scheduler는 최소 1 CPU 이상의 victim request를 찾아야 한다. 그러나 여기서 “1 CPU짜리 아무 Pod”가 답은 아니다.</p>
<p>다음처럼 정책을 문서로 만든다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>high-priority resize 허용: API tier-0, request delta &lt;= 1 CPU 또는 2 GiB
</span></span><span style="display:flex;"><span>victim 후보: batch-low, checkpoint 완료 &lt;= 20초, external side effect 없음
</span></span><span style="display:flex;"><span>보호 대상: database, payment, migration, default-priority API
</span></span><span style="display:flex;"><span>node reserve: allocatable의 15% 또는 1 CPU 중 큰 값
</span></span><span style="display:flex;"><span>자동 preemption 상한: node당 1회 / 30분, pool당 3회 / 1시간
</span></span></code></pre></div><p>수치는 예시이며 workload의 recovery p95와 traffic pattern으로 조정해야 한다. 중요한 것은 <code>PriorityClass</code>만 남기지 않고 “resize request의 상한”, “희생 가능 작업”, “남겨 둘 여유”, “반복 횟수”를 한 정책에서 연결하는 점이다. preemption이 잦다면 feature가 잘 작동하는 것이 아니라 request 산정, node packing, HPA/VPA, pool isolation 중 하나가 틀렸다는 신호로 본다.</p>
<h3 id="2-먼저-preemption-없이-deferred를-관찰한다">2) 먼저 preemption 없이 deferred를 관찰한다</h3>
<p>production gate를 바로 활성화하지 말고 staging에서 high/low PriorityClass, 의도적으로 부족한 node headroom, resize subresource RBAC를 만든다. high-priority Pod의 request를 올려 <code>ResizeDeferred</code> event를 확인하고, 다음 이벤트 순서와 실제 cgroup allocation을 기록한다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>ResizeDeferred
</span></span><span style="display:flex;"><span>  -&gt; scheduler가 victim 선택 또는 보류한 근거
</span></span><span style="display:flex;"><span>  -&gt; victim Preempted / termination event
</span></span><span style="display:flex;"><span>  -&gt; ResizeStarted
</span></span><span style="display:flex;"><span>  -&gt; ResizeCompleted 또는 다시 Deferred
</span></span></code></pre></div><p>그 다음 production에서는 gate 없이도 deferred 시간, candidate victim, autoscaler action, API SLO를 dashboard로 모은다. 2주 데이터를 보면 resize preemption이 실제로 필요한 희귀 spike인지, 예약 headroom과 node pool 분리로 더 싸게 해결할 구조적 부족인지 구분할 수 있다.</p>
<h3 id="3-단일-pool에서-48시간-제한-canary를-실행한다">3) 단일 pool에서 48시간 제한 canary를 실행한다</h3>
<p>canary는 high-priority API 하나와 재시작 가능한 batch/consumer 하나가 <strong>전용 node pool</strong>에 있는 경우로 제한한다. customer-facing API와 DB, migration, baseline web workload가 섞인 일반 pool은 첫 대상이 아니다. 48시간 동안 다음 기준을 동시에 통과해야 다음 대상으로 넓힌다.</p>
<table>
  <thead>
      <tr>
          <th>지표</th>
          <th>승격 기준</th>
          <th>즉시 중단/rollback 기준</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>의도하지 않은 victim</td>
          <td>0건</td>
          <td>보호 대상 또는 default priority가 1건이라도 victim</td>
      </tr>
      <tr>
          <td>PDB/SLO 위반</td>
          <td>0건</td>
          <td>availability SLO breach 또는 PDB 관련 unexpected event</td>
      </tr>
      <tr>
          <td>resize 완료 시간 p95</td>
          <td>2분 이하 또는 기존 대비 50% 개선</td>
          <td>10분 초과 deferred가 2회</td>
      </tr>
      <tr>
          <td>victim 복구 p95</td>
          <td>checkpoint 포함 2분 이하</td>
          <td>duplicate side effect, data repair 필요</td>
      </tr>
      <tr>
          <td>preemption 빈도</td>
          <td>pool당 3회/시간 미만</td>
          <td>5회/시간이 15분 지속</td>
      </tr>
      <tr>
          <td>high-priority p99</td>
          <td>baseline 대비 악화 없음</td>
          <td>+10%가 10분 지속</td>
      </tr>
  </tbody>
</table>
<p>“preemption 0건”도 성공적인 canary 결과일 수 있다. 실제로는 scheduler가 불필요하게 행동하지 않았고, headroom 또는 autoscaler가 충분했다는 뜻일 수 있다. 이 경우 feature를 더 넓히는 대신 deferred 원인이 node fragmentation인지 request inflation인지 점검하는 편이 좋다.</p>
<h3 id="4-event와-ownership을-runbook에-넣는다">4) event와 ownership을 runbook에 넣는다</h3>
<p>on-call이 high-priority Pod만 보고 “resize가 성공했다”고 끝내면 victim의 backfill·duplicate·queue lag를 놓친다. alert에는 최소한 resize 대상, request delta, node, victim list, PriorityClass, PDB 상태, queue lag, rollback flag가 함께 있어야 한다.</p>
<p>우선순위는 다음처럼 고정할 수 있다.</p>
<ol>
<li>data integrity와 customer action의 중복 여부를 확인한다.</li>
<li>victim workload의 recovery/queue lag가 SLO 안인지 확인한다.</li>
<li>high-priority Pod의 allocation과 p99가 회복됐는지 확인한다.</li>
<li>같은 node/pool에서 재발하면 preemption을 끄고 capacity·autoscaling·packing을 조사한다.</li>
</ol>
<p>이 순서는 high-priority API의 latency가 중요하지 않아서가 아니다. preemption으로 만든 숨은 부채가 늦게 나타나도 되돌릴 수 있도록, 먼저 irreversible side effect를 보는 것이다.</p>
<h2 id="트레이드오프주의점">트레이드오프/주의점</h2>
<h3 id="무중단이라는-단어가-비용을-가린다">&ldquo;무중단&quot;이라는 단어가 비용을 가린다</h3>
<p>in-place resize는 high-priority Pod의 restart를 피할 수 있다. 그러나 그 자원을 만들기 위해 victim이 종료된다면 cluster 전체 관점에서는 가용성 비용을 옮긴 것뿐이다. batch가 안전하게 재시작되는지, queue가 중복을 흡수하는지, downstream가 burst를 견디는지를 증명하지 못하면 무중단이라는 표현을 운영 결론으로 쓰지 않는다.</p>
<h3 id="preemption은-capacity-planning의-대체품이-아니다">preemption은 capacity planning의 대체품이 아니다</h3>
<p>지속적으로 deferred가 발생하는 pool은 피크 capacity가 부족하거나 request가 실제 사용량과 맞지 않거나, heterogeneous workload가 한 node에 섞였을 가능성이 크다. 이 경우 preemption을 자주 허용하면 high-priority 부하는 빨라질 수 있어도 batch starvation, queue lag, noisy-neighbor 문제가 늘어난다. headroom reservation, node pool 분리, HPA/VPA tuning, cluster autoscaler latency를 먼저 비교해야 한다.</p>
<h3 id="alpha-gate는-전체-제어면을-바꾼다">Alpha gate는 전체 제어면을 바꾼다</h3>
<p>기능이 Alpha이고 feature gate가 여러 component에 걸친다는 점도 중요하다. upgrade·rollback 시 gate 조합, node 교체, scheduler event schema, autoscaler와의 상호작용을 실제 버전에서 확인해야 한다. feature gate를 켠 상태에서 policy 오류가 나면 단순 application rollback으로 scheduler behavior가 사라지지 않을 수 있으므로, gate off 절차와 node rollout 영향을 runbook에 함께 둔다.</p>
<h2 id="체크리스트">체크리스트</h2>
<ul>
<li><input disabled="" type="checkbox"> v1.37 이상 control plane과 모든 대상 kubelet의 version/gate 조합을 inventory로 확인했는가?</li>
<li><input disabled="" type="checkbox"> <code>ResizeDeferred</code>, <code>ResizeStarted</code>, <code>ResizeCompleted</code>, <code>Preempted</code> event를 workload·node·PriorityClass별로 조회할 수 있는가?</li>
<li><input disabled="" type="checkbox"> resize 대상의 최대 delta와 node reserve를 숫자로 정했는가?</li>
<li><input disabled="" type="checkbox"> victim 후보가 idempotency, checkpoint/safe retry, termination 시간, queue lease 검증을 통과했는가?</li>
<li><input disabled="" type="checkbox"> PriorityClass 기본값을 암묵적으로 사용하는 Pod와 stateful·결제·migration workload를 자동 victim에서 제외했는가?</li>
<li><input disabled="" type="checkbox"> PDB를 실제 preemption 흐름과 cluster version에서 검증했으며, PDB만을 data integrity 보증으로 쓰지 않는가?</li>
<li><input disabled="" type="checkbox"> 1개 pool·1~2 deployment·48시간 canary의 preemption 상한과 abort 조건을 정했는가?</li>
<li><input disabled="" type="checkbox"> gate off, victim scale-out, high-priority resize 취소의 rollback owner와 10분 이내 판단 기준이 문서화됐는가?</li>
</ul>
<h3 id="연습-restart-없는-확장과-안전한-중단을-함께-증명하기">연습: restart 없는 확장과 안전한 중단을 함께 증명하기</h3>
<ol>
<li>constrained staging node에 4 CPU high-priority Pod와 3 CPU low-priority batch Pod를 배치해 1 CPU headroom을 남긴다.</li>
<li>high-priority Pod를 6 CPU로 resize해 <code>ResizeDeferred</code>가 발생하는지 확인한다.</li>
<li>feature gate를 시험 환경에만 켠 뒤, low-priority Pod가 preempt됐을 때 checkpoint·queue re-delivery·idempotency key가 중복 side effect 없이 복구되는지 검사한다.</li>
<li>high-priority Pod의 <code>allocatedResources</code>, p99, error rate와 low-priority job의 lag·완료 수를 같은 시간축에 놓는다.</li>
<li>마지막으로 victim을 보호 대상(PDB가 있는 stateful Pod 또는 non-idempotent worker)으로 바꾼 fixture에서 automation을 허용하지 않고 alert/abort로 끝나는지 테스트한다.</li>
</ol>
]]></content:encoded></item><item><title>2026 개발 트렌드: Kubernetes v1.37 Node Lifecycle Conditions, 노드 정비를 자동화 전에 계약으로 만들기</title><link>https://jyukki.com/posts/2026-09-11-kubernetes-node-lifecycle-conditions-trend/</link><pubDate>Fri, 11 Sep 2026 10:07:00 +0900</pubDate><guid>https://jyukki.com/posts/2026-09-11-kubernetes-node-lifecycle-conditions-trend/</guid><description>Kubernetes v1.37의 Node Lifecycle Conditions를 계기로 drain·maintenance·graceful shutdown 상태를 공통 계약으로 다루는 방법과, Alpha 신호를 자동 eviction 정책으로 오해하지 않는 운영 기준을 정리합니다.</description><content:encoded><![CDATA[<p>Kubernetes에서 node 정비는 보통 여러 신호로 흩어진다. 운영자는 <code>kubectl cordon</code>으로 새 Pod 배치를 막고, <code>drain</code>으로 기존 Pod를 옮기며, taint나 PodDisruptionBudget으로 예외를 다룬다. load balancer는 별도의 health check로 traffic을 뺀다. 이 절차 자체는 맞지만 &ldquo;이 node가 왜 비어 가는가&rdquo;, &ldquo;언제 정비가 시작되는가&rdquo;, &ldquo;drain이 끝났는가&quot;는 label·event·티켓·채팅에 따로 남기기 쉽다.</p>
<p>Kubernetes v1.37이 도입한 <strong>Node Lifecycle Conditions</strong>는 이 간극을 공통 상태 이름으로 메우려는 움직임이다. 새 이름은 <code>DrainInProgress</code>, <code>Drained</code>, <code>MaintenancePlanned</code>, <code>MaintenanceInProgress</code>, <code>GracefulNodeShutdownInProgress</code> 다섯 가지다. 중요한 제약도 분명하다. 이 기능은 Alpha이고 <code>NodeLifecycleConditions</code> feature gate는 v1.37에서 기본 비활성화다. 더구나 gate를 활성화해도 현 릴리스의 core component는 condition을 읽어 scheduling·eviction·drain 동작을 바꾸지 않는다.</p>
<p>즉, 이번 변화는 &ldquo;노드가 자동으로 안전하게 정비된다&quot;는 기능 출시가 아니다. 먼저 <strong>운영 의도를 누가, 어떤 freshness로, 어떤 실행 절차와 연결해 기록할 것인가</strong>를 정하는 계약의 출시다. 이 글은 <a href="/learning/deep-dive/deep-dive-kubernetes-rollouts/">Kubernetes Rollout 전략</a>, <a href="/learning/deep-dive/deep-dive-graceful-shutdown/">Graceful Shutdown</a>, <a href="/learning/deep-dive/deep-dive-load-balancer-healthchecks/">Load Balancer Health Check</a>, <a href="/posts/2026-09-08-kubernetes-rootless-node-components-trend/">Kubernetes v1.37 Rootless Node Components</a>를 node maintenance 관점으로 연결한다.</p>
<p>참고한 공식 자료:</p>
<ul>
<li>Kubernetes Blog, <a href="https://kubernetes.io/blog/2026/09/09/kubernetes-v1-37-node-lifecycle-conditions/">Kubernetes v1.37: Introducing Node Lifecycle Conditions</a></li>
<li>Kubernetes Docs, <a href="https://kubernetes.io/docs/reference/node/node-status/">Node Status</a></li>
<li>Kubernetes Docs, <a href="https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/">Safely Drain a Node</a></li>
<li>Kubernetes Docs, <a href="https://kubernetes.io/docs/concepts/workloads/pods/disruptions/">Pod Disruptions</a></li>
</ul>
<h2 id="이-글에서-얻는-것">이 글에서 얻는 것</h2>
<ul>
<li>Node Lifecycle Conditions가 Ready·taint·cordon·drain과 각각 무엇이 다른지 구분합니다.</li>
<li>v1.37의 Alpha 상태를 자동 조치 기능으로 오해하지 않는 이유를 이해합니다.</li>
<li>condition writer의 권한·source·freshness·clear 기준을 운영 계약으로 만드는 방법을 익힙니다.</li>
<li>기존 node maintenance runbook과 연결하되, 위험한 자동화를 shadow mode로 제한하는 기준을 세웁니다.</li>
</ul>
<h2 id="핵심-개념이슈">핵심 개념/이슈</h2>
<h3 id="1-다섯-condition은-행동-명령이-아니라-공유-상태다">1) 다섯 condition은 행동 명령이 아니라 공유 상태다</h3>
<p>각 condition은 node lifecycle의 특정 지점을 표현한다.</p>
<table>
  <thead>
      <tr>
          <th>Condition</th>
          <th>표현하는 사실</th>
          <th>대체하지 않는 기존 제어</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>DrainInProgress</code></td>
          <td>관리자가 정한 기준으로 drain이 진행 중</td>
          <td>cordon, eviction, PDB</td>
      </tr>
      <tr>
          <td><code>Drained</code></td>
          <td>선택한 drain 기준에 도달함</td>
          <td>node 재부팅·삭제 승인</td>
      </tr>
      <tr>
          <td><code>MaintenancePlanned</code></td>
          <td>앞으로 node 변경이 예정됨</td>
          <td>scheduler의 즉시 placement 변경</td>
      </tr>
      <tr>
          <td><code>MaintenanceInProgress</code></td>
          <td>정비가 실제로 시작됨</td>
          <td>traffic drain, workload 이동</td>
      </tr>
      <tr>
          <td><code>GracefulNodeShutdownInProgress</code></td>
          <td>graceful shutdown이 진행됨</td>
          <td>kubelet shutdown policy와 app 종료 처리</td>
      </tr>
  </tbody>
</table>
<p>condition이 <code>True</code>라는 사실만으로 Kubernetes scheduler가 새 Pod를 막거나 controller가 기존 Pod를 evict하지 않는다. 그 동작은 여전히 cordon, drain, taint/toleration, PodDisruptionBudget, deployment rollout 같은 기존 API와 runbook이 담당한다. 운영 중 <code>MaintenancePlanned=True</code>만 보고 cordon을 생략하거나, 반대로 <code>Drained=True</code>만 보고 node를 삭제하면 의도가 실행 결과로 바뀌었다고 잘못 가정하게 된다.</p>
<p>이 구분은 load balancer readiness와도 같다. health check에서 traffic이 빠졌다고 모든 in-flight 요청이 끝난 것이 아니듯, lifecycle condition은 사람이 공유할 상태를 말할 뿐 물리적·논리적 정비의 완료를 보장하지 않는다. application traffic, batch job, local data, storage detach의 종료 기준은 별도 증거를 가져야 한다.</p>
<h3 id="2-alpha-feature-gate와-신호-소비는-별개다">2) Alpha feature gate와 신호 소비는 별개다</h3>
<p>v1.37에서 새 이름은 well-known <code>NodeConditionType</code>으로 예약됐고, 관리자가 또는 관리자가 권한을 준 controller가 설정·해제한다. <code>NodeLifecycleConditions</code> gate는 기본으로 꺼져 있으며, 켜더라도 현재 release에서는 실질적으로 no-op에 가깝다. core workload controller가 이 상태에 맞춰 행동하지 않는다는 뜻이다.</p>
<p>이 사실은 두 가지 설계 원칙으로 이어진다.</p>
<ol>
<li>production availability action의 단일 입력으로 쓰지 않는다.</li>
<li>미래 release에서 controller가 소비할 수 있음을 전제로, 지금부터 상태의 author와 의미를 느슨하게 만들지 않는다.</li>
</ol>
<p>&ldquo;Alpha라서 아무것도 하지 않는다&quot;도 아쉽고, &ldquo;Alpha지만 우리 automation이 알아서 처리한다&quot;도 위험하다. 지금 할 수 있는 좋은 작업은 상태 어휘를 dashboard·change ticket·runbook에 일관되게 넣는 일이다. 미래 기능을 기다리는 동안에도 on-call이 node의 의도와 실제 drain 진행을 빠르게 맞출 수 있다.</p>
<h3 id="3-condition-writer는-작은-control-plane이다">3) condition writer는 작은 control plane이다</h3>
<p>Node condition은 보통 node object를 patch할 수 있는 주체가 쓴다. 이 권한을 maintenance bot, cluster autoscaler, hardware controller, 사람이 모두 가지면 같은 node에 상충하는 상태를 만들 수 있다. 예를 들어 한 controller가 <code>MaintenanceInProgress=True</code>를 기록한 직후 다른 도구가 작업을 취소했는데 clear하지 못하면, dashboard의 상태는 진실이 아니다.</p>
<p>최소 계약은 아래 네 항목이다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>writer: 서비스 계정 하나 또는 명시적 handoff 규칙
</span></span><span style="display:flex;"><span>source: controller 이름 + change ticket/maintenance window
</span></span><span style="display:flex;"><span>freshness: lastTransitionTime 및 heartbeat/재확인 시간
</span></span><span style="display:flex;"><span>clear: 성공, 취소, timeout 각각의 해제 owner와 절차
</span></span></code></pre></div><p>source는 message에 자유 텍스트만 남기는 것보다 annotation이나 event의 change ID로 참조 가능한 편이 좋다. 단, condition 정의 바깥의 annotation schema는 조직이 정하는 정책이지 Kubernetes가 보장하는 표준은 아니다. 정책을 만들 때는 정비 시작·종료 시간이 기록되는 system of record를 하나 정하고, condition은 그 상태를 cluster에 전달하는 projection으로 보는 편이 안전하다.</p>
<h3 id="4-stale-상태를-정상-상태보다-먼저-다룬다">4) stale 상태를 정상 상태보다 먼저 다룬다</h3>
<p><code>MaintenancePlanned=True</code>가 일주일 전 예정을 아직 가리키는지, 실제로 정비가 막힌 것인지, 이미 취소됐는데 clear가 누락된 것인지는 condition 값만으로 알기 어렵다. 그래서 첫 대시보드는 &ldquo;현재 maintenance node 수&quot;보다 <strong>stale signal</strong>을 먼저 보여줘야 한다.</p>
<p>시작 기준의 예시는 다음과 같다.</p>
<table>
  <thead>
      <tr>
          <th>신호</th>
          <th>사람이 확인할 조건</th>
          <th>자동 행동 금지 이유</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>MaintenancePlanned=True</code></td>
          <td>예정 시각이 지났거나 24시간 이상 재확인 없음</td>
          <td>계획 취소·연기 여부는 외부 change record에 있음</td>
      </tr>
      <tr>
          <td><code>DrainInProgress=True</code></td>
          <td>30분 안에 eviction/event 진행이 없음</td>
          <td>PDB, finalizer, local storage 때문에 멈췄을 수 있음</td>
      </tr>
      <tr>
          <td><code>Drained=True</code></td>
          <td>cordon과 workload 잔존 수가 기준과 맞지 않음</td>
          <td>팀마다 &ldquo;drained&quot;의 정의가 다를 수 있음</td>
      </tr>
      <tr>
          <td>shutdown condition</td>
          <td>node heartbeat·graceful shutdown event가 모순</td>
          <td>controller와 kubelet 관측 시간이 다를 수 있음</td>
      </tr>
  </tbody>
</table>
<p>10분, 30분, 24시간은 예시다. 짧은 maintenance window와 수 시간 걸리는 stateful workload를 같은 임계값으로 다루면 alert noise가 커진다. 중요한 것은 condition을 자동 action trigger보다 <strong>investigation trigger</strong>로 시작하는 순서다.</p>
<h2 id="실무-적용">실무 적용</h2>
<h3 id="1-기존-drain-runbook을-바꾸지-않고-상태를-병행-기록한다">1) 기존 drain runbook을 바꾸지 않고 상태를 병행 기록한다</h3>
<p>처음 도입하는 cluster는 flow를 새로 만들지 않는다. 다음처럼 기존의 검증된 절차에 lifecycle 상태를 붙인다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>change 승인
</span></span><span style="display:flex;"><span>  -&gt; MaintenancePlanned 기록 (owner·window·ticket 연결)
</span></span><span style="display:flex;"><span>  -&gt; load balancer에서 traffic 제거 확인
</span></span><span style="display:flex;"><span>  -&gt; cordon
</span></span><span style="display:flex;"><span>  -&gt; DrainInProgress 기록
</span></span><span style="display:flex;"><span>  -&gt; drain + PDB/eviction 관찰
</span></span><span style="display:flex;"><span>  -&gt; Drained 기록 (조직의 완료 정의 충족 시)
</span></span><span style="display:flex;"><span>  -&gt; MaintenanceInProgress 기록
</span></span><span style="display:flex;"><span>  -&gt; 정비·reboot·검증
</span></span><span style="display:flex;"><span>  -&gt; condition clear + uncordon + traffic 복귀 증거
</span></span></code></pre></div><p>여기서 <code>Drained</code>의 완료 정의는 팀마다 명시해야 한다. 예를 들어 &ldquo;DaemonSet과 mirror Pod는 제외하고, 일반 workload Pod 0개, termination 중 Pod 0개, unschedulable=true&quot;처럼 적는다. stateful workload에 local PV가 있다면 volume detach와 application health까지 완료 조건에 넣을지 정해야 한다. 이름이 표준이어도 종료 기준까지 표준인 것은 아니다.</p>
<h3 id="2-writer-권한과-감사-흔적을-분리한다">2) writer 권한과 감사 흔적을 분리한다</h3>
<p>condition을 쓰는 service account에는 필요 최소의 node patch 권한만 주고, 일반 배포 도구나 개발자 role에 넓은 Node write 권한을 주지 않는다. 변경 이벤트에는 writer, node, condition, 이전/이후 값, ticket, 만료 시각을 남긴다. audit log가 없는 cluster라면 처음에는 사람이 수동 입력하는 횟수를 최소화하는 편이 낫다.</p>
<p>권한 검토에서 확인할 질문은 세 가지다.</p>
<ul>
<li>이 writer가 모든 node를 patch해야 하는가, 아니면 관리하는 node pool만 다루면 되는가?</li>
<li>두 writer가 같은 condition을 소유할 때 handoff와 conflict resolution은 무엇인가?</li>
<li>change ticket이 취소됐을 때 누가 condition을 clear하고, 일정 시간 뒤 stale alert를 받는가?</li>
</ul>
<p>condition을 &ldquo;대시보드용 메타데이터&quot;라며 권한을 느슨하게 주면, 나중에 automation이 붙는 순간 신뢰 경계가 이미 무너진다. condition writer를 control plane의 작은 구성요소로 취급해야 하는 이유다.</p>
<h3 id="3-12-node-48시간-shadow-consumer로-검증한다">3) 1~2 node, 48시간, shadow consumer로 검증한다</h3>
<p>첫 canary는 maintenance가 예정된 worker 1~2대에서 시작한다. 48시간 동안 기존 runbook의 action과 lifecycle condition의 순서를 비교한다. 평가 지표는 feature gate가 작동했는지가 아니라 다음의 운영 일치도다.</p>
<table>
  <thead>
      <tr>
          <th>검증 항목</th>
          <th>통과 기준 예시</th>
          <th>abort/보류 신호</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>기록 일치</td>
          <td>ticket·event·condition의 owner와 time window가 100% 일치</td>
          <td>source 또는 완료 시각이 1건이라도 모순</td>
      </tr>
      <tr>
          <td>drain 추적</td>
          <td><code>DrainInProgress</code> 후 drain event가 5분 내 관측</td>
          <td>event 없이 상태만 True로 지속</td>
      </tr>
      <tr>
          <td>상태 정리</td>
          <td>완료·취소 뒤 condition이 10분 안에 clear</td>
          <td>stale condition 1건 발생</td>
      </tr>
      <tr>
          <td>안전성</td>
          <td>PDB 위반 0건, 예상 밖 workload eviction 0건</td>
          <td>availability impact 또는 rollback 필요</td>
      </tr>
      <tr>
          <td>관측</td>
          <td>dashboard/event/audit에서 동일 node 상태 확인</td>
          <td>writer를 추적할 수 없음</td>
      </tr>
  </tbody>
</table>
<p>내부 automation을 시험하고 싶다면 <code>MaintenancePlanned</code>를 받았을 때 &ldquo;cordon했을 것&quot;이라는 예상 action만 로그에 적는 shadow mode로 시작한다. 실제 <code>kubectl cordon</code>은 수행하지 않는다. 48시간 이상, 또는 여러 maintenance event에서 shadow 결과가 기존 runbook과 100% 맞고 stale·rollback 사례도 경험한 뒤에야 human approval이 있는 제안 모드로 올릴 수 있다.</p>
<h3 id="4-node-pool-capability-변화와-함께-읽는다">4) node pool capability 변화와 함께 읽는다</h3>
<p>최근 Kubernetes v1.37의 rootless node component처럼 node 자체의 capability가 달라지는 변화는 재부팅·drain·rollback을 더 자주 요구한다. 이런 때 lifecycle condition은 rootless, GPU, storage, OS image 같은 capability label의 대체재가 아니다. 반대로 node를 어떤 pool로 되돌리고 어떤 workload가 대기 중인지 설명하는 맥락을 제공한다.</p>
<p>예를 들어 rootless node pool을 upgrade할 때 <code>MaintenancePlanned</code>는 계획과 owner를 전달할 수 있다. 실제 placement는 label·taint·affinity가, 작업 중 안전한 축소는 PDB와 capacity budget이, traffic 손실 방지는 readiness와 load balancer deregistration이 맡는다. 한 상태 필드로 모든 control plane 문제를 해결하려 하면 오히려 책임 경계가 흐려진다.</p>
<h2 id="트레이드오프주의점">트레이드오프/주의점</h2>
<p>첫째, condition은 명세화된 단어이지 강제된 workflow가 아니다. <code>Drained=True</code>의 기준을 통일하지 않으면 다른 팀의 controller가 서로 다른 완료 상태를 같은 이름으로 기록한다. 처음부터 owner와 종료 정의를 짧게라도 남겨야 한다.</p>
<p>둘째, Alpha feature의 API·소비 방식은 바뀔 수 있다. platform dashboard나 data model을 만들더라도 condition 이름의 미래 동작을 확정적으로 가정하지 말고, raw condition·writer·version을 함께 저장한다. gate를 production 기본값으로 바꾸는 결정도 Kubernetes upgrade와 별도로 review한다.</p>
<p>셋째, maintenance signal을 받자마자 자동 eviction·autoscaling·capacity 감소를 수행하면 false positive가 availability incident로 바뀐다. condition은 외부 ticket·Node Ready·cordon·drain event·PDB 상태와 함께 평가해야 한다. 특히 stateful workload는 event ordering이 맞아도 data path가 종료되지 않았을 수 있다.</p>
<p>넷째, 너무 많은 custom annotation은 공통 어휘의 장점을 잃게 한다. Kubernetes가 제공하는 다섯 condition은 공통으로 쓰고, 조직 고유 메타데이터는 owner·ticket·window·expiry처럼 최소로 유지한다. 운영자가 한 화면에서 30초 안에 &ldquo;누가 왜 이 node를 비우는가&quot;를 읽지 못하면 계약이 과해진 것이다.</p>
<h2 id="체크리스트-또는-연습">체크리스트 또는 연습</h2>
<ul>
<li><input disabled="" type="checkbox"> <code>DrainInProgress</code>, <code>Drained</code>, <code>MaintenancePlanned</code>, <code>MaintenanceInProgress</code>, <code>GracefulNodeShutdownInProgress</code>의 팀별 완료 정의를 적었다.</li>
<li><input disabled="" type="checkbox"> condition writer, Node patch RBAC, owner handoff, audit log와 clear 절차를 정했다.</li>
<li><input disabled="" type="checkbox"> v1.37에서는 기존 cordon·drain·taint·PDB·traffic drain을 대체하지 않는다는 원칙을 runbook에 명시했다.</li>
<li><input disabled="" type="checkbox"> maintenance ticket, condition, node event, load balancer 상태를 한 화면 또는 한 runbook에서 대조한다.</li>
<li><input disabled="" type="checkbox"> stale maintenance, stalled drain, 완료 뒤 미해제 상태에 대한 사람 확인 임계값을 정했다.</li>
<li><input disabled="" type="checkbox"> 1~2 node에서 48시간 canary하고, consumer automation은 action 없는 shadow mode로 검증했다.</li>
<li><input disabled="" type="checkbox"> 자동 cordon/eviction으로 승격하기 전에 PDB·capacity·rollback·human approval을 별도 게이트로 둔다.</li>
</ul>
<p>연습으로 최근 node maintenance 한 건을 골라 보자. change ticket의 예정 시각, traffic 제거 시각, cordon, drain 시작/완료, reboot, workload 복귀, uncordon을 타임라인으로 적는다. 그다음 어느 지점에 다섯 lifecycle condition을 기록할지와 각 writer를 정한다. 마지막으로 ticket이 취소되거나 drain이 PDB에서 멈춘 상황을 넣어 condition을 누가 언제 clear할지 써 보면, 이 기능의 핵심이 자동화 문법이 아니라 운영 책임의 명확화라는 점이 드러난다.</p>
<h2 id="관련-글">관련 글</h2>
<ul>
<li><a href="/learning/deep-dive/deep-dive-kubernetes-rollouts/">Kubernetes Rollout 전략</a></li>
<li><a href="/learning/deep-dive/deep-dive-graceful-shutdown/">Graceful Shutdown</a></li>
<li><a href="/learning/deep-dive/deep-dive-load-balancer-healthchecks/">Load Balancer Health Check</a></li>
<li><a href="/posts/2026-09-08-kubernetes-rootless-node-components-trend/">Kubernetes v1.37 Rootless Node Components</a></li>
</ul>
]]></content:encoded></item></channel></rss>