Cloudflare 2025년 11월 18일 장애 사후 분석
(blog.cloudflare.com)- 2025년 11월 18일 11시 20분(UTC)에 Cloudflare 네트워크의 핵심 트래픽 전달 기능이 중단되어 전 세계 사용자들이 오류 페이지를 보게 됨
- 원인은 데이터베이스 권한 변경으로 인해 Bot Management 시스템의 ‘feature 파일’이 비정상적으로 커진 것이며, 사이버 공격과는 무관함
- 이 파일 크기 증가로 인해 트래픽 라우팅 소프트웨어가 한계치를 초과해 실패, HTTP 5xx 오류가 대규모로 발생
- 14시 30분경 문제 파일의 배포를 중단하고 이전 정상 버전으로 교체해 핵심 트래픽 복구, 17시 6분에 모든 서비스 정상화
- Cloudflare는 이번 사태를 2019년 이후 최악의 장애로 평가하며, 구성 파일 검증 강화와 전역 차단 스위치 도입 등 재발 방지 조치 추진
장애 개요
- 11시 20분경 Cloudflare 네트워크에서 핵심 트래픽 전달 실패가 발생, 사용자들은 Cloudflare 내부 오류 페이지를 확인
- 사이버 공격이나 악의적 행위는 원인이 아님, 데이터베이스 시스템의 권한 변경이 직접적 원인
- 해당 변경으로 인해 Bot Management 시스템이 사용하는 ‘feature 파일’의 크기가 두 배로 증가, 네트워크 전반에 배포됨
- 트래픽 라우팅 소프트웨어가 이 파일을 읽는 과정에서 파일 크기 제한을 초과, 시스템 오류 발생
- 초기에는 대규모 DDoS 공격으로 오인했으나, 원인 파악 후 이전 정상 파일로 교체하여 복구 진행
장애 진행 및 영향
- 11:20 이전에는 5xx 오류 비율이 정상 수준이었으나, 이후 잘못된 feature 파일 배포로 오류 급증
- ClickHouse 데이터베이스 클러스터의 일부 노드에서 잘못된 쿼리 결과가 5분 간격으로 생성, 정상·비정상 파일이 번갈아 배포되어 시스템이 반복적으로 복구와 실패를 반복
- 14:30부터 문제 파일 생성을 중단하고 정상 파일을 수동 삽입, 코어 프록시 재시작으로 복구
- 17:06에 모든 서비스 정상화
| 서비스 | 영향 내용 |
|---|---|
| Core CDN 및 보안 서비스 | HTTP 5xx 오류 발생 |
| Turnstile | 로드 실패, 로그인 불가 |
| Workers KV | 게이트웨이 실패로 5xx 오류 급증 |
| Dashboard | Turnstile 장애로 로그인 불가 |
| Email Security | 스팸 탐지 정확도 일시 저하, 일부 자동 이동 실패 |
| Access | 인증 실패 다수 발생, 기존 세션은 유지 |
- 장애 기간 동안 CDN 응답 지연 증가, 디버깅 시스템의 CPU 사용량 급증이 원인
장애 원인: Bot Management 시스템
- Cloudflare의 Bot Management 모듈은 머신러닝 모델을 사용해 요청별 봇 점수를 생성
- 모델 입력으로 사용되는 feature 구성 파일이 몇 분마다 네트워크 전체에 배포되어 최신 위협에 대응
- ClickHouse 쿼리 동작 변경으로 중복된 feature 행이 다수 포함, 파일 크기 증가
- 이로 인해 Bot Management 모듈이 오류를 일으켜 HTTP 5xx 응답 반환, Workers KV와 Access에도 영향
- 신규 프록시 엔진 FL2에서는 5xx 오류 발생, 구버전 FL에서는 봇 점수가 0으로 설정되어 오탐 증가
ClickHouse 쿼리 동작 변경
- 11:05에 ClickHouse의 데이터베이스 접근 권한 변경 배포
- 기존에는
default데이터베이스의 메타데이터만 조회 가능했으나, 변경 후r0데이터베이스의 메타데이터도 노출 - Bot Management의 feature 파일 생성 쿼리가 데이터베이스 이름 필터 없이 실행, 결과적으로 중복 컬럼 반환
- 이로 인해 feature 파일의 행 수가 두 배 이상 증가, 시스템 한계 초과
메모리 사전 할당 및 시스템 패닉
- Bot Management 모듈은 최대 200개의 머신러닝 feature 제한을 두고 메모리를 사전 할당
- 잘못된 파일이 200개를 초과하는 feature를 포함하면서 Rust 코드에서 panic 발생,
thread fl2_worker_thread panicked: called Result::unwrap() on an Err value오류 출력 - 이로 인해 HTTP 5xx 오류 대량 발생
기타 영향 및 복구 과정
- Workers KV와 Access가 코어 프록시에 의존해 장애 영향 확대
- 13:04에 Workers KV가 프록시를 우회하도록 패치, 오류율 감소
- Dashboard는 Turnstile과 Workers KV 의존으로 로그인 불가
- 11:30~13:10, 14:40~15:30 두 차례 가용성 저하
- 백로그와 재시도 요청으로 지연 증가, 15:30경 복구
- 14:30 이후 대부분 서비스 정상화, 17:06 완전 복구
재발 방지 조치
- Cloudflare 생성 구성 파일의 입력 검증 강화
- 전역 기능 차단 스위치(kill switch) 확대
- 오류 보고로 인한 시스템 자원 고갈 방지
- 코어 프록시 모듈 전반의 오류 조건 검토
타임라인 요약 (UTC)
| 시각 | 상태 | 설명 |
|---|---|---|
| 11:05 | 정상 | 데이터베이스 접근 제어 변경 배포 |
| 11:28 | 영향 시작 | 고객 트래픽에서 첫 오류 발생 |
| 11:32–13:05 | 조사 진행 | Workers KV 오류 원인 분석, 완화 시도 |
| 13:05 | 영향 감소 | Workers KV 및 Access 우회 적용 |
| 13:37 | 복구 작업 집중 | Bot Management 구성 파일 롤백 준비 |
| 14:24 | 문제 파일 배포 중단 | 정상 파일 테스트 완료 |
| 14:30 | 주요 영향 해소 | 정상 파일 전역 배포, 서비스 복구 시작 |
| 17:06 | 완전 복구 | 모든 서비스 정상화 완료 |
결론
- 이번 장애는 Bot Management 구성 파일 생성 로직과 데이터베이스 권한 변경의 상호작용으로 발생
- Cloudflare는 이를 2019년 이후 가장 심각한 네트워크 중단으로 평가
- 향후 시스템 복원력 강화를 위한 구조적 개선과 자동화된 방어 체계 강화 추진
근본적인 문제는 잘못된 쿼리죠.
근데 전 unwrap으로 문제 검증을 생략한 것도 문제라고 생각해요
내부적으로 문제가 생길지언정 에러처리했으면 트래픽 다운되진 않았을테니까요
Hacker News 의견
-
이건 수백만 달러짜리 .unwrap() 사고담임
인터넷의 핵심 인프라 경로에서.unwrap()을 호출한다는 건 “절대 실패하지 않는다, 실패하면 바로 스레드를 죽인다”는 선언과 같음
Rust 컴파일러는 실패 가능성을 명시적으로 표현하도록 강제하지만, 이들은 우아하게 처리하지 않고 패닉을 택했음
전형적인 “parse, don’t validate” 안티패턴의 사례라고 생각함- 많은 사람들이
.unwrap()에 맹점이 있는 듯함. 예제 코드에서 자주 보이기 때문일지도 모름
실제 운영 코드에서는.unwrap()이나.expect()를 패닉처럼 리뷰해야 함
프로덕션 코드에서.unwrap()을 쓴다면 반드시 “INFALLIBILITY” 주석을 달아야 하고,clippy::unwrap_used로 이를 강제할 수 있음 - 이 글의 요점은 단일 원인보다 정상적인 구성요소들의 조합이 문제를 일으킨다는 것 같음
단순히.unwrap()때문이 아니라, 쿼리가 데이터베이스를 구분하지 않아 페이로드가 커졌고, ClickHouse가 더 많은 DB를 노출했기 때문임
“unwrap 때문”이라고 단정하기보다, 글로벌 킬 스위치나 시스템 리소스를 압도하지 않도록 하는 설계 개선이 더 중요하다고 봄 - 사실 이건 Rust 경로 외에서도 실패했음. 봇 관리 시스템이 모든 트래픽을 봇으로 분류했기 때문임
FL2 레이어에서 각 컴포넌트의 패닉을 잡아야 하지만, fail-open이 항상 더 나은 건 아님
패닉을 잡아 처리할지 여부를 FL2 레벨에서 명시적으로 결정하도록 로직을 추가해야 함 - 만약 우아하게 처리했다면 성능 저하가 있었을 것 같음 (그래도 신뢰성 저하보단 낫다고 생각함)
샤딩된 시스템이라면 점진적 롤아웃과 모니터링이 왜 없었는지도 궁금함 - Swift에는 암시적
!언랩과 명시적?언랩이 있음
나는 암시적 언랩을 거의 쓰지 않음. 보장된 값이라도 항상 명시적으로 처리함
예를 들어@IBOutlet weak var someView!대신@IBOutlet weak var someView?로 정의함
일종의 belt & suspenders 접근임
- 많은 사람들이
-
장애 후 24시간도 안 돼 post mortem을 공개한 건 정말 대단한 일임
- 이렇게 빠르고 투명하게 공개할 수 있는 내부 정책이 궁금함
대부분의 대기업이라면 여러 stakeholder 검토로 인해 코드 공개는 거의 불가능함 - 게다가 글 자체도 잘 썼음. AWS의 포스트모템과 비교하면 거의 문학 수준임
- 이렇게 빠르고 투명하게 공개할 수 있는 내부 정책이 궁금함
-
Cloudflare의 장애 설명을 읽으며, “왜 복구에 그렇게 오래 걸렸을까” 궁금했음
장애 원인은 이해했지만, 네트워크 대부분이 다운됐다면 최근 설정 변경을 되돌리는 게 1순위 아닌가 싶었음
물론 사후적으로는 명확하지만, 7분 만에 조사 시작한 건 인상적임- 처음엔 공격으로 오인했음. 이후 문제를 파악했지만, 손상된 파일을 큐에 교체할 방법이 없었고, 전 세계 수많은 머신을 재시작해야 했음
-
이 사건은 CrowdStrike 사고와 유사하게 느껴짐
자동 생성된 설정 파일이 소프트웨어를 망가뜨렸고, 전체 네트워크로 전파됨
빠른 배포가 필요하다는 건 이해하지만, 이번 일은 점진적 롤아웃과 롤백 전략의 부재를 드러냄- 이건 CI/CD 배포라기보다 분산 봇 탐지 시스템의 제어 채널로 보는 게 맞음
- 시뮬레이터 없이 바로 프로덕션에 푸시했다는 게 놀라움
- 그래도 이번 일을 계기로 개선이 이뤄질 거라 믿음
-
Cloudflare가 발표한 향후 개선 계획을 보면,
- Cloudflare 생성 설정 파일의 검증 강화
- 글로벌 킬 스위치 추가
- 코어 덤프나 에러 리포트로 인한 리소스 고갈 방지
-
프록시 모듈의 오류 모드 검토
등이 있음
하지만 카나리 배포나 점진적 구성 배포는 빠져 있음
글로벌 스위치는 위험할 수 있고, 버그 하나로 전체 시스템을 멈출 수도 있음 - 봇 관리 설정은 빠르게 전파돼야 하지만, 한 인스턴스에서 먼저 테스트했다면 패닉을 미리 잡았을 것임
왜 ClickHouse를 feature flag 저장소로 썼는지도 의문임. ClickHouse deduplication 문서에도 위험 요소가 있음 - 설정 서비스는 점진적 롤아웃이 있었지만, 소비하는 서비스들이 너무 자주 자동 업데이트되어 작은 비율의 오류도 전체에 영향을 줬음
- 글로벌 설정은 빠른 대응에 유용하지만, 빠른 롤백 체계가 필수임
서비스 간 의존 관계 맵핑이 있으면 원인 추적이 훨씬 쉬워질 것임 - 결국 대부분의 대형 장애는 config push 때문임
코드 배포는 신중하지만 설정 배포는 그렇지 않음. 설정도 코드라는 인식이 필요함
-
상태 페이지가 다운돼 공격으로 오인했다는 부분이 흥미로움
Cloudflare 인프라와 완전히 분리돼 있다는데, 왜 같이 죽었는지 설명이 없음- 아마 트래픽 급증으로 인한 인프라 확장 실패일 가능성이 높음
- 실제로는 Cloudflare 의존성이 없다고 생각했지만, 인터넷의 상당 부분이 CloudFront에 의존하기 때문에 완전히 독립적이지 않았을 수도 있음
- 원인을 알려면 statuspage.io의 포스트모템이 필요함. Atlassian이 운영하는 서비스임
- 단순히 Cloudflare의 규모가 너무 커서 트래픽이 몰렸을 수도 있음
-
나는 Turnstile을 fail-open 전략으로 통합했는데, 이번 장애에서 효과를 봤음
JS가 로드되지 않으면 더미 토큰으로 제출을 허용하고, 백엔드에서 검증 실패 시에도 fail-open으로 처리함
일부 사용자는 여전히 차단됐지만, 전체 영향은 줄었음
다른 봇 완화 수단이 있어서 가능한 접근임- 그렇다면 공격자가 스크립트를 차단하면 CAPTCHA를 우회할 수 있지 않냐는 의문이 있음
하지만 이는 타깃형 공격이 아닐 때만 통할 것 같음
- 그렇다면 공격자가 스크립트를 차단하면 CAPTCHA를 우회할 수 있지 않냐는 의문이 있음
-
왜 Cloudflare 코드에
.unwrap()을 허용했는지 의문임
최소한expect("이건 절대 일어나지 않음")이라도 써야 했음
에러를 값으로 다루는 철학이 이런 문제를 막기 위한 것인데, 진단을 훨씬 쉽게 했을 것임- 나는 Cloudflare 직원은 아니지만 Rust를 많이 씀
네트워크 호출이 포함된 코드에서는 실패 가능성이 너무 많음
개발 중엔.unwrap()을 쓰지만, 프로덕션에서는 종종 expect() 를 남겨두기도 함. 더 이상 진행할 방법이 없을 때가 있기 때문임
- 나는 Cloudflare 직원은 아니지만 Rust를 많이 씀
-
진짜 교훈은 너무 많은 기능이 소수의 플레이어에 의존한다는 점임
승자독식 구조가 심화되며 시스템의 탄력성이 떨어지고 있음
물론 그들이 실력으로 얻은 자리지만, 인터넷이 항상 “정상 작동”할 거라 기대하는 건 과한 것 같음 -
“Rust면 다 안전하다”는 말은 과장임
어떤 언어든 잘못 쓰면 총구를 자기 발에 겨누는 것과 같음