Published on

EKS 노드가 Join 못할 때 - bootstrap.sh 실패 진단

Authors

서론

EKS에서 노드가 Ready로 올라오지 않고, EC2 인스턴스는 떴는데 클러스터에는 노드가 보이지 않거나(kubectl get nodes에 미등장), Auto Scaling Group이 계속 인스턴스를 교체하는 상황을 종종 만납니다. 이때 핵심은 대부분 노드 부팅 과정에서 실행되는 /etc/eks/bootstrap.sh가 실패했거나, bootstrap은 성공했지만 kubelet이 API Server에 등록(register)하지 못한 케이스입니다.

이 글은 “노드가 Join 못할 때”를 bootstrap.sh 관점에서 빠르게 좁혀가는 체크리스트와, 실제로 많이 터지는 원인(인자/AMI/네트워크/IAM/인증서/시간 동기화/컨테이너 런타임)을 재현 가능한 커맨드로 정리합니다.

> 참고: 노드가 Join은 했는데 NotReady로 남는 케이스는 CNI/ENI가 원인인 경우가 많습니다. 그 경우는 EKS Node NotReady - CNI ENI 할당 실패 해결 가이드도 함께 보세요.

증상 패턴: “Join 실패”를 어떻게 구분할까

Join 실패는 크게 3가지로 나뉩니다.

  1. 클러스터에 노드가 아예 안 보임

    • kubectl get nodes에 없음
    • ASG가 새 인스턴스를 계속 띄웠다 지움(헬스체크 실패)
  2. 노드는 보이는데 NotReady

    • bootstrap은 어느 정도 성공했으나 CNI, kube-proxy, container runtime, DNS 등 후속 구성에서 실패
  3. 노드가 잠깐 보였다가 사라짐

    • kubelet이 죽거나 인증 갱신/네트워크 문제로 지속 연결 실패

이 글은 1번(아예 등록 실패)과 3번(등록 지속 실패)에서 특히 중요한 bootstrap.sh 실패를 중심으로 다룹니다.

가장 먼저 볼 로그: bootstrap.sh와 kubelet

EKS 최적화 AMI(amazon-eks-node) 기준으로, bootstrap과 kubelet 관련 로그는 다음을 우선 확인합니다.

1) cloud-init 출력(UserData 실행 로그)

sudo tail -n 200 /var/log/cloud-init-output.log

여기에 bootstrap.sh 실행 라인과 에러가 그대로 찍히는 경우가 많습니다.

2) bootstrap 스크립트 자체 로그/실행 흔적

환경/AMI에 따라 다르지만 보통 cloud-init에서 호출되므로 cloud-init 로그가 1차입니다. 추가로 스크립트를 직접 열어 어떤 인자를 기대하는지 확인합니다.

sudo sed -n '1,200p' /etc/eks/bootstrap.sh

3) kubelet systemd 로그

sudo systemctl status kubelet --no-pager
sudo journalctl -u kubelet -b --no-pager | tail -n 200

Join이 안 될 때는 kubelet 로그에서 다음 키워드가 자주 보입니다.

  • Unable to register node with API server
  • x509: certificate signed by unknown authority
  • dial tcp ... i/o timeout
  • Unauthorized
  • No such file or directory (kubeconfig, bootstrap kubeconfig)

원인 1: UserData에서 bootstrap.sh 인자/형식이 틀림

가장 흔한 실수는 클러스터 이름, API 서버 엔드포인트, CA 번들, kubelet extra args를 잘못 넘기는 것입니다.

관리형 노드 그룹(Managed Node Group)이라면

대부분 AWS가 UserData를 구성해주므로, 커스텀 AMI나 Launch Template을 섞는 순간부터 위험해집니다. Launch Template을 사용할 때는 “EKS가 넣어주는 UserData를 덮어쓰지 않았는지”가 핵심입니다.

  • Launch Template에서 UserData를 직접 지정하면, EKS가 생성하는 bootstrap UserData가 대체될 수 있습니다.
  • 해결: EKS 문서대로 MIME multi-part로 병합하거나, EKS가 생성한 UserData를 기반으로 필요한 부분만 추가합니다.

자가관리 노드(ASG)라면: 최소 예시

다음은 가장 기본적인 형태입니다.

#!/bin/bash
set -o xtrace

/etc/eks/bootstrap.sh my-eks-cluster \
  --kubelet-extra-args '--node-labels=role=worker --max-pods=110'

여기서 흔한 문제:

  • 클러스터 이름 오타
  • --kubelet-extra-args에 따옴표가 깨져서 systemd drop-in이 망가짐
  • --max-pods를 CNI 설정과 불일치하게 과대 설정

따옴표/이스케이프 깨짐 확인

/etc/systemd/system/kubelet.service.d/10-kubelet-args.conf 같은 drop-in 파일이 생성되는데, 값이 깨져 있으면 kubelet 자체가 실행되지 않습니다.

sudo ls -al /etc/systemd/system/kubelet.service.d/
sudo cat /etc/systemd/system/kubelet.service.d/*.conf
sudo systemctl daemon-reload
sudo systemctl restart kubelet

원인 2: 노드 IAM Role이 필요한 권한을 못 가짐

bootstrap.sh는 대략 다음을 수행합니다.

  • 클러스터 엔드포인트/CA를 얻어 kubeconfig 생성
  • kubelet 실행
  • CNI 플러그인/필수 데몬셋이 동작할 수 있도록 노드 IAM 권한 필요

노드 Role에 최소로 아래 정책들이 붙어있는지 확인합니다.

  • AmazonEKSWorkerNodePolicy
  • AmazonEKS_CNI_Policy
  • AmazonEC2ContainerRegistryReadOnly

추가로 SSM으로 접속하려면 AmazonSSMManagedInstanceCore가 필요합니다.

“Unauthorized”로 등록 실패하는 케이스

노드 Role은 있어도, aws-auth ConfigMap에 매핑이 없으면 kubelet이 인증을 못 합니다(특히 자가관리 노드).

kubectl -n kube-system get configmap aws-auth -o yaml

예시(자가관리 노드 Role 매핑):

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: arn:aws:iam::123456789012:role/my-eks-node-role
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes

Managed Node Group는 보통 자동 반영되지만, 커스텀 구성에서 누락될 수 있습니다.

원인 3: 네트워크/보안그룹/엔드포인트 접근 문제

kubelet이 Join하려면 노드에서 EKS API Server(443) 로 나갈 수 있어야 합니다.

체크 1) DNS와 라우팅

노드에서 API 서버 도메인을 resolve하고 TCP 연결이 되는지 확인합니다.

CLUSTER_ENDPOINT="https://XXXXXXXX.gr7.ap-northeast-2.eks.amazonaws.com"
HOST=$(echo $CLUSTER_ENDPOINT | sed 's#https://##')

getent hosts $HOST
curl -vk https://$HOST/healthz
  • getent hosts가 실패하면 VPC DNS 설정, Route 53 Resolver, DHCP options, CoreDNS 문제가 아니라 노드 OS 레벨 DNS 문제일 수 있습니다.
  • curl이 timeout이면 보안그룹/NACL/라우팅/프록시를 의심합니다.

체크 2) 프라이빗 엔드포인트 사용 시

클러스터 엔드포인트 접근이 Private only인데 노드가 다른 VPC/서브넷 경로에 있거나, 인터페이스 엔드포인트/DNS가 꼬이면 Join이 안 됩니다.

  • 노드 서브넷이 클러스터와 같은 VPC인지
  • VPC 내에서 EKS 엔드포인트로 라우팅 가능한지
  • 사내 프록시/방화벽이 443을 MITM하는지(이 경우 x509 에러로 이어짐)

체크 3) 보안그룹 규칙

노드 SG는 보통 “노드 간 통신”과 “컨트롤 플레인과 통신”이 필요합니다.

  • 노드 → API server 443 아웃바운드 허용
  • (클러스터 SG가 별도라면) 클러스터 SG 인바운드에 노드 SG 허용

EKS는 관리형으로 어느 정도 자동 구성하지만, 기존 SG를 재사용하거나 제한적으로 구성하면 쉽게 막힙니다.

원인 4: 시간 동기화(NTP) 문제로 TLS 실패

의외로 빈도가 있습니다. 노드 시간이 크게 틀어지면 EKS API 서버 TLS 검증에서 실패합니다.

date
sudo timedatectl status

NTP가 막혀 있거나 chrony 설정이 꼬였으면 수정합니다.

sudo systemctl status chronyd --no-pager
sudo chronyc sources -v

원인 5: AMI/쿠버네티스 버전 불일치 또는 커스텀 AMI 구성 누락

EKS는 버전별로 권장 AMI가 있고, bootstrap 스크립트/컨테이너 런타임 설정이 그 전제를 따릅니다.

  • EKS 1.24+는 Docker shim 제거 이후 containerd 기반이 일반적
  • 커스텀 AMI에서 containerd, iptables, required kernel modules가 누락되면 kubelet이 떠도 정상 Join이 안 될 수 있음

containerd 상태 확인

sudo systemctl status containerd --no-pager
sudo crictl info | head

crictl이 없다면 설치가 안 되었거나 PATH 문제일 수 있습니다.

kubelet이 참조하는 kubeconfig 경로 확인

ps -ef | grep kubelet | grep -v grep
sudo ls -al /var/lib/kubelet/
sudo cat /var/lib/kubelet/kubeconfig 2>/dev/null || true

kubeconfig가 없거나 CA 경로가 잘못되면 Join 불가입니다.

원인 6: bootstrap.sh가 클러스터 정보를 못 가져옴(aws cli/IMDS 문제)

bootstrap.sh는 인스턴스 메타데이터(IMDS)와 AWS API 호출에 의존합니다.

IMDSv2 토큰/접근 차단

Launch Template에서 IMDS를 “v2 required”로 해두고, OS/스크립트가 토큰을 못 가져오면 메타데이터 조회가 실패합니다.

# IMDSv2 토큰 발급
TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

# 메타데이터 확인
curl -sH "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/instance-id

여기서 막히면 IMDS 설정/라우팅/호스트 방화벽을 확인해야 합니다.

aws cli 호출 실패(권한/리전)

노드 Role에 eks:DescribeCluster 권한이 없거나, 리전이 잘못 잡히면 클러스터 정보를 못 가져옵니다.

aws sts get-caller-identity
aws eks describe-cluster --name my-eks-cluster --region ap-northeast-2 | head
  • AccessDenied면 IAM 정책 문제
  • ResourceNotFoundException이면 클러스터 이름/리전 문제

원인 7: 엔드포인트 CA/프록시로 인한 x509 오류

로그에 아래처럼 나오면:

  • x509: certificate signed by unknown authority
  • x509: certificate is valid for ... (SNI/프록시)

대개는 다음 중 하나입니다.

  • 회사 프록시가 TLS를 가로채며 자체 CA로 재서명
  • 노드가 잘못된 CA 번들을 사용
  • DNS가 엉뚱한 곳으로 향함(사설 DNS 오염)

우선 API 서버 인증서 체인을 확인합니다.

HOST=XXXXXXXX.gr7.ap-northeast-2.eks.amazonaws.com
echo | openssl s_client -connect ${HOST}:443 -servername ${HOST} 2>/dev/null | openssl x509 -noout -issuer -subject -dates

issuer가 예상과 다르면(기업 프록시 CA 등) 네트워크 경로를 정리하거나 프록시 예외 처리(NO_PROXY)가 필요합니다.

빠른 복구 전략: “원인 좁히기”를 위한 재부팅/재실행

문제를 고칠 때마다 인스턴스를 새로 띄우는 대신, 현재 인스턴스에서 빠르게 검증할 수 있는 순서입니다.

  1. cloud-init 로그에서 실패 지점 확인
  2. kubelet/containerd 상태 확인
  3. API 서버 연결 테스트(curl)
  4. aws eks describe-cluster 성공 여부 확인
  5. bootstrap 재실행(주의: 설정 덮어쓸 수 있음)

bootstrap 재실행 예시

sudo /etc/eks/bootstrap.sh my-eks-cluster \
  --kubelet-extra-args '--node-labels=debug=true'

sudo systemctl daemon-reload
sudo systemctl restart kubelet
sudo journalctl -u kubelet -b --no-pager | tail -n 100

재실행은 편하지만, 이미 생성된 kubelet 설정을 덮어쓸 수 있으니 운영 환경에서는 “새 인스턴스에서 수정된 UserData로 검증”이 더 안전합니다.

실전에서 자주 맞는 조합 이슈 3가지

1) Launch Template에서 UserData를 덮어씀 → bootstrap 호출 자체가 없음

  • cloud-init-output.log/etc/eks/bootstrap.sh 라인이 없다
  • 해결: EKS가 생성하는 UserData를 유지하고 추가 스크립트만 append

2) Private endpoint + 제한 SG/NACL → API server timeout

  • kubelet 로그: i/o timeout
  • curl -vk https://.../healthz도 timeout
  • 해결: 노드 서브넷 라우팅/SG/NACL, VPC DNS, 엔드포인트 설정 점검

3) aws-auth 누락(특히 자가관리) → Unauthorized

  • kubelet 로그: Unauthorized / forbidden
  • 해결: aws-auth에 node role 매핑 추가

Join 이후에도 불안정하면: 다음 단계 체크

노드가 Join은 했는데 워크로드가 이상하거나, 네트워크 계층에서 5xx가 보인다면 데이터 플레인/인그레스/로드밸런서 문제로 이어질 수 있습니다. 예를 들어 NLB 타겟이 Unhealthy로 유지되거나, ALB에서 504가 나는데 Pod는 정상인 상황은 Join 문제와 별개로 “트래픽 경로”를 봐야 합니다.

결론

EKS에서 노드가 Join하지 못할 때는 “쿠버네티스가 어렵다”기보다, 대부분 부팅 자동화(bootstrap.sh) → kubelet → API 서버 연결 중 어디에서 끊겼는지를 빠르게 특정하는 게임입니다.

  • cloud-init-output.logjournalctl -u kubelet로 실패 지점을 먼저 잡고
  • (1) UserData/Launch Template 덮어쓰기 여부
  • (2) 노드 IAM + aws-auth 매핑
  • (3) API 서버까지의 네트워크(TLS 포함)
  • (4) AMI/containerd/kubelet 구성

이 4가지를 순서대로 확인하면, “노드가 안 붙는” 문제의 대부분은 30분 안에 원인을 좁힐 수 있습니다.