사례6건
6건 중 1~5번째
IL-0405AWS Load Balancer Controller 의 webhook 인증서가 만료돼 US·Canada 지역의 BSEE SaaS 인스턴스가 전부 멈췄다PortSwigger2026- 증상
US 와 Canada 지역 고객이 BSEE SaaS 인스턴스의 Web UI 에 들어가지 못했고 스캔도 시작하지 못했다. 엔지니어는 고객이 지원 창구로 알려 온 뒤에야 장애를 알았다.
- 촉발
PortSwigger 는 2025년 11월 28일 AWS Load Balancer Controller 를 v1.14.1 에서 v1.16.0 으로 올렸다. v1.15.0 의 Helm 차트가 인증서를 관리하는 방식을 바꿨고, 이 업그레이드 뒤에 소유자가 사라진 webhook 용 TLS Secret 이 남았다.
- 원인
- 구성 요소
- 기여 요인
- 감지
엔지니어는 고객이 지원 창구로 신고한 뒤에야 장애를 알았다. 신고는 2026년 1월 12일 09:52 UTC 에 들어왔고, 영향이 시작되고 23시간 52분이 지난 때였다.
- 조치
- US 와 Canada 클러스터에서 소유자가 사라진 aws-load-balancer-tls Secret 을 지웠다.
- Helm 으로 AWS Load Balancer Controller 설정을 다시 배포했다.
- 인증서가 새로 만들어졌는지, webhook 이 제대로 동작하는지 확인했다.
- 후속 조치
- 모든 클러스터에서 소유자인 Certificate 리소스가 사라진 Secret 을 찾아 정리한다.
- SaaS 클러스터마다 외부 프로브가 애플리케이션에 접속할 수 있는지 확인하고, 접속하지 못하면 알림을 보내게 한다.
- 증상
2025년 9월 14일, 이미 떠 있던 노드는 계속 돌았지만 새로 만들어지는 노드는 하나도 클러스터에 제대로 들어오지 못했다. 스팟 인스턴스에서 돌던 백그라운드 워커, 리포트 서비스, 주문 할당 시스템이 완전히 멈췄다. API 는 기존 노드에서 계속 응답했지만 부하가 늘어도 확장할 수 없었다.
- 촉발
노드 풀의 자동 업그레이드를 꺼 두었는데도 Google 이 버전 지원 종료 기한을 이유로 컨트롤 플레인을 v1.32.x 에서 v1.33.2 로 강제 업그레이드했다.
- 원인
- 구성 요소
- 기여 요인
- 조치
- 강제 업그레이드와 같은 버전인 v1.33 으로 새 GKE 클러스터를 만들었다.
- 쿠버네티스 1.33 과 호환되도록 Istio 를 1.27.1 로 올렸다.
- 쿠버네티스 1.33 에 맞게 cert-manager 설정을 갱신했다.
- 모든 운영 워크로드를 새 클러스터로 옮기고, 파드 스케줄링, DNS 조회, PVC 마운트, 외부 연동이 정상인지 확인했다.
- 손상된 기존 클러스터를 폐기했다.
- 후속 조치
- 모든 클러스터를 GKE stable 릴리스 채널로 설정한다.
- 2025년 4분기까지 Argo CD 와 모니터링 같은 관리용 구성 요소를 따로 돌리는 관리 클러스터를 만든다.
- 중요한 워크로드의 스팟 인스턴스 사용을 다시 검토하고, 온디맨드 대체 노드 풀을 추가한다.
- SRE 업그레이드 절차를 문서로 남긴다. 관리 클러스터를 먼저 올리고, 며칠 동안 지켜본 뒤 운영 클러스터를 올린다.
- 쿠버네티스, Istio, cert-manager 사이의 호환 표를 문서로 남긴다.
- 업그레이드를 자동으로 시험하는 파이프라인을 만든다.
- 빠르게 이전할 수 있도록 고정 IP 를 예약해 둔다.
- Istio, cert-manager, CSI, DNS 애드온의 버전을 고정한다.
- 지원 종료 기한에 몰리지 않도록 stable 릴리스를 따라 제때 올리고, GKE 알림과 공지를 정기적으로 확인한다.
- 교훈
Google 은 자동 업그레이드를 꺼 두어도 버전 지원 종료 기한이 되면 컨트롤 플레인을 강제로 올린다. 강제 업그레이드를 피하려면 지원 버전 범위 안에 머물러야 한다. 기존 노드가 멀쩡해도 새 노드 초기화는 완전히 실패할 수 있다. 선점된 스팟 노드가 클러스터에 돌아오지 못하면 장애가 연쇄로 번진다. 컨트롤 플레인을 올린 뒤 노드 초기화가 되돌릴 수 없게 망가지면 클러스터를 통째로 다시 만들어야 할 수도 있다. 예측할 수 있고 검증된 릴리스 주기를 원하면 GKE stable 채널을 쓴다. 쿠버네티스를 올릴 때는 Istio, cert-manager 같은 의존 구성 요소를 함께 맞춰야 한다. 같은 클라우드에 대기 클러스터가 없으면 바로 넘어갈 곳이 없다. SRE 가 롤아웃을 단계적으로 관리하면 업그레이드 위험이 줄어든다.
- 증상
2025-03-12 10:58 UTC 부터 백엔드 응답 시간이 높다는 알림이 들어왔다. 3월 12일과 13일 이틀 동안 mw-api-int, mw-parsoid, mw-jobrunner 에서 지연이 커지고 데이터베이스 오류가 늘었다. External Storage 클러스터의 부하와 연결 수도 크게 늘었다.
- 촉발
mw-api-int, mw-parsoid, mw-jobrunner 를 PHP 8.1 로 옮기는 예정된 Scap 배포가 장애를 일으켰다. 3월 12일 10:44 UTC 에 1126607[puppet] 과 1126650 [deployment-charts] 를, 3월 13일 11:15 UTC 에 1127476[puppet] 과 1127478[deployment-charts] 를 배포했다.
- 원인
- 구성 요소
- 기여 요인
- 감지
3월 12일 10:58 UTC 에 백엔드 응답 시간이 높다는 알림이 들어오기 시작했다. 처음 세운 가설은 특정 네트워크나 클라이언트의 많은 트래픽, 잘못 실행된 예약 작업, 자원을 많이 먹는 changeprop-jobqueue 작업이었다. 원인을 가리켰을 MediaWikiMemcachedHighErrorRate 알림은 logstash-prometheus 파이프라인이 밀려 빠지거나 곧바로 회복됐다. 장애 중에는 알림 78개가 울리고 있어서, 그 알림이 울렸더라도 부수 증상으로 보고 넘겼을 가능성이 컸다.
- 조치
- 3월 12일 13:19 UTC 에 cirrus-streaming-updater 의 SUP 병렬도를 낮추는 패치 1126988[deployment-charts] 를 배포했다.
- 13:33 에 1127000[deployment-charts] 로 changeprop-jobqueue 의 categoryMembershipChange 작업 동시성을 낮췄다.
- 13:44 에 1126607[puppet] 과 1126650 [deployment-charts] 를 되돌리고 Scap 을 다시 돌렸다.
- 3월 13일 11:34 UTC 에 changeprop-jobqueue 를 롤링 재시작했다.
- 11:43 에 mw-api-int 를 롤링 재시작했다.
- 11:44 에 1127500[deployment-charts] 로 changeprop-jobqueue 의 categorymembership 작업을 껐다.
- 11:46 에 1127476[puppet] 과 1127478[deployment-charts] 를 되돌렸다.
- 11:58 에 되돌린 내용을 반영하려고 mw-api-int 를 다시 배포했다.
- 12:07 에 mw-api-int 의 레플리카 수를 늘렸다.
- 12:28 에 되돌린 내용을 반영하려고 mw-jobrunner 를 다시 배포했다.
- 14:06 에 되돌린 내용을 반영하려고 mw-parsoid 를 다시 배포했다.
- 후속 조치
- 장애 때 channel:Memcached 로 보내는 메시지 양을 줄인다.
- Scap 에 helm 롤백 기능을 넣는다.
- -Dbuild_mw_container_image:False 로 실행할 때도 Scap 이 helmfile-defaults 를 갱신해야 하는지 검토한다.
- MediaWiki 이미지에서 envvars.inc 를 뺄지 검토한다.
- 서로 관련된 구성 요소를 한꺼번에 보여 주고 따라갈 링크를 담은 "Why" 대시보드를 만들지 검토한다.
- 장애 대응 조율을 개선한다. 누가 무엇을 맡았는지, 누가 대응에 들어오고 나갔는지 장애 조율자가 알 수 있어야 한다. 또 맥락 없는 의견과 대응과 관계없는 논의로 채널이 시끄러워지지 않아야 한다.
- 장애 중에 MediaWiki 개발자와 연락해 개발자의 관점을 바로 들을 수 있게 한다.
- 교훈
Wikimedia 는 기술적 원인보다, 3월 12일 장애 때부터 MediaWiki 배포 세 곳이 Memcached 클러스터에 닿지 못하고 있다는 것을 알아채지 못한 이유가 더 중요하다고 봤다. 알림에 연결된 대시보드는 무엇이 일어나는지만 보여 줬다. Wikimedia 는 서로 의존하거나 영향을 주는 구성 요소를 함께 보여 주는 대시보드가 있었다면 달랐을 것이라고 적었다. 예를 들어 SQLBlobStore 키는 External Storage 와 바로 이어져 있다. 원인을 가리킬 알림은 곧바로 회복됐다. 그 알림이 울렸더라도 알림 78개 사이에서는 부수 증상으로 봤을 수 있었다. 응답자가 많았지만 누가 무엇을 하는지, 누가 대응에 들어오고 나갔는지 분명하지 않아 장애 조율자가 조율하기 어려웠다. 외부 사용자 영향을 제대로 가늠하지 못해 실제보다 심각하게 여겼다. trace.wikimedia.org 도 장애 중에 쓰지 않았다.
- 증상
뉴스 속보로 트래픽이 늘어 파드가 자동으로 늘어나던 중, 새로 뜨는 파드가 설정을 읽지 못해 시작하지 못했다. 컨테이너가 재시작을 거듭하며 파드가 차례로 죽어 장애가 났다.
- 촉발
옛 DB 를 폐기하려고 ConfigMap 과 코드에서 그 설정을 지운 릴리스가 Argo Rollouts 카나리로 파드 하나에만 나갔고, 전체 롤아웃을 잊은 상태였다. 그때 뉴스 속보로 트래픽이 늘어 구 버전 파드가 늘어났다.
- 원인
- 구성 요소
- 기여 요인
- 후속 조치
- Argo Rollouts 의 canary 단계를 setWeight: 1 뒤 pause duration: 1h 를 거쳐 setWeight: 100 으로 자동 롤아웃하도록 설정을 바꿨다.
- Helm 리소스에 "helm.sh/resource-policy": keep 주석을 달아 지난 버전의 ConfigMap 을 남기는 설정을 도입한다.
- ConfigMap 이름에 넣은 리비전 번호로 최신부터 순서를 확인해 지난 버전을 정리하는 Helm 플러그인 helm-clear 를 만들었다. CD 에서 helm clear configmap 에 --history 5 를 주어 지난 다섯 버전만 남기게 했다.
- 교훈
옛 DB 를 없애는 일은 드무니 다음에 조심하자는 회고로는 장애가 곧 다시 난다. 사람의 주의력은 믿을 수 없으므로, 재발을 막으려면 사람의 주의에 기대지 않는 장치를 만들어야 한다. Argo Rollouts 도입 초기에는 사람의 손을 거치지 않고 자동으로 롤아웃되는 것을 걱정했다. 그러나 몇 년 운영해 보니 오히려 롤아웃을 잊는 일이 더 많았고, 큰 릴리스는 어차피 Datadog 같은 감시 화면을 지켜보므로 거의 잊지 않았다. 지난 ConfigMap 을 남기자 ConfigMap 이 바뀐 릴리스도 CI/CD 를 거치지 않고 빠르게 롤백할 수 있게 되었다.
- 증상
Kubernetes 클러스터의 master 노드에서 디스크 장애가 났다. 커널 로그에 "[HAWKEYE] kernel:: [22329228.540857] blk_update_request: I/O error, dev sda, sector 795030104 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0, at" 오류가 찍혔다.
- 촉발
하드웨어 장애로 master 1번 노드의 sda 디스크에 I/O 오류가 나고 읽기 전용 상태가 됐다.
- 원인
- 구성 요소
- 기여 요인
- 감지
커널 로그에 sda 디스크의 blk_update_request I/O 오류가 찍히면서 디스크 장애를 알았다.
- 조치
- Kubespray inventory 의 kube_control_plane 순서를 바꿔 node-2 가 첫 번째 control plane 노드가 되게 했다.
- 하드웨어 장애가 난 1번 장비를 클러스터에서 제거했다.
- kubectl edit cm -n kube-public cluster-info 로 cluster-info ConfigMap 의 옛 control plane IP 를 살아 있는 장비의 IP 로 바꾸고 certificate-authority-data 값도 바꿨다.
- 새 노드에 kubelet 을 비롯한 컴포넌트를 설치하고 kubeadm join --control-plane --skip-phases control-plane-join/etcd 로 control plane 노드를 추가했다.
- etcd 노드를 따로 추가했다.
- 교훈
Kubespray 는 첫 번째 control plane 노드에 특별한 권한을 주기 때문에, master 세 대로 HA 를 구성해도 1번 노드의 장애 복구는 매우 까다롭다. 복구하려면 inventory 순서를 바꿔 1번 역할부터 다른 노드로 넘겨야 한다. control plane 노드가 죽으면 etcd 쿼럼이 위험해지므로 다른 노드가 더 망가지기 전에 etcd 를 되살려야 한다.
이 6건에서 가장 많이 나오는 구성 요소: 컨피그맵과 시크릿, Helm. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.