- Published on
Terraform apply 후 EKS 노드 NotReady - CNI·IRSA·보안그룹 점검
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Terraform으로 EKS 클러스터와 노드그룹(Managed Node Group 또는 self-managed ASG)까지 apply가 끝났는데, 정작 노드가 NotReady에서 멈추는 경우가 종종 있습니다. 이때 중요한 건 “노드가 EC2로는 떠 있는데 Kubernetes에서 Ready가 안 되는” 원인을 계층적으로 분리하는 것입니다.
대부분의 케이스는 다음 셋 중 하나(혹은 조합)로 수렴합니다.
- CNI(aws-node) 문제: Pod 네트워킹이 안 떠서 노드가
NetworkPluginNotReady로 남음 - IRSA/IAM 권한 문제: CNI 또는 kube-proxy가 AWS API 호출에 실패해 네트워크 구성/ENI 할당이 막힘
- 보안그룹/라우팅/DNS 문제: 노드 ↔ API Server, 노드 ↔ STS/ECR, 노드 ↔ VPC DNS 통신이 막힘
아래는 “재현이 어렵고 로그가 흩어져 있는” EKS NotReady를 30분 내로 원인 좁히는 순서로 정리한 체크리스트입니다.
1) 먼저 증상을 ‘정확한 메시지’로 고정하기
가장 먼저 해야 할 일은 NotReady라는 결과를 만드는 조건(Condition) 메시지를 확인하는 것입니다.
kubectl get nodes -o wide
kubectl describe node <node-name>
describe 출력에서 특히 아래를 찾습니다.
ReadyCondition이False- Message에 자주 나오는 키워드
NetworkPluginNotReadycni plugin not initializedcontainer runtime network not readyfailed to setup network for sandbox
그리고 시스템 파드 상태를 봅니다.
kubectl -n kube-system get pods -o wide
kubectl -n kube-system get ds
여기서 **aws-node(amazon-vpc-cni)**가 CrashLoopBackOff/ImagePullBackOff/Pending이면, NotReady의 1차 원인은 거의 CNI 라인입니다.
2) CNI(amazon-vpc-cni)부터: aws-node DaemonSet 로그/이벤트
EKS에서 노드가 Ready가 되려면 CNI가 정상 기동되어 노드에 Pod 네트워크가 초기화되어야 합니다. CNI가 깨지면 노드는 NotReady로 남는 경우가 많습니다.
# aws-node 파드 확인
kubectl -n kube-system get pods -l k8s-app=aws-node -o wide
# 특정 노드에 뜬 aws-node 로그
kubectl -n kube-system logs -l k8s-app=aws-node --tail=200
# 이벤트로도 힌트가 잘 나옵니다
kubectl -n kube-system get events --sort-by=.lastTimestamp | tail -n 50
2-1) 이미지 풀 실패(ImagePullBackOff)면: ECR/프록시/엔드포인트
ImagePullBackOff가 보이면 노드가 ECR(또는 퍼블릭 레지스트리)에 접근하지 못하는 것입니다.
- 노드 서브넷이 프라이빗인데 NAT 게이트웨이/라우팅이 없음
- VPC 엔드포인트(ECR API, ECR DKR, S3)가 없고 NAT도 없음
- 보안그룹/NACL이 443 아웃바운드를 막음
진단 포인트:
# 노드에서 직접 확인(SSM 또는 SSH)
curl -I https://api.ecr.<region>.amazonaws.com
curl -I https://sts.<region>.amazonaws.com
nslookup kubernetes.default.svc
프라이빗 클러스터/프라이빗 노드 환경이라면, 최소한 아래 중 하나가 필요합니다.
- NAT Gateway를 통한 인터넷 egress
- 또는 VPC Interface Endpoint:
com.amazonaws.<region>.ecr.api,ecr.dkr,sts - 그리고 Gateway Endpoint:
s3(ECR 레이어 다운로드에 영향)
2-2) CrashLoopBackOff면: IRSA/IAM 권한 또는 CNI 설정
aws-node가 뜨긴 뜨는데 CrashLoop라면, 로그에 대개 AWS API 실패가 찍힙니다.
AccessDeniedExceptionUnauthorizedOperationfailed to get enifailed to attach eni
이 경우는 CNI가 사용할 IAM 권한이 부족한 경우가 많습니다.
3) IRSA(또는 Node IAM Role) 확인: CNI가 어떤 권한으로 동작하는가
EKS에서 CNI 권한을 주는 방법은 크게 두 갈래입니다.
- (권장) IRSA로
aws-nodeServiceAccount에 IAM Role을 붙임 - (구식/간편) 노드 인스턴스 프로파일(Node IAM Role)에 권한을 얹음
둘 중 무엇을 쓰는지부터 확인합니다.
kubectl -n kube-system get sa aws-node -o yaml | sed -n '1,120p'
여기서 아래 어노테이션이 있으면 IRSA 사용입니다.
eks.amazonaws.com/role-arn: arn:aws:iam::<acct>:role/<role>
3-1) IRSA를 쓰는데도 실패하면: OIDC Provider/Trust Policy/토큰
IRSA가 동작하려면 3가지가 맞아야 합니다.
- 클러스터에 OIDC Provider가 연결되어 있어야 함
- IAM Role의 Trust Policy가 해당 OIDC와
system:serviceaccount:kube-system:aws-node를 허용해야 함 - 파드가 projected service account token을 통해 STS AssumeRoleWithWebIdentity를 성공해야 함
Terraform에서 OIDC를 만들었는지 확인(예: aws_iam_openid_connect_provider). 그리고 Trust Policy는 이런 형태여야 합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<acct>:oidc-provider/oidc.eks.<region>.amazonaws.com/id/<oidc-id>"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.<region>.amazonaws.com/id/<oidc-id>:sub": "system:serviceaccount:kube-system:aws-node"
}
}
}
]
}
추가로, 프라이빗 환경에서 STS 엔드포인트 접근이 막히면 IRSA가 실패합니다. 이때는 sts VPC 엔드포인트 또는 NAT가 필요합니다.
3-2) 노드 IAM Role을 쓰는 경우: 정책 누락 점검
노드 인스턴스 프로파일에 아래 정책들이 최소로 붙어 있는지 확인합니다.
AmazonEKSWorkerNodePolicyAmazonEC2ContainerRegistryReadOnly- (CNI를 노드롤로 처리한다면)
AmazonEKS_CNI_Policy
Terraform 예시(Managed Node Group 역할):
resource "aws_iam_role_policy_attachment" "worker_node" {
role = aws_iam_role.node.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
}
resource "aws_iam_role_policy_attachment" "ecr_readonly" {
role = aws_iam_role.node.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
}
resource "aws_iam_role_policy_attachment" "cni" {
role = aws_iam_role.node.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
}
IRSA와 노드롤 방식을 섞으면 “권한은 있는데도 특정 파드만 실패” 같은 애매한 증상이 나올 수 있으니, 운영 정책을 하나로 정리하는 편이 안정적입니다.
4) 보안그룹/네트워크: 노드가 API Server에 붙을 수 있는가
노드가 Ready가 되려면 최소한 아래 통신이 성립해야 합니다.
- 노드 → EKS API Server(클러스터 엔드포인트) : TCP 443
- 노드 → kube-dns(CoreDNS) : 클러스터 내부 통신
- 노드 → STS/ECR/S3(이미지 풀/IRSA/애드온) : TCP 443
4-1) 클러스터 엔드포인트 접근(퍼블릭/프라이빗 설정)
EKS 클러스터 엔드포인트가 프라이빗 전용인데 노드가 해당 VPC/서브넷에서 라우팅이 안 되거나, 보안그룹이 막고 있으면 노드는 API Server에 못 붙습니다.
확인할 것:
endpointPrivateAccess=true인지endpointPublicAccess=false인지- 노드 서브넷이 클러스터와 같은 VPC인지
- 노드 SG에서 아웃바운드 443이 열려 있는지
노드에서 API 서버 DNS를 확인해보면 힌트가 큽니다.
# 노드에서
CLUSTER_ENDPOINT=$(aws eks describe-cluster --name <cluster> --query 'cluster.endpoint' --output text)
curl -k -I "$CLUSTER_ENDPOINT"
nslookup $(echo "$CLUSTER_ENDPOINT" | sed 's#https://##')
4-2) 보안그룹 규칙: ‘노드 ↔ 노드’와 ‘노드 ↔ 컨트롤플레인’
EKS는 관리형 컨트롤플레인이라 SG가 두 축으로 나뉩니다.
- Cluster security group (EKS가 관리)
- Node security group (노드가 속함)
일반적으로 EKS 모듈을 쓰면 필요한 규칙을 자동 생성하지만, 커스텀 SG를 붙이거나 규칙을 강하게 잠그면 문제가 생깁니다.
최소 권장(개념적으로):
- 노드 SG 인바운드: 노드 SG 자신(self)로부터의 통신(특히 10250, 443, 53 등)
- 노드 SG 아웃바운드: 443 포함(최소한 API/ST S/ECR)
- 컨트롤플레인 ↔ 노드: kubelet(10250) 경로가 막히면 등록/헬스체크가 꼬일 수 있음
NACL도 함께 봐야 합니다. SG는 허용인데 NACL에서 ephemeral port를 막아 “간헐적 실패”가 나오기도 합니다.
5) Terraform 관점에서 자주 터지는 포인트(의존성/애드온 순서)
Terraform으로 EKS를 만들 때 NotReady가 나는 흔한 패턴은 “클러스터는 만들어졌는데 애드온/IRSA/보안그룹이 완성되기 전에 노드가 먼저 올라오는” 경우입니다.
5-1) aws-auth ConfigMap 누락/오류
노드가 클러스터에 조인하려면(특히 self-managed) aws-auth에 노드 역할이 매핑되어야 합니다.
kubectl -n kube-system get configmap aws-auth -o yaml
mapRoles에 노드 IAM Role ARN이 없으면 조인이 안 되며, 노드는 EC2로는 살아있어도 Kubernetes에는 정상 등록이 안 되거나 상태가 이상해집니다.
Terraform에서 aws-auth를 관리한다면 apply 순서/권한(프로바이더가 클러스터 접근 가능한 시점)을 보장해야 합니다.
5-2) EKS Add-on(특히 vpc-cni) 버전/충돌
EKS Add-on으로 vpc-cni를 관리하는데, 기존 Helm/매니페스트 설치와 충돌하거나 버전이 맞지 않으면 aws-node가 불안정해질 수 있습니다.
aws eks list-addons --cluster-name <cluster>
aws eks describe-addon --cluster-name <cluster> --addon-name vpc-cni
문제가 의심되면 한 번 “단일 소스”로 정리하세요.
- EKS Add-on으로 관리할 거면 기존 수동 설치 제거
- Helm으로 관리할 거면 Add-on 제거
6) 빠른 복구 루틴: 원인별 처방전
아래는 현장에서 많이 쓰는 “원인 → 조치” 매핑입니다.
6-1) aws-node가 AccessDenied
- IRSA 사용 시: OIDC Provider/Trust Policy/SA annotation 확인
- Node Role 사용 시:
AmazonEKS_CNI_Policy부착 - 프라이빗 환경: STS VPC 엔드포인트 또는 NAT 추가
6-2) aws-node가 ImagePullBackOff
- NAT 또는 ECR/S3 VPC 엔드포인트 구성
- SG/NACL에서 443 egress 허용
- 프록시 환경이면 containerd/docker proxy 설정 점검
6-3) 노드가 API Server에 연결 실패
- 클러스터 endpoint 접근 설정(Private/Public) 재검토
- 노드 서브넷 라우팅/SG/NACL에서 443 경로 확인
7) 실전 디버깅 커맨드 모음(복붙용)
# 1) 노드 상태/조건
kubectl get nodes -o wide
kubectl describe node <node>
# 2) 시스템 파드
kubectl -n kube-system get pods -o wide
kubectl -n kube-system get ds
# 3) aws-node 로그
kubectl -n kube-system logs -l k8s-app=aws-node --tail=200
# 4) aws-auth
kubectl -n kube-system get cm aws-auth -o yaml
# 5) EKS 애드온 상태
aws eks list-addons --cluster-name <cluster>
aws eks describe-addon --cluster-name <cluster> --addon-name vpc-cni
# 6) 노드에서 네트워크(SSM/SSH)
curl -I https://sts.<region>.amazonaws.com
curl -I https://api.ecr.<region>.amazonaws.com
nslookup kubernetes.default.svc
8) 마무리: NotReady는 ‘CNI/권한/네트워크’로 분해하면 빨라진다
EKS 노드 NotReady는 겉보기엔 Kubernetes 문제처럼 보이지만, 실제로는 AWS 네트워크/권한과 Kubernetes CNI 초기화의 교차점에서 터지는 경우가 대부분입니다. 따라서 다음 순서로 보면 시행착오가 확 줄어듭니다.
kubectl describe node로 Condition 메시지를 먼저 고정aws-node상태/로그로 CNI 라인을 최우선 분기- IRSA(OIDC/Trust/ST S) 또는 Node Role 정책으로 권한 라인 확정
- SG/NACL/라우팅/NAT/VPC 엔드포인트로 네트워크 라인 마무리
운영 중 장애 대응 관점에서 네트워크/타임아웃 진단 체크리스트가 필요하다면, 원인 분해 방식은 ALB/프록시 계층에서도 동일하게 유효합니다. 예를 들어 AWS ALB 502·504 난사 - 원인별 해결 체크리스트처럼 “계층별로 관측 지점을 고정”하는 접근이 EKS NotReady에도 그대로 통합니다.