Incident Library
  • 원인
  • 구성 요소
  • 증상
  • 기여 요인
  • 사례

사례8건

8건 중 1~5번째

  1. 증상

    워커 노드 한 대에서 NodeFilesystemFilesFillingUp 알림이 울려 담당자가 호출됐다. inode 가 바닥날 추세라는 알림이었다.

    촉발

    단일 스테이지로 빌드한 이미지에 소스, 빌드 결과물, 개발 의존성까지 포함한 node_modules 전체가 들어갔고, 새 레이어 다이제스트가 생길 때마다 노드에 새 스냅샷이 풀렸다.

    원인
    구성 요소
    기여 요인
    감지

    node_exporter mixin 에 들어 있는 NodeFilesystemFilesFillingUp 알림이 inode 사용률 67% 에서 울렸다. predict_linear 로 inode 가 바닥날 시점을 예측하는 추세 알림이다.

    후속 조치
    • 멀티 스테이지 Dockerfile 로 바꿔, 한 스테이지에서 의존성을 설치하고 빌드한 뒤 최종 이미지에는 nginx:alpine 위에 컴파일된 결과물만 넣는다.
    • 추세 알림과 별도로 루트 파일 시스템의 inode 사용률이 0.80 을 넘으면 울리는 고정 기준 알림을 for: 30m 으로 둔다.
    • 노드마다 inode 사용률에서 디스크 사용률을 뺀 값을 큰 순서로 보여 주는 Prometheus 쿼리를 대시보드에 둔다.
    • 노드에 이미 쌓인 오래된 스냅샷은 crictl rmi --prune 으로 어떤 컨테이너도 쓰지 않는 이미지를 지워 정리한다.
    교훈

    kubelet 의 백그라운드 정리는 바이트를 기준으로 돈다. 용량보다 파일 수가 먼저 차는 워크로드에는 미리 손쓸 장치가 없다. kubelet 은 inode 가 5% 남았을 때 파드 축출로 처음 개입한다. 파드 축출은 노드를 지키는 마지막 수단일 뿐 해결책이 아니다. ext4 의 inode 수는 포맷할 때 정해진다. 작은 파일이 많으면 디스크가 넉넉해도 inode 가 먼저 바닥난다. 추세 알림은 증가가 잠시 멈추면 조용해진다. kubelet 의 95% eviction 지점보다 한참 앞에서 울리는 고정 기준 알림을 함께 둔다. node_modules 뿐 아니라 Python virtualenv, 벤더링한 PHP 패키지, Ruby gem 처럼 작은 파일이 많은 의존성 트리를 담은 이미지는 모두 같은 문제를 일으킨다.

  2. 증상

    외부에서 쿠버네티스 클러스터에 접속할 수 없었다. 작성자는 컨트롤 플레인 노드로 들어가 sudo systemctl status kubelet 으로 kubelet 로그를 확인했다.

    촉발

    kubeadm 이 만든 하위 인증서의 기본 유효기간 1년이 2026-02-20 16:16 UTC 에 끝났다.

    원인
    구성 요소
    기여 요인
    감지

    외부에서 클러스터에 접속이 안 되는 것으로 알아챘다. 컨트롤 플레인 노드에서 kubelet 로그, containerd 로그, kube-apiserver 컨테이너 로그를 차례로 보다가 kube-apiserver 로그의 x509 만료 오류에서 원인을 확인했다.

    조치
    • sudo kubeadm certs renew all 로 만료된 인증서를 한 번에 갱신했다.
    • containerd 와 kubelet 을 재시작해 Static Pod 로 도는 컨트롤 플레인 컴포넌트를 새 인증서로 다시 띄웠다.
    • /etc/kubernetes/admin.conf 를 $HOME/.kube/config 로 복사하고 소유자를 바꿔 kubeconfig 를 새 인증서로 바꿨다.
    교훈

    sudo kubeadm certs check-expiration 으로 만료일을 주기적으로 확인하고, 만료 30일 전부터 갱신하는 것이 좋다. kubeadm upgrade apply 를 실행하면 인증서도 함께 갱신되므로, 1년에 한 번 이상 쿠버네티스 버전을 올리는 클러스터는 이 문제를 자연스럽게 피한다. 버전을 올리지 않고 오래 운영하는 클러스터라면 kubeadm certs renew all 과 kubelet 재시작을 cron 으로 매년 걸어 둘 수 있다. 작성자가 든 예시에서는 매년 1월 1일 새벽 3시에 실행한다.

  3. 증상

    프로덕션 EKS 클러스터에서 원인을 바로 알기 어려운 서비스 에러가 간헐적으로 났다. RDS 나 Redis 같은 외부 서비스를 호출할 때 연결이 가끔 실패했다. DNS 요청이 몰릴 때는 도메인 조회 응답이 늦어지거나 조회가 아예 실패했다.

    촉발

    모든 CoreDNS 파드가 같은 노드에 배치되었고, 그 노드의 메모리 사용률이 급증해 메모리가 고갈됐다.

    원인
    구성 요소
    기여 요인
    감지

    글은 전면 장애를 어떻게 알아챘는지 적지 않았다. 그 전의 DNS 문제는 원인을 알기 어려운 서비스 에러가 간헐적으로 나면서 드러났다. 팀은 RDS 와 Redis 호출이 가끔 실패하는 현상을 추적하다가 DNS 이름 해석 실패가 근본 원인이라는 것을 확인했다.

    조치
    • CoreDNS 앞에서 DNS 질의를 받아 노드마다 캐시하는 NodeLocalDNS Cache 를 데몬셋으로 배포해 CoreDNS 부하를 줄였다.
    • CoreDNS 에 maxUnavailable: 1 인 PodDisruptionBudget 과 topology.kubernetes.io/zone 기준의 topologySpreadConstraints 를 적용해 파드가 한 곳에 몰리지 않게 했다.
    • Deployment 의 dnsConfig 로 ndots 값을 5 에서 2 로 낮춰 불필요한 DNS 조회를 줄였다.
    교훈

    쿠버네티스의 ndots:5 기본값은 점이 적은 외부 도메인을 조회할 때마다 질의를 여러 번 만든다. 이 부하는 트래픽이 큰 프로덕션 환경에서야 드러난다. CoreDNS 를 파드 2개의 기본 구성으로 두면 클러스터 전체의 단일 장애점이 된다. CoreDNS 파드는 노드와 가용 영역에 흩어 둔다. 별도 노드 그룹이나 Fargate 로 분리하면 다른 워크로드와 자원을 다투지 않는다. Affinity 와 PriorityClassName 으로 우선순위를 주면 더 안정적이다. CoreDNS 의 캐시와 bufsize 조정은 증상을 잠깐 줄였을 뿐이다. ndots 값은 여러 자료가 1이나 2를 권하지만 각 환경에 맞춰 정해야 한다.

  4. 증상

    2023년 10월 8일 06:00 UTC 무렵부터 일부 Managed Kubernetes 클러스터의 워커 노드가 NotReady 가 됐고, 그 노드에서 돌던 서비스와 애플리케이션이 멈췄다. 07:43부터 노드를 교체하면 해결되는 워커 노드 장애를 알리는 고객 보고가 드문드문 들어왔다.

    촉발

    DigitalOcean Debian 이미지의 systemd 타이머가 매일 06:00 UTC에 최대 60분의 무작위 지연을 두고 apt 업그레이드를 시작했다.

    원인
    구성 요소
    기여 요인
    감지

    10월 8일 07:43 UTC부터 노드 교체로 해결되는 워커 노드 장애를 알리는 고객 보고가 드문드문 들어왔다. 장애가 시작되고 약 1시간 43분 뒤였다. 내부 장애 대응은 영향이 여러 리전으로 번진 뒤인 10월 9일 07:10에 시작됐다.

    조치
    • 고객에게 영향받은 머신을 교체하거나 재부팅해 장애를 줄이라고 안내했다.
    • 장애가 없는 동안 워커 노드에 SSH 와 콘솔 접근을 마련했고, 10월 10일 영향받은 노드에 붙어 실시간으로 디버깅했다.
    • 각 워커 노드에서 apt 업그레이드 systemd 타이머를 끄는 DaemonSet 을 만들어 10월 11일 03:08 UTC에 영향받은 모든 클러스터에 배포했다.
    • 자동 업그레이드 과정을 없앤 새 노드 이미지를 배포했다.
    후속 조치
    • 노드 프로비저닝 과정에서 같은 수정을 적용해 워커 노드에 타이머가 처음부터 켜지지 않게 한다. 그 뒤 모든 클러스터에서 DaemonSet 을 지운다.
    • 새 클러스터 버전마다 도는 내부 적합성 테스트에 자동 업그레이드와 원하지 않는 다른 타이머가 꺼져 있는지 확인하는 검사를 넣었다.
    • 평소 쓰는 접근 경로가 막혀도 워커 노드 머신에 로그인할 수 있게 한다.
    교훈

    베이스 이미지에 기본으로 켜진 자동 업그레이드 타이머가 운영 중인 노드에 지원하지 않는 패키지 버전을 들일 수 있다. 업그레이드 중 커널 같은 시스템 패키지가 타임아웃으로 강제 종료되면 노드 네트워킹이 망가질 수 있다. 네트워크가 끊긴 노드에 들어갈 방법이 없으면 깊은 조사는 장애가 없는 시간까지 밀린다. 매일 같은 시각에 반복되고 특정 버전에만 나는 장애는 노드에서 주기적으로 도는 작업을 가리켰다.

  5. 증상

    2019년 6월 11일, Zalando 가 직접 만들어 운영하는 CI/CD 플랫폼인 CDP 의 빌드가 한 시간 가까이 타임아웃으로 실패했다.

    촉발

    CDP 쿠버네티스 클러스터의 규모가 조정되면서 공교롭게도 빌더 파드가 쓸 수 있는 노드가 한 대만 남았다. 그 노드에서 파드가 빠르게 생성되고 삭제되자 kubelet 이 따라가지 못했다.

    원인
    구성 요소
    기여 요인
    조치
    • 클러스터를 수동으로 늘려 빌더 파드가 노드 한 대가 아니라 여러 대에 흩어지게 했다.
    후속 조치
    • 전역 클러스터 설정으로 kubelet QPS 를 기본값 5 에서 50 으로 올렸다.
내 클러스터에도 있는지 확인하기

이 8건에서 가장 많이 나오는 구성 요소: kubelet, 쿠버네티스 API 서버. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.

원인별4

용량 한계4건

  1. IL-0071 작은 파일이 4만 개 넘게 든 단일 스테이지 이미지의 containerd 스냅샷이 쌓여 워커 노드의 inode 가 디스크보다 빠르게 소진되고 있었다
  2. IL-0027 CoreDNS 파드가 모두 한 노드에 몰린 채 그 노드의 메모리가 고갈되자 DNS 가 응답하지 못해 아이디어스 서비스 전체가 멈췄다
  3. IL-0002 빌더 파드가 노드 한 대에 몰리자 kubelet 이 기본 API QPS 한도에 막혀 파드 상태를 늦게 갱신했고, CI 빌드가 AWS IAM 자격 증명을 받지 못해 실패했다
  4. IL-0040 비운영 쿠버네티스 클러스터에 Helm 차트를 한꺼번에 설치하자 작은 gp2 루트 볼륨의 IOPS 가 바닥나, 워커 노드 세 대에서 kubelet 이나 Docker 가 내려가 세 대 모두 NotReady 가 됐다

인증서·자격 증명 만료1건

조직
  1. IL-0183 kubeadm 으로 만든 클러스터의 1년짜리 인증서가 만료되어 kube-apiserver 가 etcd 와 TLS 연결을 맺지 못하고 재시작을 반복했고, 클러스터에 접속할 수 없게 됐다.

설정 변경2건

  1. IL-0153 DOKS 1.27.6-do.0 과 1.28.2-do.0 워커 노드에서 매일 아침 도는 apt 자동 업그레이드가 15분 타임아웃에 걸려 강제 종료되면서 노드 네트워킹이 망가지고, 노드가 사흘 연속 NotReady 가 됐다.
  2. IL-0035 ElastAlert 에 새로 넣은 알림 규칙 때문에 ElastAlert 파드가 메모리를 끝없이 써서 노드가 잇달아 NotReady 가 되었고, 같은 클러스터의 도구와 Grafana, Prometheus 가 함께 흔들렸다.
    NU.nl2019

unknown1건

조직
  1. IL-0059 kubeadm이 만든 etcd 정적 파드의 liveness probe가 실패해 kubelet이 약 2분마다 etcd를 재시작했고, 단일 마스터 클러스터의 API 서버가 계속 끊겼다