🔸 배경
사이트 광고가 종료되어 eks 리소스가 감소되던 중 registry.k8s.io 의 장애로 인해 서비스에 영향이 발생했습니다. 당시 ingress-nginx가 외부 이미지 레지스트리를 직접 참조하고 있었고, 파드 재시작 시 이미지를 pull 하지 못해 ImagePullBackOff 상태에 빠지는 상황이 있었습니다
🔸 후속 조치
① Private Image Registry 전환
외부 레지스트리 의존성을 제거하기 위해 ingress-nginx 가 참조하는 이미지를 private registry 전환하여 관리하도록 변경했다. (이미지 pull이 외부 네트워크 장애에 영향받지 않도록 격리)
② ingress-nginx PodDisruptionBudget 설정
노드 드레인이나 롤링 업데이트 시 ingress-nginx 파드가 동시에 중단되는 것을 막기 위해 PodDisruptionBudget 을 설정했다. minAvailable 으로 최소 파드는 항상 Running 상태를 유지하도록 보장.
너무 간단한 후기라 추가로 아래 내용 정리하겠습니다!
Karpenter를 운영하다 보면 노드 프로비저닝이 예상대로 되지 않아 Pod가 Pending 상태에 묶이는 상황을 마주치게 된다. 이 글은 Karpenter 공식 트러블슈팅 문서를 바탕으로, 실제로 자주 마주치는 문제들을 유형별로 정리한 것이다.
문제가 발생했을 때 가장 먼저 해야 할 일은 로그 레벨을 debug로 올리는 것이다. Karpenter의 기본 로그 레벨은 info이므로 프로비저닝 실패의 세부 원인이 출력되지 않는 경우가 많다.
# 환경 변수로 즉시 변경
kubectl set env deployment/karpenter -n kube-system LOG_LEVEL=debug
# Helm 설치 시 옵션으로 지정하는 경우
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \
--set logLevel=debug \
...
주의: debug 로그는 엄청난 양의 로그를 생성하여 CloudWatch 비용 등에 영향을 줄 수 있다. 문제 분석이 끝나면 반드시
LOG_LEVEL=info로 되돌려야 한다.
EC2 Spot을 처음 사용하는 AWS 계정이라면, Spot용 Service Linked Role이 없어서 아래 오류가 발생한다.
AuthFailure.ServiceLinkedRoleCreationNotPermitted: The provided credentials do not have permission
to create the service-linked role for EC2 Spot Instances
해결 방법은 해당 Spot용 Role을 직접 생성하면 된다.
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com
Karpenter 설치 시 아래와 같은 오류가 나타난다면, Karpenter가 STS 엔드포인트에 접근하지 못하는 것이다. dnsPolicy: ClusterFirst로 설정된 상태에서 클러스터 내 DNS 서비스가 아직 기동되지 않았을 때 주로 발생한다.
WebIdentityErr: failed to retrieve credentials
caused by: Post "https://sts.us-east-1.amazonaws.com/": dial tcp: i/o timeout
두 가지 해결 방법이 있다.
방법 1: Karpenter의 dnsPolicy를 Default로 변경하여 VPC DNS를 직접 사용하도록 한다.
helm upgrade --install karpenter ... --set dnsPolicy=Default
방법 2: MNG 또는 Fargate가 DNS 파드 용량을 충분히 확보하도록 설정한다.
Karpenter 버전을 업그레이드한 뒤 CRD가 변경된 경우, 아래와 같은 오류가 발생할 수 있다.
Error from server (BadRequest): strict decoding error: unknown field "spec.template.spec.nodeClassRef.foo"
이 경우 공식 Upgrade Guide에 따라 CRD를 업데이트해야 한다.
no instance type met the scheduling requirements 로그가장 흔히 마주치는 로그 메시지 중 하나다. 이 메시지의 의미는 다음 두 가지 중 하나이다.
Case A — 인스턴스 타입/리소스 조건 불충족
Pod의 리소스 요청량, NodePool의 인스턴스 타입 제약, DaemonSet의 리소스 오버헤드가 합쳐져서 어떤 인스턴스 타입도 조건을 충족하지 못하는 상태다. NodePool의 requirements에 허용 인스턴스 타입이 너무 좁게 설정되어 있지는 않은지 확인한다.
Case B — 특정 AZ에서 해당 인스턴스 타입의 Offering 부재
had a required offering 문구가 함께 나타나면 AZ 문제다. Stateful 워크로드에서 EBS 볼륨이 연결된 AZ와 Pod가 스케줄링되려는 AZ가 다를 때 자주 발생한다. NodePool의 topology.kubernetes.io/zone 설정과 실제 서브넷 AZ를 대조해봐야 한다.
Security Groups for Pods를 사용하면 Karpenter가 노드를 정상 기동했음에도 Pod가 최대 30분 동안 ContainerCreating 상태에 머무는 현상이 발생할 수 있다. 이는 Karpenter와 amazon-vpc-resource-controller 간의 상호작용 문제다.
해결 방법은 NodePool에 아래 레이블을 추가하는 것이다.
apiVersion: karpenter.sh/v1
kind: NodePool
spec:
template:
metadata:
labels:
vpc.amazonaws.com/has-trunk-attached: "false"
PVC를 대량으로 사용할 때 볼륨 어태치 한도 초과로 스케일업에 실패할 수 있다. 두 가지 케이스가 있다.
인-트리 스토리지 플러그인 사용 중인 경우: Karpenter는 AWSElasticBlockStore 같은 인-트리 플러그인을 지원하지 않으며, 이 경우 노드의 최대 볼륨 어태치 수를 제대로 계산하지 못한다. 로그에 아래와 같은 에러가 나타나면 StorageClass와 PV를 CSI 드라이버로 마이그레이션해야 한다.
PersistentVolume source 'AWSElasticBlockStore' uses an in-tree storage plugin
which is unsupported by Karpenter
Kubernetes 스케줄러-CSINode 레이스 컨디션의 경우: 노드 등록 시 스케줄러가 CSINode보다 먼저 실행되어 실제보다 더 많은 볼륨을 어태치할 수 있다고 판단하는 경우다. topologySpreadConstraints와 podAntiAffinity를 적용해 단일 노드에 PVC가 집중되지 않도록 분산하는 것이 권장된다. EBS CSI 드라이버는 startupTaint를 지원하므로 NodePool에 아래와 같이 설정할 수 있다.
apiVersion: karpenter.sh/v1
kind: NodePool
spec:
template:
spec:
startupTaints:
- key: ebs.csi.aws.com/agent-not-ready
effect: NoExecute
Pod가 ContainerCreating 상태에서 멈추고 아래와 같은 에러가 나타나면 CNI의 IP 할당 실패다.
failed to assign an IP address to container
주요 원인은 두 가지다.
maxPods 설정이 인스턴스 지원 Pod 밀도를 초과하는 경우: ENI 수와 ENI당 IP 수의 곱이 실제 Pod 수용 한도다. 해결 방법으로는 Prefix Delegation 활성화, maxPods 값 조정, 또는 Security Groups for Pods 사용 시 RESERVED_ENIS=1 설정이 있다.
서브넷 IP 주소 소진의 경우: 특정 서브넷의 IP가 고갈된 상태다. topologySpreadConstraints로 파드를 AZ에 분산하거나, 서브넷의 CIDR 범위를 늘리거나, IPv6 전환을 고려할 수 있다.
아래 상황처럼 Pod의 NodePool 호환성과 실제 AZ 제약이 불일치하면 Karpenter가 일부 레플리카를 스케줄링하지 못한다.
us-east-1a, us-east-1b만 허용이 경우 Pod 스펙에 nodeAffinity를 명시해 eligible 도메인을 NodePool 범위와 일치시켜야 한다.
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ['us-east-1a', 'us-east-1b']
Karpenter 로그에서 아래와 같이 잘못된 블록 디바이스 매핑이나 기타 EC2 fleet 오류가 보인다면, 커스텀 Launch Template 설정을 점검해야 한다.
Could not launch node, launching instances, with fleet error(s),
InvalidBlockDeviceMapping: Invalid device name /dev/xvda
Karpenter는 아래 세 가지 조건이 모두 충족되어야 노드를 initialized로 판단한다.
Ready 컨디션이 True여야 한다.ec2:DescribeInstanceTypes로 조회된 리소스가 node.status.allocatable에 모두 등록되어 있어야 한다. GPU 인스턴스에서 NVIDIA 플러그인 데몬이 없거나, vpc.amazonaws.com/pod-eni 리소스가 등록되지 않는 경우가 대표적이다.startupTaints가 노드 spec에서 완전히 제거되어야 한다.노드가 뜨긴 했는데 클러스터에 Join하지 못하는 경우다. IAM 권한, Security Group, 네트워킹 설정을 점검해야 한다. AL2 기반 노드라면 SSM을 통해 직접 접속하여 kubelet 로그를 확인하는 것이 가장 빠르다.
자주 보이는 kubelet 오류 패턴은 다음과 같다.
# CNI 초기화 실패 → IAM 권한 문제 가능성
KubeletNotReady runtime network not ready: NetworkPluginNotReady
# API 서버 등록 실패 → aws-auth ConfigMap 설정 문제 가능성
Unable to register node with API server: Unauthorized
aws-auth ConfigMap에 Karpenter 노드 Role이 등록되어 있는지 확인한다.
kubectl get configmaps -n kube-system aws-auth -o yaml
커스텀 Launch Template에서 암호화된 EBS 볼륨을 사용하거나, EC2NodeClass의 Block Device Mapping에 CMK 암호화가 설정된 경우, 노드가 생성 직후 종료될 수 있다. KMS 키 정책에 해당 IAM 주체가 허용되어 있지 않은 경우다. EBS가 권장하는 방식은 특정 Role을 명시하는 대신 kms:ViaService 조건으로 계정 전체에 접근을 허용하는 것이다.
Karpenter와 AWS Node Termination Handler를 함께 운영하는 경우, Spot 재조정 권고(Rebalance Recommendation)에 NTH가 반응해서 노드를 삭제하면 Karpenter가 동일한 인스턴스 타입으로 다시 프로비저닝하고, 또다시 재조정 권고가 발생하는 무한 루프가 생길 수 있다.
Karpenter 공식 권장사항은 Spot 노드 운영 시 Spot Rebalance Recommendation 대응을 비활성화하는 것이다. NTH를 유지해야 한다면 아래 설정을 적용한다.
enableSpotInterruptionDraining: false
enableRebalanceDraining: false
Karpenter가 노드를 삭제하지 않는 주요 원인은 다음과 같다.
초기화 미완료: karpenter.sh/initialized 레이블이 없는 노드는 Karpenter가 deprovisioning 대상으로 고려하지 않는다. 위의 노드 초기화 문제를 참고한다.
PDB(Pod Disruption Budget) 차단: Karpenter는 PDB를 존중하여 Pod를 강제 삭제하지 않는다. PDB의 minAvailable이나 maxUnavailable 설정으로 인해 파드 Eviction이 불가능한 경우 노드 삭제가 진행되지 않는다.
karpenter.sh/do-not-disrupt annotation: annotation이 붙은 Pod가 있는 노드는 Consolidation 대상에서 제외된다. 삭제를 원하면 해당 어노테이션을 제거한다.
스케줄링 제약으로 인한 Consolidation 불가: Pod를 다른 노드로 이동시키려 했을 때 inter-pod affinity/anti-affinity나 TopologySpreadConstraint로 인해 스케줄링이 불가한 경우, Consolidation이 실행되지 않는다.
IAM 권한을 수정하거나 서브넷/보안그룹 태그를 변경한 경우, Karpenter가 이전 상태를 캐시하고 있을 수 있다. 아래 명령어로 강제 재검증을 트리거할 수 있다.
kubectl annotate ec2nodeclass default \
karpenter.sh/force-refresh="$(date +%s)" \
--overwrite
IGW나 NAT Gateway 없이 완전히 격리된 프라이빗 서브넷에서 Karpenter를 운영하는 경우, 아래와 같은 Pricing API 타임아웃 오류가 주기적으로 발생한다.
ERROR controller.aws.pricing updating on-demand pricing
caused by: Post "https://api.pricing.us-east-1.amazonaws.com/": dial tcp: i/o timeout
using existing pricing data from 2022-08-17T00:19:52Z
Price List Query API의 VPC 엔드포인트가 존재하지 않기 때문에 발생하는 문제다. Karpenter는 바이너리에 내장된 가격 데이터를 폴백으로 사용하므로 즉각적인 장애로 이어지지는 않지만, 가격 데이터가 Karpenter 버전 업그레이드 시에만 갱신된다. 오류 메시지를 없애려면 환경 변수를 설정한다.
# 격리 VPC 환경에서 Pricing 조회 비활성화
AWS_ISOLATED_VPC=true
# 또는 --aws-isolated-vpc 플래그 사용
Karpenter로 인해 Pod가 Pending 상태에 머물거나 노드 프로비저닝이 실패할 때, 아래 순서로 확인하면 대부분의 문제를 빠르게 좁혀갈 수 있다.
| 단계 | 확인 명령 | 주목할 시그널 |
|---|---|---|
| 1. Pod 이벤트 확인 | kubectl describe pod <pod> | FailedScheduling 여부 |
| 2. NodeClaim 존재 확인 | kubectl get nodeclaim | No resources found이면 Karpenter가 요청 자체를 안 만든 것 |
| 3. Karpenter 로그 확인 | kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter | ERROR, WARN, discovered subnets [] |
| 4. debug 로그 활성화 | kubectl set env deployment/karpenter -n kube-system LOG_LEVEL=debug | 세부 원인 파악 |
| 5. EC2NodeClass 설정 확인 | kubectl get ec2nodeclass default -o yaml | subnetSelectorTerms, securityGroupSelectorTerms 태그 |
| 6. 실제 리소스 태그 대조 | aws ec2 describe-subnets --filters ... | 서브넷에 태그가 실제로 붙어 있는지 |
| 7. IAM 정책 확인 | aws sts decode-authorization-message | 누락된 권한 액션 |
| 8. NodePool 설정 검토 | kubectl get nodepool default -o yaml | spot-only 여부, 허용 인스턴스 타입, AZ 범위 |