사례4건
4건 중 1~4번째
IL-0063Redis 스테이트풀셋을 다시 만들자 Redis 클러스터가 한꺼번에 재시작되며 일부가 망가졌고, 재연결 폭주로 백엔드가 OOM 으로 죽어 AdGuard VPN 이 약 50분 멈췄다.AdGuard VPN2025- 증상
8:10 UTC 에 팀은 Redis 스테이트풀셋을 고아 삭제했다. 고아 삭제는 파드와 디스크는 그대로 두고 스테이트풀셋 객체만 지우는 방식이다. 스테이트풀셋을 다시 만들자 쿠버네티스가 8:10 부터 8:15 UTC 사이에 스테이트풀셋 전체를 빠르게 재시작했다. 처음에는 백엔드 서비스가 Redis 연결 오류를 냈지만 서비스 전체가 곧바로 무너지지는 않았다.
- 촉발
팀은 디스크를 온라인으로 늘린 뒤 Git 의 선언적 설정을 실제 상태와 맞추려고 Redis 스테이트풀셋을 고아 삭제했다. 고아 삭제는 파드와 디스크는 남기고 스테이트풀셋 객체만 지우는 방식이다. 그다음 늘린 크기를 적은 저장소 템플릿으로 스테이트풀셋을 다시 만들었다.
- 원인
- 구성 요소
- 기여 요인
- 감지
Redis 클러스터 여러 인스턴스의 디스크 여유 공간이 바닥나고 있다는 알림이 시작이었다. 그 뒤의 장애는 스테이트풀셋을 다시 만든 직후 백엔드 서비스가 Redis 연결 오류를 내면서 드러났다.
- 조치
- 망가진 Redis 클러스터를 손으로 복구했다. 마스터에서 쓸모없어진 노드 ID 를 지우고, 해당 레플리카를 리셋한 뒤 클러스터에 다시 붙이고 복제가 따라잡기를 기다렸다.
- 실제로 메모리가 바닥난 구성 요소를 찾아낸 뒤, 백엔드 서비스를 확장하고 그 구성 요소의 메모리 한도를 올렸다.
- 프로브를 잠시 느슨하게 해, 백엔드 인스턴스가 ready 와 not ready 를 오가지 않고 바로 트래픽을 받게 했다.
- 10:00 UTC 에 임시 조치를 되돌렸다.
- 후속 조치
- 빠져 있던 스타트업 프로브를 넣는다. 데이터셋을 다 읽기 전에는 파드를 ready 로 표시하지 않는다.
- 재시작한 파드가 옛 마스터 IP 주소에 묶여 있을 때 쓸 수 있도록 Redis 용 자체 컨트롤 플레인을 만든다.
- 서비스 전체의 단일 장애점이 된 이 Redis 클러스터 의존을 줄인다. 중요한 정보 일부를 서버마다 따로 둔 Redis 클러스터 같은 다른 데이터 저장소로 옮기고, 백엔드 일부 구성 요소에 Kafka 를 쓰기 시작한다.
- 교훈
장애를 일으킨 것은 디스크 확장이 아니라 상태를 맞추려고 컨트롤러 객체를 다시 만든 단계였다. 고칠 수 없는 필드가 있으면 객체를 통째로 바꾸는 위험한 방법을 쓰게 된다. 그렇게 바꾸면 쿠버네티스가 워크로드를 넓은 범위에서 다시 조정할 수 있다. 스타트업 프로브와 엄격한 레디니스 프로브가 있어야 롤링 재시작이 현재 파드가 정말 준비될 때까지 기다린다. 이번 장애에서 가장 뼈아픈 것은 프로브가 없었다는 점이다. 분산 클러스터가 다시 붙고 복제를 따라잡는 데는 시간이 걸리므로, 의존하는 서비스는 그동안 기능을 줄여서라도 버티도록 설계해야 한다.
- 증상
2025-10-21 21:45 UTC, 타임아웃 오류를 줄이려는 배포 직후 자동 모니터링이 기능 플래그 서비스의 오류율 상승을 잡았다. 2분 만에 롤백했지만 오류율은 내려가지 않았고, US 데이터센터에서 평가 요청의 약 38% 가 오류를 냈다.
- 촉발
10월 21일에는 증상만 고치려던 타임아웃 처리 배포가 장애를 일으켰다. 28일에는 플래그 서비스와 무관한 일상 배포가 파드 롤아웃을 일으켰고, 이 롤아웃이 장애로 이어졌다. 24일에는 로드밸런서 뒤에서 모든 트래픽을 IP 하나에서 온 것으로 본 레이트 리밋 배포가 장애를 일으켰다.
- 원인
- 구성 요소
- 기여 요인
- 감지
21일에는 배포 2분 뒤 자동 모니터링이 오류율 상승을 잡았고, 28일에는 온콜이 높은 오류 수 경보를 받았다. PostHog 는 대부분의 경우 2분 안에 감지했다고 정리했다. 24일에는 429 오류 경보가 없어서 18:10 UTC 부터 들어온 고객 신고로 알았고, 감지까지 62분이 걸렸다. CPU 경보가 아예 없어서 공통 원인인 CPU 포화는 오래 드러나지 않았고, 29일에서 30일 사이 장애는 7시간 넘게 이어졌다.
- 조치
- 10월 21일 타임아웃 처리를 바꾼 배포를 2분 안에 롤백했지만 오류는 멈추지 않았다.
- 10월 21일 23:00 UTC 부터 연결과 Redis 쓰기를 조절해 23:28 UTC 에 서비스를 복구했다.
- 10월 28일 롤아웃을 멈추고 US 플래그 파드를 0개로 줄여 Redis 가 회복되기를 기다린 뒤, 파드를 차례로 다시 올렸다.
- 10월 30일 00:03 UTC 에 하드웨어 변경을 롤백해 오류는 대부분 가라앉혔지만 지연은 남았다.
- 파드 리소스 요청을 늘리고 쿠버네티스 플릿 크기를 조정해 노드당 파드 수를 줄였다.
- 로드밸런서와 서비스 사이 Envoy 재시도에 한도를 걸었다.
- 데이터베이스 연결 타임아웃과 다른 중요한 설정을 런타임 설정으로 옮기고, 타임아웃 값을 피크 부하에 맞게 늘렸다.
- 노드마다, 파드마다 CPU 사용률이 80% 를 5분 동안 넘으면 온콜을 호출하는 CPU 경보를 추가했다.
- 후속 조치
- 플래그, 수집, 인프라 팀 엔지니어로 전담 팀을 꾸려 애플리케이션과 인프라 전체를 살피고 남은 병목을 찾는다.
- 플래그 서비스의 Redis 를 본 애플리케이션의 Redis 와 분리하는 방안을 검토한다.
- ArgoCD 동기화를 다시 켜기 전에 플래그 서비스 차트 설정을 ArgoCD 의 현재 값에 맞추고, 배포 전략과 Redis 전략을 정한다.
- 플래그 관련 코드의 필수 코드 오너를 기능 플래그 팀으로 정한다.
- 중요한 플래그 지표와 런북 링크를 한데 모은 대시보드를 만들고, 기능 플래그 런북을 고친다.
- 기존 서비스 지표와 인프라 지표에 빠져 있는 경보를 추가한다.
- readiness 검사가 liveness 검사를 그대로 따라 하지 않고, 데이터베이스에 ping 을 보내는 식으로 부하가 걸렸을 때 약해졌던 의존성을 확인하게 바꾼다.
- 캐시 채우기 작업에 레이트 리밋을 걸어 Redis 에 감당하지 못할 만큼 작업이 몰리지 않게 한다.
- 연결 풀을 모니터링해 풀이 바닥에 가까워지면 자동으로 요청을 조절하고, 동시 연결 수에 상한을 둔다.
- 운영 규모의 부하 시험 체계를 만든다.
- 점진적 롤아웃 기반을 만들어 배포가 미치는 범위를 줄인다.
- Postgres 에서 Redis 로 가는 데이터 흐름을 문서로 정리하고, 데이터 전송이 한꺼번에 몰릴 때 쓸 런북을 만든다.
- 교훈
가장 큰 교훈은 CPU 크기 산정이었다. 노드가 파드 요청에 비해 작으면 노드 하나에 파드가 너무 많이 몰리고, CPU 포화가 과도한 병렬 처리, 연결 풀 고갈, 느린 쿼리처럼 서로 다른 모습으로 나타난다. 증상만 고친 수정 때문에 21일의 장애가 28일에 되풀이됐다. PostHog 는 CPU 를 넉넉히 받은 파드를 적은 수로 돌리는 편이 낫다고 정리했다. 기능 플래그 서비스와 본 애플리케이션이 Redis 를 같이 쓰면 한쪽 과부하가 다른 쪽을 쓰러뜨리는 단일 장애 지점이 된다. CPU 지표는 처음부터 경보로 걸어야 하고, Postgres 에서 Redis 로 가는 전송 급증도 경보 대상이다. 재시도, 캐시 채우기, 연결 생성에 한도가 없으면 장애가 커진다. 과부하 패턴은 운영 트래픽에서만 드러났으므로 부하 시험이 필요하고, 중요한 설정은 배포 없이 바꿀 수 있어야 한다.
- 증상
프로덕션 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를 권하지만 각 환경에 맞춰 정해야 한다.
- 증상
어느 저녁 데이터센터 OpenStack 의 네트워크 하위 시스템을 업그레이드한 뒤 Kafka 연결이 몇 시간 동안 간헐적으로 끊겼다. 개발 환경 워크로드 약 2,000개를 올린 큰 쿠버네티스 클러스터에서 노드들이 쿠버네티스에 비정상이라고 보고하기 시작했다.
- 촉발
OpenStack 의 네트워크 하위 시스템을 업그레이드하다가 예상과 달리 네트워크가 몇 시간 동안 끊겼고, Kafka 에는 간헐적으로만 닿았다. 그러자 모든 워크로드에 딸린 로깅 사이드카가 한꺼번에 깨어나 노드의 Docker 데몬에 높은 부하를 주었다.
- 원인
- 구성 요소
- 기여 요인
- 조치
- Consul 의 gossip 암호화를 켜서 오염된 gossip 메시에서 오는 메시지를 바로 거부하게 했고, 이것으로 Consul 과 Vault 를 되살렸다.
- 배포 엔진을 안정시킨 뒤 암호화 설정을 반영하려고 개발 환경 전체를 다시 배포했고, 암호화가 설정되지 않은 인스턴스는 종료했다.
- 큰 클러스터를 다루는 수정이 들어간 Consul 1.2.3 으로 제자리 업그레이드했고, UI 버그 때문에 최종적으로 1.3.0 으로 올렸다. 운영 환경의 오염된 gossip 메시도 이 업그레이드로 빠르게 정리됐다.
- 후속 조치
- 노드의 워크로드가 함께 쓰는 Docker 데몬은 깨지기 쉬운 장애 지점이므로, 데몬 하나의 장애가 미치는 범위를 줄이는 데 더 힘쓴다. 작은 클러스터를 여러 개 두는 방침과 같은 방향이다.
- 교훈
글쓴이는 작은 클러스터를 여러 개 두라는 자신의 원칙이 옳았다고 확인했다. 더 작은 개발 클러스터와 운영 클러스터의 워크로드는 큰 클러스터처럼 영향을 받지 않았고, 개발과 운영이 한 클러스터에 있었다면 훨씬 큰 일이 났을 것이다. 노드의 워크로드가 함께 쓰는 Docker 데몬은 깨지기 쉬운 장애 지점이다. 워크로드마다 자기 로깅 사이드카와 메트릭 사이드카를 두는 방식이 옳다고도 확신하게 됐다. 이것들이 클러스터 공용 자원이었다면 문제가 거의 틀림없이 더 커졌고 고치기도 훨씬 어려웠을 것이다.
이 4건에서 가장 많이 나오는 구성 요소: 인메모리 캐시, 컨테이너 메모리 한도. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.