장애 시나리오 - Team-likelion-3rd-Project/likelion-devops-7th-3rd-team01 GitHub Wiki
장애 시나리오
1주차(M1)에는 무엇을 어떻게 시험할지만 적는다. 실행 결과는 6주차(M6)에 채운다. 담당: ON 김상협 / CL 김차니 / BE 이용민
ON (온프레미스) 장애 시나리오
김상협 작성. 워커 노드 다운, etcd 복구, MySQL 백업 복원, 복구 시간 측정 등
시나리오 목록
| # | 장애 유형 | 영향 | 탐지 방법 | 대응 | 목표 복구 시간 |
|---|---|---|---|---|---|
| O1 | 노드 다운 |
백업 정책
| 대상 | 주기 | 보관 기간 | 저장 위치 |
|---|---|---|---|
복구 절차
CL (클라우드) 장애 시나리오
2026-10-14: 클라우드 시험 계획과 백업·복구 초안 등록. 실제 시험 결과는 6주차에 기록합니다.
시험 공통 기준
- 4주차 EC2+kubeadm과 5주차 이후 EKS를 구분해 기록한다. 제어 노드 1대인 kubeadm 구성의 한계는 별도로 남긴다.
- 테스트 데이터와 AI mock을 사용한다. 시험 전 정상 응답·백업·잔여 노드 용량을 확인한다.
- 한 번에 한 장애만 시험하고, 대상·시작 시각·복구 방법을 기록한다. 데이터 삭제나 실제 장애 주입은 이 문서 작성 작업에 포함하지 않는다.
- 복구 시간은 장애 주입부터 정상 응답이 연속 확인될 때까지 측정한다. 장애 전환 시간과 앱 재연결 시간을 구분한다. 목표 시간의 숫자는 팀 협의 후 확정하며 지금은 실측값을 기입하지 않는다.
- 오류율·응답 시간·파드 상태·ALB 정상 대상·DB/Redis 이벤트와 앱 로그를 함께 확인한다.
| ID | 장애와 시험 방법 | 예상 영향·탐지 | 대응·복구 | 성공 기준 |
|---|---|---|---|---|
| C1 | 테스트 작업 노드 1대를 중지해 비정상 종료를 가정한다. 정상 drain 시험과 구분한다. | 해당 노드의 파드 중단. 노드 NotReady, 정상 파드 수와 ALB 정상 대상 감소를 관찰한다. | 남은 노드에서 파드가 재생성되는지 확인하고 원래 노드를 복구한다. | 남은 용량으로 조회가 재개되고 파드 수가 정상화된다. 오류율·복구 시간을 기록한다. |
| C2 | 한쪽 AZ의 앱 작업 노드를 모두 중지한다. 실제 AZ 전체 장애가 아닌 앱 계층 부분 모의 시험이다. | 한쪽 AZ 앱 중단. ALB 정상 대상과 반대 AZ 앱의 부하를 확인한다. | 반대 AZ 앱의 요청 처리와 해당 AZ NAT를 통한 외부 연결을 확인한 뒤 노드를 복구한다. | 반대 AZ에서 조회와 외부 통신이 가능하다. 결과를 전체 AZ 장애 검증으로 표현하지 않는다. |
| C3 | 테스트 RDS Multi-AZ에서 지원되는 강제 장애 전환 기능으로 전환을 시험한다. | 일시적인 DB 연결 실패, readiness 변화와 RDS 이벤트를 관찰한다. | 앱이 서비스 엔드포인트로 재연결하도록 한다. 실패하면 연결 풀·DNS 갱신·설정을 확인한다. | 전환 후 앱 재배포 없이 읽기·쓰기가 정상화되고 시험 데이터가 유지된다. |
| C4 | 시험 데이터 저장 후 만든 RDS 스냅샷을 별도 테스트 DB로 복원한다. | 원본 DB는 유지한다. 복원 상태와 소요 시간을 확인한다. | 복원 DB의 접근 설정을 확인하고 테스트 앱만 새 엔드포인트에 연결한다. | 표본 데이터·건수와 앱 조회를 검증한다. 복원 시점 이후 데이터 차이를 기록하고 임시 DB를 정리한다. |
| C5 | 테스트 Redis 복제 그룹의 지원되는 장애 전환 기능으로 Primary 전환을 시험한다. | 일시적인 캐시·큐 연결 실패. 복제 그룹 이벤트, 연결 오류, 질문 상태를 확인한다. | 앱 재연결, 조회의 DB 대체 응답, DB 상태를 기준으로 한 대기 질문 재투입을 확인한다. | 조회와 질문 처리가 회복된다. 이미 FAILED인 질문은 임의로 재실행하지 않으며 중복 처리와 유실 여부를 기록한다. |
백업 정책 제안
아래 보관 기간은 초안이며 실제 설정 완료를 의미하지 않는다.
| 대상 | 주기·보관 제안 | 복구 용도 |
|---|---|---|
| RDS | 자동 백업 보관 7일, 주요 변경·장애 시험 전 수동 스냅샷 | 별도 DB로 복원 후 데이터 검증. 수동 스냅샷은 시험 종료 시 보관 필요성을 확인 |
| Redis | 캐시·질문 큐의 복구 기준은 MySQL의 질문 상태 | 캐시는 재생성하고 미완료 질문은 앱 정책에 따라 재투입. Redis 복제를 영구 데이터 백업으로 간주하지 않음 |
| 배포·인프라 구성 | 변경 시 Git에 버전 기록, Terraform 도입 시 상태 파일의 접근 통제·버전 보관 별도 구성 | 해당 버전으로 재구성. 비밀값은 Git에 기록하지 않음 |
복구 절차 공통
- 장애 대상과 영향을 확인하고 다른 변경을 멈춘다.
- 자동 복구·장애 전환 이벤트를 관찰한다.
- 자동 복구가 되지 않으면 원인과 접근·연결 설정을 확인한다.
- 데이터 복원이 필요하면 원본을 유지한 채 별도 DB로 복원·검증한다.
- 조회·로그인·질문 상태와 오류율을 확인한다.
- 시험 변경을 원복하고 임시 자원 정리 및 결과를 기록한다.
실행일·실제 복구 시간·성공 여부는 모두 미실행으로 남긴다. 온프레미스 상세는 담당자 내용 취합 후 보완한다.
BE (백엔드) 장애 시나리오
앱이 의존 서비스 장애를 어떻게 견디는지 시험한다. 인프라 복구(백업·복원)는 ON·CL 부분을 따른다.
원칙
- MySQL이 데이터의 기준이다. Redis는 캐시와 질문 대기열로만 쓰고, Redis에만 있는 데이터는 사라져도 된다.
- 외부 서비스(AI, 뉴스 RSS)가 실패해도 사용자 요청이 500으로 끝나지 않는다. 요약은 직전 것을 계속 보여주고, 질문은
FAILED로 끝난다. - 앱 Pod는 2개 이상 띄우고, 상태를 Pod 안에 두지 않는다. 어느 Pod가 죽어도 다른 Pod가 이어받는다.
- readiness는 DB만 본다. Redis가 죽어도 Pod가 트래픽에서 빠지지 않는다.
시나리오 목록
| # | 장애 유형 | 영향 | 탐지 방법 | 대응 | 목표 복구 시간 |
|---|---|---|---|---|---|
| B1 | Redis 중단 | 조회가 느려짐, 질문 처리 지연 | 앱 로그의 Redis 연결 오류, Grafana Redis 지표 | 조회는 DB에서 바로 응답. 질문은 DB에 PENDING으로 남았다가 Redis 복구 후 대기열에 다시 들어감. 60초 넘으면 FAILED |
조회 실패 0. Redis 복구 후 30초 안에 대기 질문 처리 재개 |
| B2 | MySQL 중단 | 서비스 전체 중단 (503) | readiness 실패, Pod Ready 수 0 알림 | DB 복구 (ON·CL 복구 절차). 앱은 Pod 재시작 없이 자동 복귀 | DB 복구 후 1분 안에 정상 응답 |
| B3 | AI 실패 | 질문 FAILED, 요약 갱신 중단 |
FAILED 질문 수 지표, 요약 배치 실패 로그 |
질문은 실패 안내 (하루 10회에서 차감 안 함). 요약은 직전 것 유지. AI 복구 시 자동 정상화 | 사용자 요청 500 0건 |
| B4 | AI 응답 지연 | 질문이 늦게 FAILED |
AI 응답 시간 지표 | 타임아웃 후 FAILED. 워커는 다음 질문 처리 |
워커 멈춤 0 |
| B5 | 뉴스 RSS 실패 | 요약이 갱신되지 않음 | 요약 배치 실패 로그, 홈 generatedAt이 1시간 넘게 그대로 |
직전 요약 유지, 화면에 갱신 지연 안내 | RSS 복구 후 다음 주기(30분) 안에 갱신 |
| B6 | (제외) 네이버 검색 실패 | 외부 검색 API를 쓰지 않기로 해(10/16) 시나리오에서 뺀다. 번호는 다른 문서와 맞추려고 비워 둔다 | - | - | - |
| B7 | 처리 중 워커 Pod 삭제 | 교체되는 동안 질문 완료가 늦어짐 | Pod 재시작 이벤트 | 워커는 처리 중인 질문을 최대 40초 기다려 끝내고, 못 끝내면 PENDING으로 되돌려 대기열에 다시 넣은 뒤 종료 (M5-9). 쿠버네티스가 Pod 재생성. 같은 질문이 두 번 처리되지 않음 |
질문 FAILED 0 |
| B8 | API Pod 1개 삭제 | 없음 (레플리카 2개 이상) | Pod 재시작 이벤트 | 남은 Pod가 응답, 쿠버네티스가 Pod 재생성. 로그인 유지 (모든 Pod가 같은 JWT 키) | 실패 요청 0 |
| B9 | 요약 배치 실패 | 그 회차 갱신 누락 | CronJob 실패 이벤트 | 직전 요약 유지. 반쯤 만든 요약은 화면에 보이지 않음 | 다음 주기(30분) 안에 정상 갱신 |
| B10 | 롤링 배포 중 요청 실패 | 배포 중 일부 요청 실패 가능 | k6 실패율, Ingress·ALB 5xx 수 | readiness 통과 전에는 트래픽을 받지 않고, 종료되는 Pod는 처리 중인 요청을 끝낸 뒤 내려감 (정상 종료) | 배포 중 실패 요청 0 (M5 완료 기준) |
시험 방법
성능 테스트 시나리오 1(조회)·2(질문)을 k6로 돌리는 도중에 장애를 일으키고, 복구한 뒤 1분 더 관찰한다. AI는 mock.
| # | 장애 일으키는 방법 |
|---|---|
| B1 | 온프렘: Redis Pod 삭제 / AWS: ElastiCache 보안그룹에서 앱 접근 차단 |
| B2 | 온프렘: MySQL Pod 삭제 / AWS: RDS 재부팅 |
| B3 | AI_MODE=fail (mock이 항상 오류 반환) |
| B4 | AI_MOCK_DELAY=70s (질문 타임아웃 60초보다 길게) |
| B5 | 요약 배치 Job을 따로 만들고 그 Pod에서만 전자신문 도메인을 127.0.0.1로 연결 (hostAliases). RSS 주소가 코드에 있어 설정만으로 바꿀 수 없음 |
| B6 | (제외) |
| B7 | PROCESSING 질문이 있을 때 워커 Pod 삭제 |
| B8 | kubectl delete pod (API Pod 2개 중 1개) |
| B9 | 요약 배치 Pod를 실행 중에 삭제 |
| B10 | 부하 중 새 이미지로 롤링 배포 (kubectl rollout restart). 위키 "성능 테스트" 시나리오 4와 같은 시험 |
검증 결과 (6주차)
| 시나리오 | 환경 | 실행일 | 결과 | 실제 복구 시간 | 비고 |
|---|---|---|---|---|---|
| O1 | 온프렘 | ||||
| C1 | AWS | ||||
| B1 | 온프렘 | 10/26 | 성공. 조회 2,570건 실패 0, 질문 FAILED·타임아웃 0 |
Redis Pod 6초 | Redis가 없던 동안 일부 요청이 연결 대기로 느려짐 (조회 최대 7.3초, 질문 접수 p95 2.7초). 대기열에서 사라진 질문은 30초 뒤 재투입돼 최대 35.6초에 완료 |
| B1 | AWS | ||||
| B2 | 온프렘 | 10/26 | 성공. 중단 동안 조회 267건(8.55%) 실패, 복구 뒤 실패 0 | MySQL Pod 27초, API Ready까지 38초 | readiness 실패로 API Pod 2개가 약 22초 동안 트래픽에서 빠짐 (예상대로 서비스 중단). API Pod는 재시작 없이 자동 복귀. 데이터는 PVC에 남음 |
| B2 | AWS | ||||
| B3 | 온프렘 | 10/26 | 성공. 질문 14건 모두 즉시 FAILED, HTTP 실패 0 |
- | 배치는 새 기사가 있던 2개 분야만 AI 실패, 직전 요약 유지 (홈 generatedAt 그대로). 배치 Job이 실패로 끝나 재시도 Pod가 돌았는데, 기사는 첫 Pod가 이미 저장해 "새 기사 0건"으로 성공 처리됨. 그 2개 분야는 다음 새 기사가 들어올 때까지 요약이 갱신되지 않음 → 아래 "개선" 참고 |
| B4 | 온프렘 | 10/26 | 성공. 질문 3건 모두 60초에 FAILED, HTTP 실패 0 |
- | AI_MOCK_DELAY=70s. 워커는 다음 질문을 계속 꺼냄. 원복 후 질문 완료 2초 |
| B5 | 온프렘 | 10/26 | 성공. 6개 분야 모두 수집 실패, 직전 요약 유지 | - | Job은 2번 시도 후 Failed (CronJob 실패 이벤트로 탐지 가능). RSS가 돌아오면 다음 주기에 갱신 |
| B6 | (제외) | ||||
| B7 | 온프렘 | 10/26 | 성공. 질문 FAILED·타임아웃 0, HTTP 실패 0 |
새 워커 Ready 16초 | AI_MOCK_DELAY=15s. 지워진 워커는 처리 중이던 질문을 끝내고 약 18초 뒤 종료. 교체되는 동안 질문이 대기열에서 기다려 접수→완료 p95 30.6초 (최대 46.7초, 60초 타임아웃 안) |
| B8 | 온프렘 | 10/26 | 성공. 조회 2,976건 실패 0, p95 57ms | 새 API Pod Ready 16초 | |
| B9 | 온프렘 | 10/27 | 부분 성공. 반쯤 만든 요약은 화면에 보이지 않음 | 재시도 Pod 약 50초 | 10:00 배치를 AI_MOCK_DELAY=70s로 띄워 첫 분야(AI) 요약을 기다리는 중 Pod 삭제. Job 재시도 Pod가 나머지 분야는 정상 갱신(MOBILE_TELECOM·SCIENCE 새 요약). 다만 AI 분야는 지운 Pod가 기사를 이미 저장해 재시도 Pod가 "새 기사 0건"으로 건너뜀 → 다음 새 기사가 들어올 때까지 요약이 갱신되지 않음 (B3과 같은 원인). 수정 후 11:00 배치에서 AI·SECURITY 분야 다시 요약 확인 |
| B10 | 온프렘 | 10/26 | 성공. 7,579건 실패 0 | - | 성능 테스트 시나리오 4 (롤링 배포 중 p95 105ms) |
| B10 | AWS |
온프렘 시험 공통: k6 조회 3040명·질문 310명 부하 중 장애 주입, AI mock. 시험 중 바꾼 설정(AI_MODE, AI_MOCK_DELAY)은 끝난 뒤 원래대로 되돌림.
개선 (B3·B9에서 발견)
| 항목 | 내용 |
|---|---|
| 문제 | 요약 배치가 "이번 회차에 새로 저장한 기사 수"로 AI 호출 여부를 정해, 기사만 저장하고 요약 전에 실패한 회차(AI 실패, Pod 삭제)가 있으면 Job 재시도 Pod와 다음 배치가 "새 기사 0건"으로 건너뜀. 그 분야 요약이 다음 새 기사가 올 때까지 멈춤 |
| 수정 | 기준을 "마지막 요약을 저장한 뒤에 수집한 기사가 있는지"로 변경 (백엔드 #15, #16, news_issues.created_at 추가). 새 기사가 정말 없으면 지금처럼 AI를 부르지 않음 |
| 확인 | 10/27 온프렘 11:00 배치에서 10/26 16:30부터 멈춰 있던 AI 분야와 SECURITY 분야가 다시 요약됨 |