kubernetes
Karpenter를 이해하는 데 가장 중요한 두 가지 개념은 NodeClass와 NodePool입니다. 이 둘은 함께 동작하면서 EKS에서 노드를 자동으로 생성하고 워크로드를 적절한 노드에 배치할 수 있도록 도와줍니다. 쉽게 말해 NodeClass는 노드 자체의 “청사진”을 정의하고, NodePool은 어떤 상황에서 그 노드를 사용할지 규칙을 정하는 역할을 합니다.
개념 | 역할 |
---|---|
NodeClass | EC2 노드의 하드웨어·네트워크 스펙, AMI 종류, 보안그룹·서브넷 등을 정의 |
NodePool | 어떤 워크로드가 어떤 조건의 노드에서 실행될지를 정의 (라벨, 인스턴스 타입, 온디맨드/스팟 등) |
NodeClass는 EC2 인스턴스의 구체적인 스펙과 환경을 정의하는 리소스입니다. 예를 들어 어떤 AMI를 사용할지, 어떤 서브넷이나 보안 그룹에 연결할지, 어떤 IAM Role을 부여할지와 같은 정보가 여기에 포함됩니다. 즉, 노드가 어떤 환경에서 어떤 모습으로 만들어질지를 결정하는 AWS 리소스 템플릿이라고 할 수 있습니다. 자동차에 비유하면 NodeClass는 “설계도”와 같습니다. 설계도에는 차종, 엔진, 색상, 그리고 기본 옵션들이 정의되어 있습니다. 하지만 설계도가 있다고 해서 바로 운행에 투입되는 것은 아닙니다. 그것을 어떻게 사용할지는 또 다른 규칙이 필요합니다.
NodePool은 워크로드가 어떤 조건을 가진 노드에서 실행될지를 정의하는 리소스입니다. 특정 워크로드가 amd64 아키텍처를 필요로 하는지, 온디맨드 인스턴스만 허용하는지, 혹은 특정 인스턴스 계열만 사용해야 하는지를 NodePool에서 설정할 수 있습니다. 다시 말해, NodePool은 스케줄러가 어떤 NodeClass를 참고해서 어떤 상황에 어떤 노드를 띄울지 결정하는 운용 규칙 역할을 합니다. 자동차 비유를 다시 들어보면, NodePool은 “운행 지침”에 해당합니다. 출퇴근용으로는 소형차만 사용하고, 장거리 여행에는 SUV를 쓰며, 주말에는 전기차만 투입한다는 식의 규칙이 NodePool에서 정의됩니다.
실무에서는 NodeClass를 주로 클러스터 전체 공통 설정에 활용합니다. 보안 그룹, VPC, IAM Role과 같이 노드 생성 시 반드시 필요한 환경 정의는 NodeClass에 담아둡니다. 반면 NodePool은 보통 서비스 단위 또는 워크로드 유형 단위로 나눠서 사용합니다. 예를 들어 프론트엔드 서비스 전용 풀, 배치 잡 전용 풀, GPU가 필요한 머신러닝 워크로드 전용 풀을 따로 두는 식입니다. 이렇게 역할을 나누어 관리하면 운영이 단순해질 뿐 아니라 비용 최적화에도 유리합니다. Spot 인스턴스를 활용할지, 온디맨드만 쓸지, 혹은 특정 계열 인스턴스만 허용할지를 NodePool에서 쉽게 정의할 수 있기 때문입니다.
┌────────────┐
│ NodePool │ ← On-Demand / t 계열 / amd64
└─────┬──────┘
│ nodeClassRef
┌─────▼──────┐
│ NodeClass │ ← AMI, Role, Subnet, SG
└────────────┘
│
(AWS EC2)
이 구성도는 Karpenter가 새로운 노드를 생성할 때 어떤 흐름으로 리소스를 참조하는지를 단순화한 그림입니다.
먼저 NodePool은 워크로드의 요구사항을 정의합니다. 예를 들어 “온디맨드 인스턴스만 허용한다”, “t 계열 인스턴스만 사용한다”, “아키텍처는 amd64여야 한다”와 같은 조건을 NodePool에서 지정합니다. 그러나 NodePool은 이러한 조건만 정의할 뿐, 실제 인스턴스를 만드는 데 필요한 세부 인프라 정보는 포함하지 않습니다.
이때 NodePool은 nodeClassRef를 통해 NodeClass를 참조합니다. NodeClass에는 EC2 인스턴스를 띄우는 데 필요한 하드웨어 및 네트워크 구성이 담겨 있습니다. 어떤 AMI를 사용할지, 어떤 IAM Role을 붙일지, 어떤 서브넷과 보안 그룹에 연결할지를 NodeClass에서 정합니다. 즉, NodePool이 “이런 조건의 노드를 원해”라고 요청하면, NodeClass가 “그럼 이렇게 만들어줄게”라고 응답하는 구조입니다.
마지막으로 Karpenter는 NodePool과 NodeClass의 정의를 합쳐서 실제 AWS EC2 인스턴스를 생성합니다. 그 결과 새로운 노드가 EKS 클러스터에 자동으로 조인되며, 사용자가 배포한 파드가 해당 노드에서 실행됩니다.
정리하면, NodePool은 정책(Policy), NodeClass는 템플릿(Template), 그리고 AWS EC2는 실제 리소스(Resource)라고 볼 수 있습니다. 이 세 가지가 연결되어야만 Karpenter가 정상적으로 노드를 자동 생성할 수 있습니다.
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: default
spec:
# Amazon Linux 2023 AMI
amiFamily: AL2023
# IAM Role (EKS 클러스터에 맞게 변경)
role: "KarpenterNodeRole-<YOUR-CLUSTER-NAME>" # 🔹 변경 필요
# Subnet 자동 선택 (discovery 태그 필요)
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: "<YOUR-CLUSTER-NAME>" # 🔹 변경 필요
# Security Group 자동 선택 (discovery 태그 필요)
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: "<YOUR-CLUSTER-NAME>"
위 예시는 Karpenter에서 EC2 노드를 생성할 때 사용할 NodeClass 정의입니다. NodeClass는 노드가 어떤 AMI를 사용할지, 어떤 IAM Role을 가질지, 어떤 서브넷과 보안 그룹 안에서 생성될지를 지정합니다. 예제에서는 amiFamily: AL2023을 통해 Amazon Linux 2023 기반의 이미지를 사용하도록 했습니다. 또한 role 항목에는 해당 클러스터용 Karpenter Node Role을 직접 지정해야 하며, 이 Role에는 EC2 인스턴스를 실행하고 EKS 클러스터에 조인할 수 있는 권한이 포함되어 있어야 합니다. subnetSelectorTerms와
securityGroupSelectorTerms
는 각각 서브넷과 보안 그룹을 자동으로 선택하는 규칙을 의미합니다. 여기서는 karpenter.sh/discovery: <YOUR-CLUSTER-NAME>
라는 태그 기반 선택 방식을 사용하고 있으며, 따라서 EKS 클러스터 VPC 리소스에 동일한 태그를 미리 설정해 두어야 합니다. 즉, NodeClass는 Karpenter가 새로운 노드를 생성할 때 반드시 필요한 AWS 인프라 환경 정의라고 이해하면 됩니다.
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: hello-world
spec:
template:
metadata:
labels:
app: hello-world
spec:
nodeClassRef:
name: default
requirements:
# 아키텍처 제한
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
# 인스턴스 category 제한 (t 계열만)
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["t"]
# On-Demand 전용
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"]
# 미사용 노드 정리 정책
disruption:
consolidationPolicy: WhenUnderutilized
consolidateAfter: 30s
NodePool은 실제 워크로드가 실행될 노드의 조건을 정의하는 리소스입니다. 이 리소스를 통해 Karpenter는 “어떤 NodeClass를 참고해야 하는지”와 “어떤 제약 조건을 만족하는 인스턴스를 선택할지”를 결정합니다. 위 예시는 hello-world라는 이름의 NodePool을 생성하고, 특정 조건을 만족하는 EC2 인스턴스만 선택하도록 요구사항을 설정한 것입니다.
nodeClassRef
는 NodePool이 참조할 NodeClass를 지정합니다. 여기서는 default라는 이름의 NodeClass를 사용합니다.requirements
는 노드가 가져야 할 속성을 제한합니다. 예를 들어, 아키텍처는 반드시 amd64여야 하고, 인스턴스 카테고리는 t 계열로만 선택되며, 용량 타입은 온디맨드 인스턴스만 허용하도록 지정했습니다.disruption
정책은 필요하지 않은 노드를 언제 정리할지를 정의합니다. 이 예시에서는 노드가 활용도가 낮을 경우 30초 후 자동으로 통합(consolidation)하도록 설정했습니다.NodePool 주요 옵션을 정리하면 아래와 같습니다.
필드 | 설명 | 예시 값 |
---|---|---|
metadata.name | NodePool의 이름 | hello-world |
template.metadata.labels | 생성된 노드에 자동으로 붙는 라벨 | app: hello-world |
template.spec.nodeClassRef | 참조할 NodeClass 이름 | default |
requirements | 노드 선택 조건 (아키텍처, 인스턴스 유형, 용량 타입 등) | amd64 , t , on-demand |
└─ key | 제한할 속성 키 (쿠버네티스 표준 라벨 또는 Karpenter 전용 라벨) | kubernetes.io/arch |
└─ operator | 조건 연산자 (In , NotIn , Exists , DoesNotExist ) | In |
└─ values | 허용되는 값 목록 | ["amd64"] , ["t"] |
disruption.consolidationPolicy | 미사용 노드를 정리하는 정책 (WhenEmpty , WhenUnderutilized ) | WhenUnderutilized |
disruption.consolidateAfter | 노드가 정리되기 전 대기 시간 | 30s , 5m , 1h |
아래는 NodePool의 requirements에서 자주 활용되는 대표적인 Key들입니다. 이들을 조합하면 원하는 노드 조건을 세밀하게 지정할 수 있습니다.
Key | 설명 | 예시 값 |
---|---|---|
kubernetes.io/arch | 노드 아키텍처 지정 | amd64 , arm64 |
kubernetes.io/os | OS 종류 | linux , windows |
karpenter.k8s.aws/instance-family | 인스턴스 패밀리 제한 | m , c , t , g |
karpenter.k8s.aws/instance-size | 인스턴스 크기 제한 | small , medium , large |
karpenter.k8s.aws/instance-category | 인스턴스 카테고리 지정 | t , m , c |
karpenter.sh/capacity-type | 용량 타입 지정 | on-demand , spot |
topology.kubernetes.io/zone | 가용 영역(AZ) 제한 | ap-northeast-2a |
karpenter.k8s.aws/instance-hypervisor | 하이퍼바이저 타입 | nitro |
karpenter.k8s.aws/instance-cpu | 최소/최대 vCPU 개수 | 2 , 4 , 8 |
karpenter.k8s.aws/instance-memory | 최소/최대 메모리 (MiB 단위) | 4096 , 8192 |
karpenter.k8s.aws/instance-gpu | GPU 개수 제한 | 1 , 2 |
$ kubectl apply -f ec2nodeclass.yaml
$ kubectl apply -f nodepool.yaml
NodeClass와 NodePool은 단순히 CRD(Custom Resource Definition) 리소스를 생성하는 것이므로, kubectl apply -f 명령으로 적용만 해주면 클러스터에 정상 반영됩니다. 이제 Karpenter는 NodePool에 정의된 요구조건과 NodeClass의 환경 설정을 참고하여, 워크로드가 스케줄링될 때 자동으로 EC2 인스턴스를 생성할 준비를 마치게 됩니다.
NodeClass와 NodePool을 적용했다고 해서 바로 노드가 생기는 것은 아닙니다. Karpenter는 클러스터에 새로운 워크로드가 스케줄링되었을 때 그 요구사항을 만족하는 노드가 없으면 NodePool을 참고하여 새 노드를 생성합니다. NodePool은 “이 워크로드는 어떤 조건을 가진 노드가 필요하다”는 제약을 제공하고, NodeClass는 “그 조건에 맞는 노드를 어떤 환경으로 생성할지”를 알려주는 템플릿 역할을 합니다. 즉, 아래 과정을 통해 두 리소스가 실제 동작하는 모습을 확인할 수 있습니다.
먼저 간단한 Nginx Pod을 배포합니다. 현재 클러스터에 NodePool 조건(amd64, t 계열, on-demand)을 만족하는 노드가 없기 때문에, Karpenter는 NodePool과 NodeClass를 참조하여 새로운 EC2 인스턴스를 자동으로 띄우게 됩니다.
$ kubectl run nginx --image=nginx --replicas=1
Karpenter 컨트롤러가 어떻게 노드 생성을 결정했는지 로그를 확인합니다. 여기서 NodePool 요구조건을 검토하고, NodeClass에 정의된 AMI·서브넷·보안그룹 정보를 조합해 EC2 인스턴스를 요청하는 과정을 볼 수 있습니다.
$ kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter
EC2 인스턴스가 정상적으로 프로비저닝되면, EKS 클러스터에도 새로운 노드가 조인됩니다. hello-world라는 라벨이 자동으로 붙었는지 확인하여, NodePool 설정이 올바르게 반영되었는지 검증할 수 있습니다.
$ kubectl get nodes -l app=hello-world
이번 글에서는 Karpenter의 두 가지 핵심 리소스인 NodeClass와 NodePool을 살펴보았습니다. NodeClass는 EC2 인스턴스를 생성하기 위한 하드웨어·네트워크 환경을 정의하는 템플릿이고, NodePool은 특정 워크로드가 어떤 조건의 노드를 필요로 하는지를 정의하는 정책 역할을 합니다. 두 리소스는 서로 보완적으로 동작하며, 결합되어야만 Karpenter가 실제로 EC2 노드를 프로비저닝할 수 있습니다.
Hello World 실습을 통해 단 몇 개의 YAML 설정만으로도 EKS에서 새로운 워크로드가 실행될 때 자동으로 노드가 생성되는 과정을 확인했습니다. 이는 Karpenter가 제공하는 강력한 자동화의 출발점으로, 이후에는 Spot/On-Demand 혼합 구성, GPU 풀, 비용 최적화 전략 등 더 고급 시나리오로 확장할 수 있습니다. 결국 NodeClass와 NodePool을 이해하는 것이 Karpenter 활용의 첫걸음이며, 이 기반 위에서 다양한 운영 전략을 유연하게 구현할 수 있습니다.