--- name: dr-automation description: "GUARDiA DR(재해복구) 자동화 구현 스킬. DR 시나리오 관리, Failover 실행, 백업 무결성 검증, 복구 테스트, RTO/RPO 추적 기능을 FastAPI + paramiko 패턴으로 구현한다. 다음 상황에서 반드시 사용: (1) 'DR 구현', '재해복구', 'Failover', 'RTO/RPO' 관련 요청; (2) dr.py 라우터 또는 core/dr_engine.py 작업; (3) 백업 무결성 검증, 복구 테스트 구현; (4) DR 대시보드 구현; (5) 다시 실행, 업데이트, 보완 요청. paramiko SSH 패턴과 SQLAlchemy async 패턴을 따른다." --- # DR 자동화 구현 스킬 ## 구현 대상 파일 - `itsm/core/dr_engine.py` — DR 비즈니스 로직 - `itsm/routers/dr.py` — FastAPI 라우터 ## 핵심 구현 원칙 1. **Fail-Safe 시퀀스**: 스냅샷 → 대기서버 활성화 → 서비스 전환 → 헬스체크 → 롤백(실패 시) 2. **자격증명 보호**: paramiko 접속 시 IP/계정 노출 금지, AES 복호화 후 메모리만 사용 3. **비동기**: asyncio.create_subprocess_exec + paramiko를 run_in_executor로 래핑 4. **감사 기록**: 모든 DR 작업은 tb_audit_log에 기록 ## DB 모델 (models.py에 추가) ```python class DRScenario(Base): __tablename__ = "tb_dr_scenario" id = Column(Integer, primary_key=True) name = Column(String(100), nullable=False) scenario_type = Column(String(30)) # SITE_FAILURE | SERVER_FAILURE | DATA_CORRUPTION primary_server_id = Column(Integer, ForeignKey("tb_server_info.id")) standby_server_id = Column(Integer, ForeignKey("tb_server_info.id")) rto_minutes = Column(Integer) # 목표 RTO (분) rpo_minutes = Column(Integer) # 목표 RPO (분) failover_steps = Column(JSON) # 페일오버 실행 단계 목록 healthcheck_url = Column(String(255)) last_test_at = Column(DateTime) last_test_result = Column(String(20)) # PASS | FAIL | PARTIAL is_active = Column(Boolean, default=True) created_at = Column(DateTime, default=func.now()) class DRTest(Base): __tablename__ = "tb_dr_test" id = Column(Integer, primary_key=True) scenario_id = Column(Integer, ForeignKey("tb_dr_scenario.id")) test_type = Column(String(20)) # BACKUP_VERIFY | FAILOVER_SIM | RECOVERY status = Column(String(20)) # RUNNING | PASS | FAIL | PARTIAL rto_actual = Column(Integer) # 실제 RTO (분) rpo_actual = Column(Integer) # 실제 RPO (분) result_detail = Column(JSON) # 단계별 결과 started_at = Column(DateTime, default=func.now()) completed_at = Column(DateTime) triggered_by = Column(String(100)) ``` ## core/dr_engine.py 구현 패턴 ```python import asyncio, hashlib, time from datetime import datetime from typing import Optional import paramiko from sqlalchemy.ext.asyncio import AsyncSession from core.ssh_exec import _get_server_credentials # 기존 AES 복호화 함수 재사용 from models import DRScenario, DRTest, Server class DREngine: async def verify_backup(self, db: AsyncSession, server_name: str) -> dict: """백업 파일 무결성 검증 (SHA-256 체크).""" # 1. 서버 정보 조회 (ip_addr, ssh_user, os_pw_enc) # 2. AES 복호화로 자격증명 획득 # 3. paramiko SSH 접속 # 4. backup_path 하위 최신 파일 SHA-256 계산 # 5. 결과 반환 (파일명, 크기, 해시, 경로 미노출) ... async def run_recovery_test(self, db: AsyncSession, scenario_id: int, triggered_by: str) -> DRTest: """복구 테스트 실행.""" # 1. 시나리오 조회 # 2. DRTest 레코드 생성 (status=RUNNING) # 3. failover_steps 순서대로 SSH 명령 실행 # 4. 각 단계 결과 result_detail에 누적 # 5. healthcheck_url 응답 확인 # 6. RTO 계산 (started_at ~ 헬스체크 성공) # 7. DRTest status 업데이트 (PASS/FAIL/PARTIAL) ... def calculate_rto_rpo(self, tests: list[DRTest]) -> dict: """최근 5회 테스트 기반 RTO/RPO 통계.""" ... ``` ## routers/dr.py 엔드포인트 구조 ``` GET /api/dr/scenarios 목록 (ENGINEER 이상) POST /api/dr/scenarios 등록 (ADMIN 전용) GET /api/dr/scenarios/{id} 상세 PUT /api/dr/scenarios/{id} 수정 (ADMIN 전용) POST /api/dr/test 복구 테스트 실행 (ENGINEER 이상) GET /api/dr/test/{id} 테스트 결과 조회 GET /api/dr/tests 테스트 이력 목록 POST /api/dr/backup-verify 백업 무결성 검증 (ENGINEER 이상) POST /api/dr/failover/{scenario_id} Failover 실행 (ADMIN 전용, 승인 필요) GET /api/dr/rto-rpo RTO/RPO 현황 대시보드 GET /api/dr/dashboard DR 전체 현황 ``` ## 보안 규칙 - Failover 실행은 `require_admin_role` 의존성 필수 - 백업 검증은 ENGINEER 이상 허용 - 서버 IP/경로를 API 응답 body에 포함하지 않는다 - SSH 자격증명은 `core/ssh_exec.py`의 기존 AES 복호화 함수 재사용 ## 헬스체크 URL 검증 방법 ```python import httpx async with httpx.AsyncClient(verify=False, timeout=10) as client: resp = await client.get(scenario.healthcheck_url) return resp.status_code == 200 ```