Published on

Terraform apply 후 EKS 노드 NotReady - CNI·IRSA·보안그룹 점검

Authors

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 출력에서 특히 아래를 찾습니다.

  • Ready Condition이 False
  • Message에 자주 나오는 키워드
    • NetworkPluginNotReady
    • cni plugin not initialized
    • container runtime network not ready
    • failed 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 실패가 찍힙니다.

  • AccessDeniedException
  • UnauthorizedOperation
  • failed to get eni
  • failed to attach eni

이 경우는 CNI가 사용할 IAM 권한이 부족한 경우가 많습니다.

3) IRSA(또는 Node IAM Role) 확인: CNI가 어떤 권한으로 동작하는가

EKS에서 CNI 권한을 주는 방법은 크게 두 갈래입니다.

  • (권장) IRSAaws-node ServiceAccount에 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가지가 맞아야 합니다.

  1. 클러스터에 OIDC Provider가 연결되어 있어야 함
  2. IAM Role의 Trust Policy가 해당 OIDC와 system:serviceaccount:kube-system:aws-node를 허용해야 함
  3. 파드가 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을 쓰는 경우: 정책 누락 점검

노드 인스턴스 프로파일에 아래 정책들이 최소로 붙어 있는지 확인합니다.

  • AmazonEKSWorkerNodePolicy
  • AmazonEC2ContainerRegistryReadOnly
  • (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 초기화의 교차점에서 터지는 경우가 대부분입니다. 따라서 다음 순서로 보면 시행착오가 확 줄어듭니다.

  1. kubectl describe nodeCondition 메시지를 먼저 고정
  2. aws-node 상태/로그로 CNI 라인을 최우선 분기
  3. IRSA(OIDC/Trust/ST S) 또는 Node Role 정책으로 권한 라인 확정
  4. SG/NACL/라우팅/NAT/VPC 엔드포인트로 네트워크 라인 마무리

운영 중 장애 대응 관점에서 네트워크/타임아웃 진단 체크리스트가 필요하다면, 원인 분해 방식은 ALB/프록시 계층에서도 동일하게 유효합니다. 예를 들어 AWS ALB 502·504 난사 - 원인별 해결 체크리스트처럼 “계층별로 관측 지점을 고정”하는 접근이 EKS NotReady에도 그대로 통합니다.