Published on

EKS Bottlerocket 노드 NotReady일 때 로그 수집법

Authors

서버리스처럼 보이지만, 장애는 결국 노드에서 터집니다. 특히 Bottlerocket은 “컨테이너 호스트 OS”라는 철학 때문에 일반적인 Amazon Linux 노드처럼 ssh로 들어가 /var/log를 뒤지는 방식이 통하지 않습니다. 노드가 NotReady가 되면 더 난감해집니다. kubectl logs는 파드가 살아있어야 의미가 있고, 노드 자체의 kubelet/containerd 로그가 핵심인데 접근 경로가 제한적이기 때문입니다.

이 글은 EKS + Bottlerocket 환경에서 노드가 NotReady일 때, “지금 당장” 수집해야 할 로그와 메트릭을 재현 가능한 절차로 정리합니다. 목표는 두 가지입니다.

  1. 노드가 NotReady가 된 직접 원인(네트워크, 디스크, kubelet/containerd, 인증/부팅 문제 등) 을 빠르게 좁히기
  2. 노드가 더 망가지기 전에 증거(로그/상태)를 외부로 보존하기

관련해서 노드 디스크 압박이 원인인 경우도 매우 흔합니다. Evicted 폭주/디스크 압박 대응은 별도 글로 정리해두었으니 함께 참고하면 원인 분류가 빨라집니다: EKS 노드 디스크 부족 Evicted 폭주 해결 가이드

Bottlerocket에서 “로그 수집”이 다른 이유

Bottlerocket의 핵심 특징 때문에 접근 전략이 달라집니다.

  • 기본적으로 SSH 비활성
  • 관리 작업은 admin 컨테이너 또는 control 컨테이너를 통해 수행
  • 시스템 로그는 일반적인 /var/log/messages 중심이 아니라 journald와 Bottlerocket 전용 구성요소 경로에 존재
  • 노드가 NotReady라도 EC2 인스턴스 자체는 Running인 경우가 많아, AWS 레벨(EC2/SSM/CloudWatch)에서 우회 수집 가능

즉, Kubernetes 레벨에서 안 되면 AWS 레벨로 내려가서 수집해야 합니다.

0) 먼저 “수집 우선순위”를 정하자

NotReady는 원인이 다양합니다. 아래 우선순위대로 증거를 확보하면, 원인 추적이 빨라집니다.

  1. 노드 이벤트 (kubectl describe node)와 컨디션(Ready/NetworkUnavailable/MemoryPressure/DiskPressure/PIDPressure)
  2. kubelet/containerd 로그(노드 내부)
  3. CNI/네트워크 관련 로그(aws-node, kube-proxy, eni 할당/라우팅)
  4. 부팅/커널/스토리지 관련 로그(EBS 오류, filesystem read-only, OOM)
  5. 클라우드 레벨 로그(EC2 system log, CloudWatch, VPC Flow Logs)

1) Kubernetes 레벨에서 즉시 확보할 것

노드가 NotReady라도 API 서버는 살아있으니, 먼저 여기서 최대한 뽑습니다.

1-1. 노드 상태/이벤트 덤프

NODE=<notready-node-name>

kubectl get node "$NODE" -o wide
kubectl describe node "$NODE" > /tmp/${NODE}-describe.txt
kubectl get events -A --sort-by=.lastTimestamp | tail -n 200 > /tmp/cluster-events-tail.txt

describe에서 특히 아래를 확인합니다.

  • Conditions:
    • ReadyUnknown이면 kubelet이 API 서버로 하트비트를 못 보내는 상태
    • DiskPressure=True / MemoryPressure=True는 즉시 원인 후보
  • Allocated resources가 과도한지(리소스 고갈)
  • EventsNodeNotReady, Kubelet stopped posting node status 등 메시지

1-2. 해당 노드에 떠 있는 시스템 파드 로그

노드가 NotReady여도 DaemonSet 파드가 남아있거나 재시작 흔적이 있습니다.

kubectl get pods -A -o wide --field-selector spec.nodeName=$NODE

# aws-node(CNI) / kube-proxy 같은 핵심 파드 로그
kubectl -n kube-system logs -l k8s-app=aws-node --tail=200
kubectl -n kube-system logs -l k8s-app=kube-proxy --tail=200

만약 로그가 안 나오면(컨테이너 접근 불가), 이 시점부터는 노드 내부 로그로 넘어가야 합니다.

2) Bottlerocket 노드 내부로 들어가 로그를 뽑는 3가지 방법

Bottlerocket에서 노드 내부 접근은 보통 아래 3가지 루트 중 하나입니다.

  1. SSM(Session Manager): 가장 깔끔(사전 준비 필요)
  2. 관리 컨테이너(admin container): Bottlerocket의 정석(사전 활성화 필요)
  3. EC2 콘솔의 System Log / Serial Console: 최후의 보루(일부만 확보 가능)

환경마다 준비 상태가 다르므로, 가능한 것부터 적용하세요.

3) (권장) SSM으로 접속해 로그 수집하기

3-1. 전제 조건

  • 인스턴스에 SSM Agent가 동작해야 함(최근 Bottlerocket는 SSM 사용 패턴이 널리 쓰임)
  • 인스턴스 프로파일에 AmazonSSMManagedInstanceCore 권한 또는 동등 권한 필요
  • VPC가 SSM 엔드포인트에 접근 가능(프라이빗이면 VPC Endpoint 필요)

3-2. SSM 세션 시작

INSTANCE_ID=i-xxxxxxxxxxxxxxxxx
aws ssm start-session --target $INSTANCE_ID

SSM으로 들어갔다면, 실제로는 Bottlerocket의 관리 쉘/컨테이너로 진입하는 형태가 될 수 있습니다. 배포/설정에 따라 명령이 다를 수 있으니, 접속 후 ps, ctr, journalctl 가능 여부를 확인합니다.

3-3. 핵심 로그 수집 명령(journald 중심)

다음은 “원인 분류”에 도움이 되는 대표 로그들입니다.

# 부팅/커널/시스템 전체 로그
sudo journalctl -b --no-pager > /tmp/journal-boot.txt

# kubelet
sudo journalctl -u kubelet -b --no-pager > /tmp/kubelet.txt

# containerd
sudo journalctl -u containerd -b --no-pager > /tmp/containerd.txt

# 네트워크(있다면)
sudo journalctl -u wicked -b --no-pager > /tmp/network.txt || true

Bottlerocket은 서비스 이름이 환경/버전에 따라 다를 수 있습니다. systemctl list-units --type=service | grep -E 'kube|container|net'로 실제 유닛명을 확인한 뒤 수집하세요.

3-4. 파일을 밖으로 빼기(SSM + S3)

노드가 곧 종료/교체될 수 있으니 S3로 업로드해 보존하는 게 안전합니다.

TS=$(date +%Y%m%d-%H%M%S)
NODE_NAME=$(hostname)
ARCHIVE=/tmp/${NODE_NAME}-${TS}-logs.tgz

tar czf $ARCHIVE -C /tmp journal-boot.txt kubelet.txt containerd.txt network.txt 2>/dev/null || true

aws s3 cp $ARCHIVE s3://<your-bucket>/eks-notready/$NODE_NAME/

권한이 없다면, 최소한 SSM 세션 로그를 로컬로 캡처하거나(터미널 로깅), aws ssm send-command로 표준출력을 남기는 방식도 고려합니다.

4) admin 컨테이너로 들어가 수집하기(SSH 없이)

Bottlerocket은 운영 작업을 위해 admin container를 제공할 수 있습니다(클러스터/AMI 설정에 따라 활성화 필요).

4-1. admin 컨테이너 활성화(사전 준비)

운영 환경에서는 “장애가 났을 때 켜는 것”이 아니라, 평소에 최소 권한으로 준비해두는 게 좋습니다.

Bottlerocket 설정 예시(컨셉):

[settings.host-containers.admin]
enabled = true

[settings.host-containers.control]
enabled = true

적용 방식은 UserData, Bottlerocket API, 또는 클러스터 부트스트랩 구성에 따라 달라집니다.

4-2. admin 컨테이너에서 로그 확인

admin 컨테이너에 들어가면 일반 리눅스 도구로 journalctl을 조회할 수 있습니다.

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

여기서 자주 보이는 패턴:

  • failed to run Kubelet / node status update 실패: API 서버 통신/인증 문제
  • containerdfailed to mount / no space left on device: 스토리지/디스크 문제
  • CNI 관련 오류: IP 할당 고갈, ENI attach 실패, iptables 문제

5) EC2 콘솔에서 System Log/Serial Console로 뽑기

노드 내부 진입이 전부 막힌 경우, EC2 콘솔에서 확보 가능한 로그라도 수집합니다.

  • Get system log: 커널 패닉, 부팅 실패, 파일시스템 오류 같은 “큰 사고” 확인
  • EC2 Serial Console(가능한 계정/리전/AMI에서): 로그인/콘솔 접근이 열려 있으면 강력한 우회로

이 루트는 kubelet 상세 로그까지는 어려운 경우가 많지만, “왜 kubelet이 죽었는지”의 단서(예: read-only remount, nvme 오류, oom-killer)는 얻을 수 있습니다.

6) NotReady 원인별로 어떤 로그를 봐야 하나

6-1. DiskPressure / inode 고갈

  • kubectl describe node에서 DiskPressure=True
  • kubelet 로그에서 eviction manager 메시지
  • containerd 로그에서 no space left on device

이 경우는 로그 수집과 동시에 “디스크 사용량/이미지 GC/ephemeral-storage 제한”까지 같이 봐야 합니다. 자세한 대응은 EKS 노드 디스크 부족 Evicted 폭주 해결 가이드에서 다룹니다.

6-2. 네트워크 단절(API 서버 통신 불가)

  • Ready=Unknown + Kubelet stopped posting node status
  • aws-node(CNI) 로그에서 ENI/IP 할당 실패
  • VPC Flow Logs에서 노드→API 서버(또는 엔드포인트) 트래픽 드랍

추가로, Ingress/서비스 레벨 장애처럼 보이지만 실제로는 노드 네트워크 문제인 경우도 많습니다. 트래픽은 503인데 파드는 정상처럼 보일 때 점검 흐름은 이 글이 도움이 됩니다: EKS Ingress 503인데 Pod 정상일 때 점검 가이드

6-3. containerd/kubelet 자체 장애

  • kubelet 로그: PLEG is not healthy, container runtime is down
  • containerd 로그: snapshotter/mount 오류, metadata DB 문제

이 경우는 “노드 교체”가 가장 빠른 복구일 수 있지만, 재발 방지를 위해 containerd 로그 + 디스크 상태 + 커널 로그를 함께 보존하는 게 중요합니다.

6-4. EBS/PV 마운트 계열 문제로 노드가 비정상화

특정 워크로드가 노드를 망가뜨리는 케이스도 있습니다. 예를 들어 잘못된 fsType/마운트 옵션/권한 문제로 반복 재시도가 발생하며 노드 자원을 고갈시키는 경우입니다.

  • kubelet 로그: MountVolume.MountDevice failed
  • CSI 드라이버 로그: 권한/토큰/Attach 실패

PV 마운트 오류 트러블슈팅은 아래 글도 참고하세요.

7) 장애 대응용 “원샷 수집 스크립트” 예시

SSM/admin 컨테이너로 들어갈 수 있다는 가정 하에, 최소 증거를 빠르게 묶는 스크립트 예시입니다.

#!/usr/bin/env bash
set -euo pipefail

OUTDIR=/tmp/eks-notready-collect
mkdir -p "$OUTDIR"

# 기본 정보
{
  echo "=== date ==="; date
  echo "=== hostname ==="; hostname
  echo "=== uptime ==="; uptime || true
  echo "=== disk ==="; df -h || true
  echo "=== mem ==="; free -m || true
} > "$OUTDIR/system.txt" 2>&1

# journald
journalctl -b --no-pager > "$OUTDIR/journal-boot.txt" 2>&1 || true
journalctl -u kubelet -b --no-pager > "$OUTDIR/kubelet.txt" 2>&1 || true
journalctl -u containerd -b --no-pager > "$OUTDIR/containerd.txt" 2>&1 || true

# 네트워크
ip a > "$OUTDIR/ip-a.txt" 2>&1 || true
ip r > "$OUTDIR/ip-r.txt" 2>&1 || true

TS=$(date +%Y%m%d-%H%M%S)
ARCHIVE=/tmp/eks-notready-${TS}.tgz

tar czf "$ARCHIVE" -C "$OUTDIR" .
echo "ARCHIVE=$ARCHIVE"

이 스크립트의 목적은 “완벽한 진단”이 아니라, 노드가 교체되기 전에 재현 가능한 증거 묶음을 확보하는 것입니다.

8) 운영 팁: NotReady가 나도 로그가 남게 만들기

장애 때마다 수동으로 들어가면 늦습니다. Bottlerocket/EKS에서는 아래를 미리 해두면 NotReady에서도 추적이 쉬워집니다.

  • CloudWatch Logs로 journald/kubelet/containerd를 상시 수집(에이전트/구성은 환경에 맞게)
  • 노드에 SSM 접속 경로(VPC Endpoint 포함)와 최소 권한 IAM 준비
  • 노드 교체(ASG/Managed Node Group) 시에도 로그가 남도록 S3 아카이브 경로 마련
  • DiskPressure가 잦다면 ephemeral-storage 요청/제한, 이미지 GC 정책, 로그 볼륨 분리 등 구조적 개선

마무리

Bottlerocket 노드가 NotReady일 때 핵심은 “SSH가 안 된다”에 당황하지 않고, Kubernetes → SSM/admin 컨테이너 → EC2 콘솔 순서로 수집 경로를 전환하는 것입니다. 특히 kubelet/containerd의 journald 로그를 확보하면, 원인의 70~80%는 그 자리에서 분류가 됩니다.

원인이 디스크 압박/eviction 쪽으로 보인다면 위에서 링크한 디스크 압박 대응 글을 함께 보면서, 로그 수집과 동시에 재발 방지(요청/제한, GC, 워크로드 로그 정책)까지 같이 정리하는 것을 권장합니다.