노드 자동화 운영
EKS 1.37, Karpenter v1.15 기준 (2026-10-20 확인)
template.spec.requirements: 허용할 인스턴스 조건disruption: consolidation 정책, budgettemplate.spec.expireAfter, terminationGracePeriodlimits, weight, nodeClassRefamiSelectorTerms: alias, id, name, tags, ssmParametersubnetSelectorTerms, securityGroupSelectorTermsrole 또는 instanceProfilekubelet, userDatarole 또는 instanceProfilesubnetSelectorTerms, securityGroupSelectorTermsephemeralStorage, advancedNetworkingadvancedCompute: kubelet, sysctl, hugepages 일부karpenter.k8s.aws/instance-*, Auto Mode는 eks.amazonaws.com/instance-*. NodePool을 옮길 때 requirements 키를 바꿔야 합니다.| 항목 | EKS Auto Mode | Self-managed Karpenter v1.15 |
|---|---|---|
| Karpenter controller | AWS 관리, 클러스터 Pod 아님, 버전 비공개 | 사용자 노드의 Pod, Helm과 CRD chart로 업그레이드 |
| AMI, OS | AWS 결정, Bottlerocket 변형, 새 AMI는 Drift로 교체 | amiSelectorTerms, custom AMI, userData |
| VPC CNI, CoreDNS, kube-proxy, EBS CSI, Pod Identity Agent | 내장, CNI, CoreDNS, kube-proxy는 노드의 systemd service | add-on으로 설치, 관리 |
| Spot 중단, 노드 health | AWS 자동 처리, 자동 repair 내장 | SQS, EventBridge 구성, NodeRepair는 alpha gate |
노드를 고르고, 줄이고, 바꾸는 규칙
requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] - key: karpenter.k8s.aws/instance-category operator: In values: ["c", "m", "r"] - key: kubernetes.io/arch operator: In values: ["amd64", "arm64"]
nodeClassRef: group: eks.amazonaws.com kind: NodeClass name: default requirements: - key: eks.amazonaws.com/instance-category operator: In values: ["c", "m", "r"] - key: karpenter.sh/capacity-type operator: In values: ["on-demand"]
system, general-purpose: On-Demand만, C/M/R 계열, 5세대 이상. 수정 불가WhenEmptyWhenEmptyOrUnderutilizedBalancedBalanced 추가, opt-inBalanced 추가, 기존 NodePool 동작은 그대로Balanced, built-in pool도 BalancedBalanced인 drift가 생깁니다.aws eks update-cluster-config \
--name $CLUSTER \
--kube-scheduler-config \
'{"nodeResourcesFit":{"scoringStrategy":
{"type":"MostAllocated"}}}'
resize subresource.spec.resources도 Pod 재생성 없이 resize| 워크로드 | CPU req=limit | Guaranteed | restartCount | containerID |
|---|---|---|---|---|
| Deployment (Pod 2개) | 200m → 50m | 유지 | 0 → 0 | 동일 |
| StatefulSet | 200m → 50m | 유지 | 0 → 0 | 동일 |
| DaemonSet | 200m → 50m | 유지 | 0 → 0 | 동일 |
pods/resize로 축소, memory 고정disruption: consolidationPolicy: Balanced budgets: - nodes: "10%" - nodes: "0" schedule: "0 0 * * 1-5" # UTC duration: 9h reasons: ["Underutilized", "Drifted"]
nodes: 10% 하나. Auto Mode 기본도 같습니다.expireAfter에 의한 교체는 budget과 무관하게 진행됩니다.spec.weight, spec.limits, spec.disruption.*al2023@v20261012처럼 pin. @latest는 새 AMI마다 전체 Drift라 프로덕션 비권장advancedCompute 변경도 in-place 없이 Drift| 항목 | Self-managed v1.15 | Auto Mode |
|---|---|---|
expireAfter 기본 | 720h (30일) | 336h (14일) |
| 최대 수명 | 상한 없음, Never 허용 | 21일 강제 |
terminationGracePeriod | CRD 기본값 없음 (무제한 대기) | NodeClaim에 24h 적용 |
expireAfter + terminationGracePeriod. Auto Mode는 긴 drain을 더해도 21일 상한이 우선합니다.| 경로 | 방식 | budget | 대체 노드 |
|---|---|---|---|
| Drift | graceful, consolidation보다 먼저 평가 | 적용 | 필요하면 먼저 준비 |
| Consolidation | graceful, requests 기준 재배치 | 적용 | 필요하면 먼저 준비 |
| Expiration | forceful, 정책 나이에 도달하면 시작 | 무시 | Ready를 기다리지 않음 |
kubectl delete node | 수동 삭제, -l이면 일괄 | 무시 | 보장 없음 |
expireAfter를 바꾸면 기존 NodeClaim은 Drift로 교체됩니다.settings.interruptionQueue로 연결interruptionQueue가 없으면 interruption 처리가 꺼집니다.SendSpotInstanceInterruptions 포함. Spot 중단 리허설은 self-managed 노드에서 합니다.pod-deletion-cost로 조정하는 방식은 deprecatedterminationGracePeriod를 설정하면 blocking PDB나 do-not-disrupt가 있어도 Drift 교체가 진행됩니다. Auto Mode는 21일 최대 수명에서 blocking PDB가 있으면 개입이 필요할 수 있습니다.켜기, 노드 설정, 용량, 진단, 비용, 마이그레이션, 그리고 무엇을 고를까
| 역할 | 필요한 것 |
|---|---|
| 클러스터 역할 | 신뢰: sts:AssumeRole + sts:TagSession정책 ( AmazonEKS 접두사): ClusterPolicy, ComputePolicy, BlockStoragePolicyV2, LoadBalancingPolicy, NetworkingPolicy |
| 노드 역할 | AmazonEKSWorkerNodeMinimalPolicy, AmazonEC2ContainerRegistryPullOnly |
| 워크로드 | Pod Identity로 따로 부여. OIDC provider 없이 활성화 가능 |
CONFIG_MAP에서 API_AND_CONFIG_MAP 전환은 단방향. 기존 클러스터의 클러스터 역할 ARN은 교체 불가# 기존 클러스터: 세 기능을 한 요청에서 함께 aws eks update-cluster-config --name $CLUSTER \ --compute-config '{"enabled":true, "nodePools":["general-purpose","system"], "nodeRoleArn":"'$NODE_ROLE_ARN'"}' \ --kubernetes-network-config \ '{"elasticLoadBalancing":{"enabled":true}}' \ --storage-config '{"blockStorage":{"enabled":true}}' # eksctl: 새 클러스터 create cluster --enable-auto-mode # 기존 클러스터 update auto-mode-config -f cluster.yaml kubectl wait nodepool/general-purpose nodepool/system \ --for=condition=Ready --timeout=300s
| NodePool | 아키텍처 | 용량, 인스턴스 | 누가 올라가나 |
|---|---|---|---|
general-purpose | amd64 | On-Demand, C/M/R, 5세대 이상 | 일반 워크로드 |
system | amd64, arm64 | On-Demand, C/M/R, 5세대 이상 | CriticalAddonsOnly taint를 tolerate하는 Pod |
Balanced입니다.computeConfig.nodePools에서 이름을 지우면 그 풀의 노드가 drain, 종료됩니다. 새 Pod만 막는 설정이 아닙니다.kubectl get nodepool general-purpose system -o yaml, kubectl get nodeclass default -o yaml로 실제 disruption과 taint 값을 확인합니다.apiVersion: eks.amazonaws.com/v1 kind: NodeClass spec: role: AmazonEKSAutoNodeRole # 또는 instanceProfile subnetSelectorTerms: - tags: {kubernetes.io/role/internal-elb: "1"} securityGroupSelectorTerms: - tags: {Type: worker-node} ephemeralStorage: size: 100Gi iops: 3000 throughput: 125 advancedNetworking: associatePublicIPAddress: false advancedCompute: kernel: sysctl: {vm.max_map_count: 262144} tags: {Environment: production}
role과 instanceProfile은 하나만AmazonEKSComputePolicy가 eks* 이름의 instance profile에만 권한을 주므로 이름을 eks로 시작합니다. 새 역할에는 access entry가 필요합니다.tags는 클러스터 역할 권한 필요CreateLaunchTemplate 권한 오류로 확인했습니다.| 필드 | 하는 일 | 주의 |
|---|---|---|
ephemeralStorage.kmsKeyID | 노드 root, data EBS를 고객 관리 키로 암호화 | 같은 리전 키. NVMe instance store와 앱 PVC에는 적용 안 됨 |
certificateBundles[].data | 사설 CA를 노드 신뢰 저장소에 추가 (base64) | 앱 컨테이너의 trust store는 바뀌지 않음 |
podSubnetSelectorTerms | Pod IP를 노드와 다른 subnet에서 할당 | podSecurityGroupSelectorTerms와 반드시 함께. namespace 단위가 아니라 NodeClass 단위 |
podSecurityGroupSelectorTerms | Pod 네트워크에 붙일 security group | |
advancedNetworking.associatePublicIPAddress | false면 노드에 공인 IP 없음 | outbound 경로(NAT, endpoint)는 따로 필요 |
amiFamily, amiSelectorTerms, blockDeviceMappings, userData, metadataOptions. OS는 AWS가 고르는 Bottlerocket 변형입니다.ephemeralStorage, 부팅 sysctl은 advancedCompute.kernel.sysctl, IMDS는 고정값(다음 장)hostNetwork Pod는 예외이니 앱 권한은 Pod Identity로 주고 리전 같은 값은 명시합니다.WorkerNodeMinimalPolicy와 ContainerRegistryPullOnly. 앱 권한을 노드 역할에 넣지 않습니다.weight는 Spot 비율을 정하지 않습니다.capacity-type: on-demand nodeSelector는 예약 용량을 제외합니다. preferred affinity는 비율을 보장하지 않습니다.preStop: sleep 90은 예산 대부분을 소비하는 안티패턴입니다.# NodeClass spec: capacityReservationSelectorTerms: - id: cr-0123456789abcdef0 # NodePool requirements: - key: karpenter.sh/capacity-type operator: In values: ["reserved"]
capacityReservationSelectorTerms를 쓰면 클러스터의 모든 NodeClass가 open ODCR 자동 사용을 멈춥니다. 예약은 쓰지 않아도 과금됩니다.apiVersion: karpenter.sh/v1 kind: NodePool spec: replicas: 4 # Pod 수요와 무관하게 유지 limits: nodes: 6 # 교체 여유 template: spec: nodeClassRef: {group: eks.amazonaws.com, kind: NodeClass, name: default} requirements: - {key: node.kubernetes.io/instance-type, operator: In, values: ["m5.xlarge"]}
limits는 nodes만, weight 설정 불가, consolidation 없음. replicas는 지울 수 없어 동적 풀로 되돌릴 수 없습니다.requirements: - key: eks.amazonaws.com/instance-gpu-manufacturer operator: In values: ["nvidia"] - key: node.kubernetes.io/instance-type operator: In values: ["g5.2xlarge", "g5.4xlarge"] taints: - key: nvidia.com/gpu effect: NoSchedule limits: nvidia.com/gpu: "4" disruption: consolidationPolicy: WhenEmpty consolidateAfter: 10m
amiFamily 지정도 없습니다.| 유형 | GPU | vCPU / RAM |
|---|---|---|
| g5.xlarge | A10G 1개 | 4 / 16 GiB |
| g5.2xlarge | A10G 1개 | 8 / 32 GiB |
| g5.12xlarge | A10G 4개 | 48 / 192 GiB |
advancedNetworking: networkInterfaces: - {networkCardIndex: 0, deviceIndex: 0, interfaceType: interface, secondaryIPv4PrefixCount: 1} - {networkCardIndex: 0, deviceIndex: 1, interfaceType: efa-only} - {networkCardIndex: 1, deviceIndex: 0, interfaceType: efa-only} placementGroupSelector: name: ml-training-pg
efa-only는 Pod IP를 쓰지 않음interface와 prefix가 맡습니다. Pod는 vpc.amazonaws.com/efa를 요청합니다.associatePublicIPAddress 조합 불가, 같은 NodeClass에 static hugepages 불가, EFA DRA 경로 미지원zonal-shift-config를 켜면 끝. 플래그, IAM 변경 없음. autoshift는 ARC에서 따로DoNotSchedule 같은 엄격한 AZ 제약은 그대로입니다. replica와 topology spread가 먼저입니다.karpenter-crd와 controller chart를 함께, 버전 문자열은 1.15.0ValidationSucceeded, 대시보드 label 변경Unhealthy. CRD enum이 바뀌므로 CRD를 함께 업그레이드InvalidKubeletConfiguration 으로 NotReadypodsPerCore, clusterDNS 2개 이상은 UnsupportedKubeletConfiguration reason 소문자화, consolidation_type 은 empty, single, multi, 미분류 error는 unknownBalanced 반영 여부 결정ContainerCreating에 머물지 않는지| log type | 무엇의 판단인가 |
|---|---|
AUTO_MODE_COMPUTE_LOGS | 관리형 Karpenter: 프로비저닝, disruption |
AUTO_MODE_BLOCK_STORAGE_LOGS | EBS CSI |
AUTO_MODE_LOAD_BALANCING_LOGS | Load Balancer controller |
AUTO_MODE_IPAM_LOGS | IP 주소 관리 |
PutDeliverySource, PutDeliveryDestination, CreateDelivery 순서. 목적지는 CloudWatch Logs, S3, FirehoseapiVersion: eks.amazonaws.com/v1alpha1 kind: NodeDiagnostic metadata: name: i-0abc1234def567890 # 노드 이름 spec: logCapture: destination: "https://...presigned S3 PUT URL"
.status.captureStatuses의 Success, SuccessWithErrors, Failuredestination: node도 받습니다.kubectl debug 디버그 컨테이너, 부팅 문제는 aws ec2 get-console-output. agent는 node condition과 event를 내고 자동 repair에 쓰입니다.# Auto Mode 노드만 kubectl get nodes -L eks.amazonaws.com/compute-type # NodeClaim condition (phase 필드는 없음) kubectl get nodeclaims -o wide kubectl describe nodeclaim <name> # 못 뜬 Pod kubectl get events -A \ --field-selector reason=FailedScheduling
LaunchedRegisteredInitializedReadyDriftedDisruptionBlockedUnconsolidatableFailedSchedulingNodeClassNotReadykarpenter_* 메트릭을 가정하지 않음Karpenter CloudWatch namespace가 자동으로 생기지 않습니다.eks-auto-mode/computeinclude-managed-resources로 조회| 할인 수단 | EC2 요금 | Auto Mode 관리비 |
|---|---|---|
| Compute Savings Plans | 적용 (최대 66%) | 미적용 |
| EC2 Instance Savings Plans | 적용 (최대 72%) | 미적용 |
| Reserved Instances | 적용 | 미적용 |
| Spot | Spot 가격 (SP 대상 아님) | 미적용 |
aws:eks:cluster-name*EKS-Auto:*-management-hours인 항목으로 거릅니다.tags로 팀 태그를 붙이거나 Kubecost 같은 도구로 Pod requests 기준 배분합니다.| 리소스 | 직접 설치한 controller | Auto Mode |
|---|---|---|
| StorageClass provisioner | ebs.csi.aws.com | ebs.csi.eks.amazonaws.com |
Service loadBalancerClass (NLB) | service.k8s.aws/nlb | eks.amazonaws.com/nlb |
| IngressClass controller (ALB) | ingress.k8s.aws/alb | eks.amazonaws.com/alb |
| TargetGroupBinding, IngressClassParams | elbv2.k8s.aws/v1beta1 | eks.amazonaws.com/v1 |
| NodeClass | karpenter.k8s.aws/v1 EC2NodeClass | eks.amazonaws.com/v1 NodeClass |
nodePools: []. 무제한 general-purpose가 Pending Pod를 먼저 가져가지 않게compute-type: auto selector. 저위험부터--force, --delete-emptydir-data로 오류를 덮지 않기eksctl delete nodegroup이 노드를 drain한 뒤 삭제합니다.eks.amazonaws.com/compute-type: auto selectornodepools.karpenter.sh, nodeclaims.karpenter.sh를 바꾸거나 지우지 않기karpenter.k8s.aws/instance-*를 eks.amazonaws.com/instance-*로 바꿉니다. 공존 중에는 양쪽 compute와 LB가 함께 과금됩니다.advancedCompute 지원 필드를 벗어남같은 조건, 두 클러스터: 스케일 아웃, Consolidation, Drift, Spot 중단
demo (eks.amazonaws.com/v1)Balanced, expireAfter 336hdefault + interruption queue (SQS)WhenEmptyOrUnderutilized, expireAfter 720hdemo: on-demand, c/m/r 계열, generation 5 초과, amd64, consolidateAfter 30s, budget nodes: "1". 워크로드: pause 컨테이너, replica당 1 vCPU / 1.5Gi
| 클러스터 | 선택한 노드 | NodeClaim 생성 (UTC) | 노드 Ready | Pod 10개 Ready |
|---|---|---|---|---|
| akd-auto (Auto Mode) | c7i-flex.4xlarge 1대 | 13:54:15 | +15초 | +28초 |
| akd-karp (Karpenter 1.15.0) | c7i-flex.4xlarge 1대 | 13:54:15 | +40초 | +47초 |
| 클러스터 | 바꾼 값 | Launch, Terminate, Launch, Terminate (UTC) | 완료 |
|---|---|---|---|
| akd-auto | advancedCompute.kubelet.maxPods: 64 | 14:20:12, 14:20:30, 14:20:45, 14:21:00 | +98초 |
| akd-karp | al2023@v20261003 → al2023@v20261012 | 14:20:10, 14:20:46, 14:20:57, 14:21:27 | +127초 |