신뢰성·DR

주제: 백업/복구, RTO/RPO, 멀티AZ/리전, 카오스 테스트, 비용관리


  • RTO/RPO를 “서비스·데이터 유형”별로 숫자로 못 박기(예: 결제 RTO 15m/RPO 1m)
  • 계층별 DR 패턴: 앱(무상태)·DB(PITR+리전 복제)·캐시(버림)·오브젝트(S3 버저닝)·서치(재색인)·큐(DLQ 보전)
  • 아키텍처: 멀티AZ 기본, 필요 시 크로스리전 Warm-standby(가성비) → Active/Active(고가용)
  • 복구 신뢰성: 자동 백업 + 정기 복구 리허설(Runbook/증빙)
  • 페일오버: DNS·게이트웨이·DB 리더 전환 단계별 스위치와 되돌리기
  • 카오스 실험: “노드/POD 사망, DB 장애, 리전 격절” 시나리오를 분기별
  • 비용: 중요도 낮은 워크로드는 Pilot-light, 스토리지는 라이프사이클로 냉장/아카이브

1) 용어·목표 고정

  • RTO(복구시간): 장애 → 정상 복구까지 허용 시간
  • RPO(복구시점): 데이터 손실 허용 시점(최신에서 얼마나 뒤로)

권장 매트릭스(예시)

  • 사용자 인증: RTO 15m / RPO 5m
  • 주문/결제: RTO 15m / RPO 1m
  • 로그/분석: RTO 2h / RPO 1h
  • 마케팅 페이지: RTO 30m / RPO 10m

위 표를 기준으로 각 컴포넌트(DB/캐시/서치/오브젝트/큐)별 DR 등급을 매핑하세요.


2) 계층별 DR 설계

2.1 애플리케이션(무상태 우선)

  • Next.js/BFF/FastAPI: 컨테이너 이미지만 있으면 어디서든 가동 → 다중 리전 배포 파일(Helm values 복수 세트)
  • Feature Flag / Kill Switch: 무거운 기능을 즉시 끄고 핵심 경로만 살리기
  • 구성 데이터는 GitOps(Argo)로 소스오브트루스 유지

2.2 데이터베이스(Postgres)

  • 멀티AZ + 자동 백업(PITR)
  • 크로스리전 읽기 복제(Warm-standby) 또는 논리적 복제
  • 스키마 변경Expand→Migrate→Contract(9편) — DR 중단 리스크 감소

2.3 캐시/세션(Redis)

  • 세션은 짧은 TTL + 재인증 가능 설계
  • Redis는 클러스터/멀티AZ. 리전 DR은 필요 시만(대부분 캐시는 버려도 됨)

2.4 오브젝트 스토리지(S3 등)

  • 버저닝 + MFA Delete + 라이프사이클
  • **크로스리전 복제(CRR)**로 백업 겸 DR

2.5 서치(Elastic/OpenSearch)

  • 색인은 재생성 가능을 전제로 DB→서치 CDC/리플레이 파이프라인 유지
  • DR 시 재색인 스크립트/잡을 즉시 돌릴 수 있게 준비

2.6 큐/스트림(SQS/Kafka)

  • DLQ로 실패 이벤트 보관
  • Kafka는 다중 AZ 브로커 + 스냅샷/미러(MirrorMaker/크로스리전) 검토

3) 페일오버 전략(단계별)

  1. 내부 장애(Pod/노드/AZ): HPA/재시작/PDB/TopologySpread로 흡수
  2. DB 장애: 프라이머리 → 리더 전환, 앱 커넥션 재시도/Idempotency로 이중처리 방지
  3. 리전 장애:
    • DNS 레벨(Route 53 Health Check Failover)
    • CDN 오리진 그룹(주/보조 원본)
    • 데이터 스위치: RDS 크로스리전 리더 승격, 시크릿/엔드포인트 교체

4) 백업·복구 표준(“쓰기보다 복구 검증”)

  • DB: 자동 스냅샷 + WAL 아카이브(PITR). 월 1회 타임라인 복구 리허설
  • 오브젝트: 버전 복원 스크립트, 삭제 보호(MFA Delete)
  • 서치: 스냅샷 or 재색인 잡 검증
  • 인프라: Terraform state 백업, ArgoCD App of Apps로 재구성 가능한 템플릿

5) 실행(복붙) 스니펫

5.1 Route 53 페일오버(요약)

# Terraform 예시
resource "aws_route53_health_check" "api" {
  type = "HTTPS"
  resource_path = "/healthz"
  fqdn = "api-primary.example.com"
  failure_threshold = 3
  request_interval = 30
}

resource "aws_route53_record" "api_failover_primary" {
  zone_id = var.zone_id
  name    = "api.example.com"
  type    = "A"
  set_identifier = "primary"
  failover_routing_policy { type = "PRIMARY" }
  alias {
    name                   = aws_lb.primary.dns_name
    zone_id                = aws_lb.primary.zone_id
    evaluate_target_health = true
  }
  health_check_id = aws_route53_health_check.api.id
}

resource "aws_route53_record" "api_failover_secondary" {
  zone_id = var.zone_id
  name    = "api.example.com"
  type    = "A"
  set_identifier = "secondary"
  failover_routing_policy { type = "SECONDARY" }
  alias {
    name                   = aws_lb.secondary.dns_name
    zone_id                = aws_lb.secondary.zone_id
    evaluate_target_health = true
  }
}

5.2 S3 버저닝/라이프사이클

resource "aws_s3_bucket_versioning" "main" {
  bucket = aws_s3_bucket.main.id
  versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_lifecycle_configuration" "main" {
  bucket = aws_s3_bucket.main.id
  rule {
    id = "logs"
    filter { prefix = "logs/" }
    status = "Enabled"
    transition { days = 30 storage_class = "STANDARD_IA" }
    transition { days = 90 storage_class = "GLACIER" }
    expiration { days = 365 }
  }
}

5.3 Postgres WAL 아카이브(컨테이너 + wal-g)

# env 예시
WALG_S3_PREFIX=s3://ex-pg/wal
AWS_REGION=ap-northeast-2
WALE_S3_ENDPOINT=s3.amazonaws.com
PGHOST=localhost
# 복구 리허설(개념)
export WALG_S3_PREFIX=s3://ex-pg/basebackups_005
wal-g backup-fetch /var/lib/postgresql/data LATEST
wal-g wal-fetch LATEST /var/lib/postgresql/data/pg_wal
# postgresql.conf: restore_command='wal-g wal-fetch %f %p'

5.4 K8s: 백업 CronJob(논리 덤프)

apiVersion: batch/v1
kind: CronJob
metadata: { name: pg-dump }
spec:
  schedule: "0 3 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: dump
            image: postgres:16
            envFrom: [{ secretRef: { name: api-secrets } }]
            args: ["bash","-lc","pg_dump -Fc -d $DB_URL > /backup/$(date +%F).dump"]
            volumeMounts: [{ name: backup, mountPath: /backup }]
          volumes: [{ name: backup, persistentVolumeClaim: { claimName: backup-pvc } }]

5.5 CloudFront 오리진 페일오버(요지)

  • Origin Group: Primary: api-primary, Secondary: api-secondary
  • HealthCheck: /healthz 5xx/타임아웃 시 Secondary로

5.6 카오스 테스트(Chaos Mesh 예)

apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata: { name: kill-api-pod }
spec:
  action: pod-kill
  mode: one
  selector: { namespaces: ["prod"], labelSelectors: { app: "api" } }
  duration: "60s"
  scheduler: { cron: "@monthly" }

6) 운영 시나리오(런북 템플릿)

A. DB 프라이머리 장애

  1. 알람 확인(에러율↑/DB 연결 실패)
  2. 트래픽 리드온리 모드 또는 큐잉(쓰기 차단)
  3. 리더 승격 → 앱 시크릿의 DB 엔드포인트 교체 → 롤아웃
  4. 일관성 확인(replication lag, 최근 트랜잭션 검증)
  5. 정상화 후 원복 계획 수립(역복제/재동기)

B. 리전 장애

  1. DNS/오리진 페일오버 트리거
  2. RDS 보조 리전 리더 승격
  3. Redis/서치 대체(세션 재인증, 재색인 잡 시작)
  4. SLO/버짓 모니터링, 비핵심 기능 Kill Switch
  5. 사후 보고(영향 범위/복구시간/재발 방지)

7) 테스트·증빙(필수)

  • 월 1회: DB 복구(지정 시점), 서치 재색인, S3 버전 복원
  • 분기 1회: 카오스(노드/Pod Kill, 네트워크 단절) + Runbook 타임드릴
  • 반기 1회: 리전 DR 데이(파일럿) — Route53/오리진 페일오버 실전
  • 결과는 **증빙 저장소(evidence/dr/)**에 리포트/스크린샷 첨부

8) 비용 최적화

  • Warm-standby: 컴퓨트는 최소, DB는 저사양 리더/리플리카로 유지
  • 데이터 계층별 차등: 핵심(DB/S3 메타)만 크로스리전, 서치는 DR시 재색인
  • 스토리지 라이프사이클: 장기보관 로그/스냅샷은 아카이브 클래스로 자동 이전
  • 테스트 주기 합리화: 과도한 카오스는 비용↑ → 핵심 시나리오 위주

9) 48시간 액션 플랜

  • RTO/RPO 표를 서비스/데이터 유형별로 확정
  • RDS 자동백업+PITR 확인, WAL 아카이브 설정
  • S3 버저닝+라이프사이클 적용, 삭제보호(MFA Delete) 점검
  • Route 53 Failover 레코드(헬스체크) 초안 생성
  • 카오스 퀵테스트: Pod Kill + 런북 타임드릴(15분 내 종료 목표)

10) 2주 액션 플랜

  1. 크로스리전 리더(DB) 준비 또는 논리 복제 경로 확보
  2. 재색인 잡(서치)과 데이터 리플레이(Outbox/CDC) 스크립트 작성
  3. DR 데이 체크리스트/역할 분담 문서화(연락망 포함)
  4. Kill Switch/Degrade 모드 플래그 도입(프론트 배너/공지 자동)
  5. 증빙 저장소(evidence/dr) 신설: 리허설 결과 주기 기록

마무리

신뢰성·DR의 핵심은 **“수치로 목표를 고정하고(=RTO/RPO), 복구를 주기적으로 연습한다”**입니다.
멀티AZ는 기본, Warm-standby로 가성비 있는 리전 DR을 깔고, 복구 리허설카오스 실험으로 자신감을 쌓으세요.

다음은 13편. 비용·운영 최적화 — FinOps, 캐파 계획, 성능/비용 트레이드오프로 갈게요.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다