그래프
사례2건
2건 중 1~2번째
- 증상
DNS 오류율과 서버 부하가 늘었다는 알림이 울렸다. 일부 AdGuard DNS 사용자는 DNS 조회가 느려지거나 실패했고, 타임아웃과 SERVFAIL 응답을 받았다.
- 촉발
AdGuard 는 11월 25일에 사용자 데이터 캐시 방식을 개선하려는 새 AdGuard DNS 버전을 배포했다. 버그 때문에 리졸버가 로컬 캐시 파일에 전체 사용자 데이터가 아니라 최근에 바뀐 일부만 저장했다. 다시 시도한 배포에서 많은 리졸버가 한꺼번에 전체 동기화를 시작했다.
- 원인
- 구성 요소
- 기여 요인
- 감지
15시 30분에서 16시 UTC 사이, 두 번째 배포를 시작한 뒤 DNS 오류율과 서버 부하가 늘었다는 알림이 울렸다.
- 조치
- 버그가 있는 버전을 모든 서버에서 동시에 롤백했다. 롤백은 17시 30분에서 18시 UTC 사이에 끝났다.
- 암스테르담의 트래픽 일부를 다른 유럽 거점으로 돌렸다. 암스테르담은 안정됐지만 프랑크푸르트와 런던의 부하가 늘었다.
- 프랑크푸르트와 런던에서 실제 연결 동시성과 파이프라인 깊이를 줄였다. 프랑크푸르트에서 파이프라인 깊이를 5에서 1로 줄이자 매우 빨리 회복됐다.
- 한도를 실제 하드웨어 용량에 맞게 조정했다.
- 전체 동기화가 더 일어나지 않도록 손상된 캐시 파일과 사용자 데이터 스냅샷을 고쳤다.
- 트래픽 분배를 정상으로 돌렸고 오류율이 평소 수준으로 내려왔다.
- 후속 조치
- 전체 사용자 데이터가 로컬 캐시 파일에 제대로 직렬화되고 그 파일에서 제대로 복원되는지 확인하는 자동 테스트를 넣는다. 캐시가 비었거나 잘린 것도 잡아내게 한다.
- 실제와 비슷한 부하에서 빈 캐시나 손상된 캐시로 리졸버를 시작하는 통합 테스트를 넣어, 시스템이 무리 없이 동작하고 비즈니스 로직 서비스에 과부하를 주지 않는지 확인한다.
- 거점마다 서버 하드웨어 사양을 하나로 맞춰 RAM 크기가 크게 다른 서버가 한 클러스터에 섞이지 않게 하거나, 하드웨어 종류마다 설정을 따로 관리한다.
- 표준 노드 종류마다 connlimit, 캐시 크기, 연결 한도를 다시 살펴 조정해 32 GiB 노드와 64 GiB 노드가 모두 안전한 가비지 컬렉션과 메모리 범위 안에서 돌게 한다.
- 최대 부하에서도 고루틴과 힙이 지나치게 늘지 않도록 파이프라이닝 깊이를 다시 따져 보고 필요한 곳은 낮춘다.
- 연결 수와 고루틴 수를 제한하는 방식을 다시 따져 보고, 자원 사용을 적절히 제한하면서도 연결이 끊긴 뒤에도 처리량이 충분히 나오게 한다.
- 모니터링이 CPU 와 전체 메모리뿐 아니라 Go 힙 크기, 가비지 컬렉션 시간, 초당 가비지 컬렉션 횟수, 클러스터별 요청 지연 분포도 보게 넓힌다.
- 상위 DNS 가 정상인데 SERVFAIL 비율과 요청 처리 시간이 계속 오르면 울리는 알림을 따로 둬, 내부 경합과 가비지 컬렉션 문제를 사용자가 겪기 전에 잡는다.
- DNS 리졸버의 배포와 롤백 절차를 다듬어 전체 서버의 큰 부분에서 캐시가 동시에 무효화되지 않게 한다.
- 암스테르담과 프랑크푸르트에서 본 동작을 바탕으로, 사고 중에 파이프라인 깊이, 연결 한도, connlimit 을 빨리 조정하는 플레이북을 문서로 만들고 연습한다.
- 교훈
노드마다 다르게 움직인 데에는 사용할 수 있는 RAM, Go 가비지 컬렉터, connlimit 설정이 서로 맞물린 것이 컸다. 메모리가 큰 서버에서 connlimit 을 너무 높게 잡으면 힙이 크게 자란 뒤에야 가비지 컬렉션이 강하게 돈다. 그러면 메모리는 남아도 CPU 시간이 가비지 컬렉션에 점점 더 쓰여, 지연이 늘고 처리량이 준다. AdGuard 는 이것을 메모리를 많이 쓰는 애플리케이션에서 connlimit 이 만드는 알려진 최악의 경우라고 했다. 메모리가 작은 서버는 캐시 크기와 연결 한도를 RAM 에 맞춰 줄이지 않아서 더 일찍 실패했다. 여기에 높은 파이프라이닝과 많은 재시도가 겹쳐 가비지 컬렉션 압박, 연결 포화, 클라이언트 재시도가 서로를 키우는 고리가 생겼다.
- 증상
2023년 10월 4일 07:00 UTC 부터 1.1.1.1 을 직접 쓰거나 WARP, Zero Trust, 또는 1.1.1.1 을 쓰는 다른 회사의 DNS 리졸버를 거치는 일부 사용자가 유효한 질의에 SERVFAIL DNS 응답을 받았다. 07:57 에 예상하지 못한 SERVFAIL 을 알리는 외부 신고가 처음 들어왔다.
- 촉발
9월 21일 루트 존에 ZONEMD 라는 새 리소스 레코드 형식이 처음 들어왔고, 1.1.1.1 리졸버가 이 레코드를 파싱하지 못해 새 루트 존을 쓰지 못했다. 10월 4일 07:00 UTC 에 계속 쓰던 9월 21일 판 루트 존의 DNSSEC 서명이 만료됐다.
- 원인
- 구성 요소
- 기여 요인
- 감지
07:57 UTC 에 예상하지 못한 SERVFAIL 을 알리는 외부 신고가 처음 들어왔고, 08:03 에 Cloudflare 내부 장애가 선언됐다. static_zone 이 오래된 루트 존을 내주는 상황을 알리는 알림은 없었다.
- 조치
- 08:50 UTC 에 override 규칙으로 1.1.1.1 이 오래된 루트 존 파일로 응답하지 않게 하려 했다. 다른 노드로 넘긴 질의에는 rec_disable_static 태그가 실리지 않아 static_zone 앱이 계속 오래된 정보로 답했다.
- 10:30 UTC 에 1.1.1.1 이 루트 존 파일을 아예 미리 읽지 않게 했다. 10:32 에 응답이 정상으로 돌아왔다.
- 후속 조치
- static_zone 이 오래된 루트 존 파일을 내줄 때 알리는 알림을 더한다.
- 루트 존을 받아 와 내부에 배포하는 방식을 다시 검토해, 새 RRTYPE 을 문제없이 다루고 파이프라인이 잠깐 끊겨도 최종 사용자에게 보이지 않게 한다.
- 루트 존 파싱이 실패할 때 어떻게 되는지 충분히 테스트하지 않았으므로 테스트 범위와 관련 절차를 개선한다.
- RFC 8806 이 설명하는 대로 캐시한 루트 존 데이터의 수명을 관리해, 일정 시점이 지난 오래된 루트 존 사본을 쓰지 않게 한다.
- 교훈
Cloudflare 는 무엇이든 바뀌지 않으리라고 가정하지 말라는 것을 이 사고의 분명한 교훈으로 꼽았다. 최종 실행 파일에 들어가는 긴 라이브러리 사슬 어디에든 버그가 있거나, 입력이 바뀔 때 제대로 동작할 만큼 일찍 갱신되지 않은 것이 있을 수 있다고 했다. 회귀를 잡아내는 테스트와 입력이 바뀌어도 부드럽게 실패하는 시스템이 중요하다고 했다. DNS 와 BGP 처럼 인터넷에서 가장 중요한 시스템의 형식 변경은 영향을 줄 것이라고 늘 가정해야 한다고 했다. 오래된 루트 존을 내주는 것을 더 잘 감시했다면, 이미 있는 캐시 덕분에 영향이 없었을 것이라고 했다.
이 2건에서 가장 많이 나오는 구성 요소: 재귀 DNS 리졸버 서비스, 언어 런타임 설정. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.