<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>makgol's note – 노드 선택·프로비저닝</title><link>https://docs.makgol.com/platform/kubernetes/karpenter/provisioning/</link><description>Recent content in 노드 선택·프로비저닝 on makgol's note</description><generator>Hugo -- gohugo.io</generator><language>ko-KR</language><copyright>© 2026 Mont</copyright><lastBuildDate>Sun, 04 Oct 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://docs.makgol.com/platform/kubernetes/karpenter/provisioning/index.xml" rel="self" type="application/rss+xml"/><item><title>키워드 레퍼런스 — NodePool로 스케줄링을 통제한다</title><link>https://docs.makgol.com/karpenter/03-keyword-reference/</link><pubDate>Sat, 01 Aug 2026 00:00:00 +0900</pubDate><guid>https://docs.makgol.com/karpenter/03-keyword-reference/</guid><description>
&lt;h1&gt;03 · 키워드 레퍼런스 — NodePool 안에서 affinity를 쓴다는 것&lt;/h1&gt;&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;ul&gt;
&lt;li&gt;Karpenter에는 &lt;code&gt;affinity:&lt;/code&gt; 필드가 없습니다. NodePool &lt;code&gt;spec.template.spec.requirements&lt;/code&gt;가 node affinity와 &lt;strong&gt;같은 문법(NodeSelectorRequirement)&lt;/strong&gt; 으로 &amp;ldquo;이 풀이 만들 수 있는 노드의 집합&amp;quot;을 정의합니다. 파드의 nodeSelector·affinity·topologySpread는 그 집합과 교집합을 이룹니다 — 파드 요구는 &lt;strong&gt;NodePool 요구의 부분집합이어야&lt;/strong&gt; 스케줄됩니다.&lt;/li&gt;
&lt;li&gt;NodePool이 어떤 well-known 라벨에 requirement를 걸지 않으면 &lt;strong&gt;그 축은 클라우드가 파는 모든 값이 허용&lt;/strong&gt;됩니다. arch·capacity-type·세대처럼 조직 차원에서 강제할 조건을 파드에만 두면 요구를 빠뜨린 워크로드가 무제한 오퍼링을 얻습니다.&lt;/li&gt;
&lt;li&gt;연산자는 8개다 — 표준 6개 + Karpenter 확장 &lt;strong&gt;&lt;code&gt;Gte&lt;/code&gt;/&lt;code&gt;Lte&lt;/code&gt;(v1.9.0, &lt;a href="https://github.com/kubernetes-sigs/karpenter/pull/2674"target="_blank" rel="noopener"&gt;core#2674&lt;/a&gt;)&lt;/strong&gt;. 이 4개 비교 연산자는 값이 &lt;strong&gt;1개인 음이 아닌 정수&lt;/strong&gt;여야 합니다. 정수로 파싱되지 않는 라벨값에는 매치 자체가 안 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;preferred는 힌트가 아니다.&lt;/strong&gt; Karpenter는 preferred affinity를 처음엔 required로 취급하고 실패하면 정해진 순서로 한 겹씩 벗깁니다. 그 순서에서 &lt;strong&gt;required nodeAffinity의 OR term 순회가 preferred 제거보다 먼저&lt;/strong&gt;입니다 — &amp;ldquo;내가 원한 인스턴스가 왜 안 떴나&amp;quot;의 답이 §3.2다.&lt;/li&gt;
&lt;li&gt;topologySpreadConstraint(TSC)의 &lt;code&gt;topologyKey&lt;/code&gt;는 AWS에서 &lt;strong&gt;zone·hostname·capacity-type 세 개뿐&lt;/strong&gt;이고 &lt;code&gt;topology.kubernetes.io/region&lt;/code&gt;은 미지원입니다. &lt;code&gt;DoNotSchedule&lt;/code&gt;은 완화 대상에서 빠집니다. 도메인이 부족하면 그대로 영구 Pending입니다. 그리고 다른 파드가 나에게 걸어둔 &lt;strong&gt;preferred&lt;/strong&gt; anti-affinity는 코드가 &lt;strong&gt;의도적으로 추적하지 않아&lt;/strong&gt; 전혀 반영되지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;karpenter.k8s.aws/instance-capability-flex&lt;/code&gt;(1.7, &lt;a href="https://github.com/aws/karpenter-provider-aws/pull/8315"target="_blank" rel="noopener"&gt;aws#8315&lt;/a&gt; → 개명 &lt;a href="https://github.com/aws/karpenter-provider-aws/pull/8490"target="_blank" rel="noopener"&gt;aws#8490&lt;/a&gt;)로 flex를 배제할 때 &lt;strong&gt;&lt;code&gt;DoesNotExist&lt;/code&gt;를 쓰면 인스턴스 타입 전체가 배제&lt;/strong&gt;됩니다. 정답은 &lt;code&gt;In [&amp;quot;false&amp;quot;]&lt;/code&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;karpenter.sh/capacity-type&lt;/code&gt;은 &lt;code&gt;spot&lt;/code&gt;/&lt;code&gt;on-demand&lt;/code&gt;/&lt;code&gt;reserved&lt;/code&gt; 3값이고 &lt;strong&gt;reserved가 최우선 소비&lt;/strong&gt;됩니다. &lt;code&gt;nodeSelector&lt;/code&gt;로 &lt;code&gt;on-demand&lt;/code&gt;를 정확 일치시킨 워크로드는 On-Demand Capacity Reservation(ODCR) 노드에 절대 안 뜹니다 — nodeAffinity &lt;code&gt;In [on-demand, reserved]&lt;/code&gt;로 바꿔야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;minValues&lt;/code&gt;는 &lt;strong&gt;NodePool 전용&lt;/strong&gt;이고 &lt;code&gt;In&lt;/code&gt;에서만 값 개수가 검증됩니다. 패밀리를 여러 개 나열해도 minValues가 없으면 스케줄러는 다양성을 강제하지 않습니다 — 스팟 동시 회수 리스크가 이 한 필드로 갈립니다.&lt;/li&gt;
&lt;li&gt;교집합이 비면 실패로 끝나지 않고 &lt;strong&gt;파드가 Pending으로 남아 백오프 재시도&lt;/strong&gt;됩니다. 즉 오설정과 일시적 용량 부족이 같은 겉모습을 갖습니다. 구분은 &lt;code&gt;FailedScheduling&lt;/code&gt;(파드)·&lt;code&gt;NoCompatibleInstanceTypes&lt;/code&gt;(NodePool) 이벤트 메시지로만 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;왜 이 문서인가.&lt;/strong&gt; Karpenter의 스케줄링은 kube-scheduler식 술어 평가가 아니라 &lt;strong&gt;&amp;ldquo;NodePool requirements ∩ 파드 요구 ∩ 클라우드 offering&amp;quot;의 집합 연산&lt;/strong&gt;이다 — 교집합이 비면 노드가 안 만들어집니다. &amp;ldquo;파드에 affinity를 걸었는데 노드가 안 뜬다&amp;quot;의 원인은 거의 항상 파드 쪽 요구만 보고 NodePool 쪽 우주를 안 본 데 있습니다. 이 문서는 키워드별 레퍼런스이면서 그 교집합 모델을 축으로 삼습니다.&lt;/p&gt;</description></item><item><title>인스턴스는 누가 고르는가</title><link>https://docs.makgol.com/karpenter/04-instance-selection/</link><pubDate>Sat, 01 Aug 2026 00:00:00 +0900</pubDate><guid>https://docs.makgol.com/karpenter/04-instance-selection/</guid><description>
&lt;h1&gt;04 · 인스턴스는 누가 고르는가 — 후보를 넘기고, EC2가 정한다&lt;/h1&gt;&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;ul&gt;
&lt;li&gt;인스턴스 타입을 확정하는 주체는 Karpenter 스케줄러가 아닙니다. 후보 이름 전체를 &lt;code&gt;node.kubernetes.io/instance-type In [...]&lt;/code&gt; 하나로 묶어 NodeClaim에 실어 보냅니다. 타입을 하나로 고정하는 Cluster Autoscaler와 여기서 달라집니다.&lt;/li&gt;
&lt;li&gt;최종 선택자는 EC2입니다. provider-aws가 CreateFleet을 &lt;code&gt;Type: instant&lt;/code&gt;, On-Demand 할당 전략 &lt;code&gt;lowest-price&lt;/code&gt;로 호출합니다. &amp;ldquo;7세대가 싸서 7세대만 뜬다&amp;quot;의 실제 원인은 이 한 줄입니다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;후보 절단(600/60) 때문에 8세대가 잘려 나간다&amp;quot;는 흔한 오해이고 사실이 아닙니다. 정렬 키가 &lt;em&gt;그 타입의 최저 오퍼링 가격&lt;/em&gt;이라 사이즈에 대해 단조입니다. 잘려 나가는 쪽은 양 세대의 가장 큰 사이즈들입니다.&lt;/li&gt;
&lt;li&gt;단일 NodePool 안에는 선호를 표현할 축이 없습니다. &lt;code&gt;requirements&lt;/code&gt; 스키마는 Key/Operator/Values/MinValues 넷뿐입니다. &lt;code&gt;weight&lt;/code&gt;는 NodePool 레벨에만 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;minValues&lt;/code&gt;는 우선순위 손잡이가 아니라 다양성 하한입니다. 세대 선호에 쓸 수 없을 뿐 아니라 7세대를 후보에 붙들어 두는 역효과를 냅니다.&lt;/li&gt;
&lt;li&gt;파드의 &lt;code&gt;preferred&lt;/code&gt; nodeAffinity도 대안이 아닙니다. Karpenter의 처리 방식은 &amp;ldquo;가장 무거운 term 하나를 hard requirement로 승격한 뒤 실패하면 벗기는&amp;rdquo; 것입니다. 파드마다 걸어야 하고 완화 순서상 네 번째입니다. 벗겨지려면 먼저 스케줄 시뮬레이션이 실패해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;c8i/m8i/r8i와 c7i/m7i/r7i를 하나의 NodePool에 함께 선언해 두면 &amp;ldquo;8세대 우선, 없으면 7세대 폴백&amp;quot;을 기대하게 되는데, 실제로는 항상 7세대만 뜹니다. 범인은 절단·필터·EC2 중 하나입니다. 어느 쪽이냐에 따라 대응책이 완전히 달라지므로 &amp;ldquo;싼 걸 좋아해서&amp;quot;로는 넘어갈 수 없습니다. 이 문서는 파드가 pending되는 순간부터 EC2가 인스턴스를 띄우는 순간까지 선택이 실제로 일어나는 곳을 코드로 하나씩 지워 갑니다. 흔한 오해 하나(절단이 세대를 자른다)도 명시적으로 깹니다.&lt;/p&gt;</description></item><item><title>세대 선호 만들기</title><link>https://docs.makgol.com/karpenter/05-generation-preference/</link><pubDate>Sat, 01 Aug 2026 00:00:00 +0900</pubDate><guid>https://docs.makgol.com/karpenter/05-generation-preference/</guid><description>
&lt;h1&gt;05 · 세대 선호 만들기 — weight vs NodeOverlay&lt;/h1&gt;&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;ul&gt;
&lt;li&gt;GA 해법은 NodePool을 쪼개고 &lt;code&gt;spec.weight&lt;/code&gt;를 줍니다. weight는 가격보다 먼저 적용됩니다 — NodePool을 고르는 코드 경로(&lt;code&gt;scheduler.go&lt;/code&gt; &lt;code&gt;addToNewNodeClaim&lt;/code&gt;)에는 가격 비교가 아예 없습니다. &lt;code&gt;grep -ic price scheduler.go&lt;/code&gt; → 0건.&lt;/li&gt;
&lt;li&gt;동작은 &amp;ldquo;위에서부터 순차 시도&amp;quot;가 아닙니다. NodeClaimTemplate들이 병렬로 시뮬레이션되고 뮤텍스 아래에서 성공한 것 중 인덱스가 가장 앞선(=weight 최고) 것만 채택됩니다.&lt;/li&gt;
&lt;li&gt;파드 쪽에는 아무것도 걸지 않습니다. nodeSelector/affinity는 필요 없고 오히려 &lt;code&gt;karpenter.sh/nodepool&lt;/code&gt; 셀렉터를 걸면 weight가 통째로 무시됩니다(테스트로 확인됩니다).&lt;/li&gt;
&lt;li&gt;NodeOverlay &lt;code&gt;priceAdjustment&lt;/code&gt;는 알파입니다. feature gate &lt;code&gt;NodeOverlay&lt;/code&gt;가 기본 false입니다. 세대 선호를 실제로 만드는 건 provider-aws가 EC2 Fleet 전략을 &lt;code&gt;prioritized&lt;/code&gt;로 바꾸는 지점인데 — 거기에 확인 못 한 구멍이 둘 있습니다(정수 Priority 규정, 단일 CreateFleet 내 폴백 미보장).&lt;/li&gt;
&lt;li&gt;오버레이를 쓰지 않는 한, 8세대 복귀 장치는 gen7 풀의 &lt;code&gt;expireAfter&lt;/code&gt; 하나뿐입니다. consolidation은 더 싼 쪽으로만 움직이므로 7→8 승격을 절대 하지 않습니다.&lt;/li&gt;
&lt;li&gt;적용 순서를 틀리면 대량 교체가 납니다. 기존 단일 풀에서 세대를 &lt;em&gt;빼는&lt;/em&gt; 편집은 RequirementsDrifted를 유발합니다. 두 풀을 새로 만든 뒤 기존 풀을 지우는 순서로 갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;c8i/m8i/r8i를 먼저 쓰고, 없으면 c7i/m7i/r7i로&amp;quot;는 Karpenter가 1급으로 제공하지 않는 요구입니다. &lt;code&gt;requirements&lt;/code&gt; 스키마가 Key/Operator/Values/MinValues뿐이어서 선호도를 표현할 필드가 없습니다(&lt;a href="https://docs.makgol.com/karpenter/04-instance-selection/"&gt;04&lt;/a&gt;). 업스트림도 두 번 반려했습니다 — &lt;a href="https://github.com/kubernetes-sigs/karpenter/issues/1829"target="_blank" rel="noopener"&gt;karpenter#1829&lt;/a&gt; &lt;em&gt;closed as not planned&lt;/em&gt;, &lt;a href="https://github.com/aws/karpenter-provider-aws/issues/6721"target="_blank" rel="noopener"&gt;karpenter-provider-aws#6721&lt;/a&gt; &lt;em&gt;closed&lt;/em&gt;. 남은 두 우회로를 코드까지 비교하고 복붙용 매니페스트 전문을 내놓습니다.&lt;/p&gt;</description></item><item><title>용량이 없을 때 — ICE와 폴백 지연</title><link>https://docs.makgol.com/karpenter/07-ice-fallback/</link><pubDate>Sat, 01 Aug 2026 00:00:00 +0900</pubDate><guid>https://docs.makgol.com/karpenter/07-ice-fallback/</guid><description>
&lt;h1&gt;07 · 용량이 없을 때 — ICE와 폴백 지연&lt;/h1&gt;&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;ul&gt;
&lt;li&gt;폴백은 이미 공짜입니다. 8세대가 Insufficient Capacity Error(ICE, AWS 응답 코드 &lt;code&gt;InsufficientInstanceCapacity&lt;/code&gt;)를 내는 순간 provider-aws가 그 오퍼링을 unavailable로 마킹하고 코어는 &lt;code&gt;Offerings.Available()&lt;/code&gt;에서 그 오퍼링을 걸러 다음 루프의 후보에서 뺍니다. 손으로 만들어야 하는 건 &amp;ldquo;평소엔 8세대&amp;quot;라는 상향 강제뿐입니다.&lt;/li&gt;
&lt;li&gt;코어는 ICE 난 NodeClaim을 지우고 재큐하지 않습니다. &lt;code&gt;err&lt;/code&gt;를 반환하지도 않으니 controller-runtime 백오프도 안 붙습니다. 재시도는 파드 컨트롤러의 10초 재큐 + 배치창(idle 1s / max 10s)뿐이고 실측 기대치는 대략 11~30초입니다.&lt;/li&gt;
&lt;li&gt;ICE 캐시는 3분입니다. 키는 &lt;code&gt;&amp;lt;capacityType&amp;gt;:&amp;lt;instanceType&amp;gt;:&amp;lt;zone&amp;gt;&lt;/code&gt;. 부족이 지속되면 3분 주기로 실패 왕복이 반복됩니다. 거꾸로 용량이 회복되면 최대 3분 뒤 8세대가 저절로 부활합니다. 사람이 할 일은 없습니다.&lt;/li&gt;
&lt;li&gt;가용성 판정 축은 셋입니다. &lt;code&gt;IsUnavailable()&lt;/code&gt;이 보는 것은 오퍼링 캐시 ∨ capacity-type 전체 차단 캐시 ∨ 전 서브넷 차단 캐시의 OR입니다. 서브넷 IP 고갈은 인스턴스 타입과 무관하게 오퍼링을 죽입니다.&lt;/li&gt;
&lt;li&gt;spot을 섞으면 세대 강제가 무력화됩니다. capacity-type 우선순위가 &lt;code&gt;reserved &amp;gt; spot &amp;gt; on-demand&lt;/code&gt;인데다 spot Fleet은 &lt;code&gt;price-capacity-optimized&lt;/code&gt;라 EC2가 용량 깊이로 타입을 고릅니다. 세대 선호가 목적이면 on-demand로 한정하십시오.&lt;/li&gt;
&lt;li&gt;단일 NodePool 안에서 알파 없이 8세대를 1순위로 만드는 길은 On-Demand Capacity Reservation(ODCR)입니다. reserved 오퍼링 가격이 &lt;code&gt;odPrice / 10,000,000&lt;/code&gt;이라 정렬·절단·Fleet 전부에서 무조건 앞섭니다. 게이트 &lt;code&gt;ReservedCapacity&lt;/code&gt;는 기본 ON(BETA)입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&amp;ldquo;8세대(c8i/m8i/r8i)가 부족하면 7세대로 폴백하고 싶다&amp;quot;에서 폴백 쪽은 이미 동작합니다. ICE를 오퍼링 가용성으로 환산해 스케줄링에 되먹이는 배선이 코어와 provider-aws에 완비돼 있습니다. 이 문서는 그 폴백이 어느 코드에서 몇 초 만에 일어나는지를 끝까지 내려가 01~03에서 고른 구성이 ICE 상황에서 어떻게 행동하는지 확정합니다.&lt;/p&gt;</description></item></channel></rss>