InfraGuard

문제 해결 (인시던트 케이스)

장애·오류를 케이스로 등록해 등록→분석→원인판명→해결→종료 순으로 추적하고, 직접·배경 원인과 조치·재발 방지를 기록하는 인시던트 워크플로우입니다.

개요 — 무엇을 하나

문제 해결(Problem Resolution) 은 Service 역할 영역의 인시던트 케이스 워크플로우입니다. 장애·오류·성능 저하를 하나의 케이스로 등록하고, 상태 파이프라인을 따라 등록 → 분석 → 원인판명 → 해결 → 종료 순으로 관리하며, 각 단계에서 조사 내용·원인·조치를 구조화해 남깁니다. 사후 회고(post-mortem)와 재발 방지 대장 역할을 합니다.

핵심 데이터는 infr_incident 테이블 한 곳에 저장되며, 이 화면은 그중 area='service' 행만 다룹니다. 같은 테이블을 보안 문제 해결 화면이 area='security' 로 공유합니다 — 스키마는 동일하지만 보드가 분리됩니다.

케이스에는 500 리포트(사후 보고서)에 준하는 4개 원인·조치 필드가 있습니다.

필드 의미 예시
direct_cause (직접적 원인) 장애를 즉시 유발한 표면 원인 mysql 연결 한도 초과 (Too many connections)
root_cause (배경적 원인) 그 표면 원인을 만든 근본 배경 5분 주기 스크립트가 연결을 반환하지 않아 누수 → 한도 고갈
resolution (해결) 장애 중 서비스를 복구한 조치 연결 정리·프로세스 재시작·한도 상향으로 즉시 복구
action_taken (재발 방지) 향후 재발을 막는 항구 대책 전용 계정 MAX_USER_CONNECTIONS 격리, 스크립트 수정, 경보 추가

이 화면은 옛 "SLA & Uptime · 가동률" 메뉴에서 개명된 것입니다(네임스페이스 sla → resolution). 업타임 목표 KPI(uptime_target)는 유지됩니다.


언제 쓰나


화면 구성

KPI 스트립

상단 KPI는 area='service' 케이스 전체에서 계산됩니다.

KPI 계산 방식
전체 (total) 서비스 케이스 총 개수
미해결 (active) open + investigating + cause_identified 상태 합
해결 (resolved) resolved + closed 상태 합
평균 해결 MTTR (mttr_min) occurred_at 과 resolved_at 이 둘 다 있는 케이스만 (해결시각 − 발생시각)의 분 단위 평균
누적 다운타임 (downtime_min) 각 케이스 downtime_min 필드의 단순 합(분)
업타임 목표 (uptime_target) 운영 정책 sla_uptime_target(기본 99.9%)

MTTR을 정확히 얻으려면 케이스에 발생 일시(occurred_at) 를 입력하고, 해결 단계로 전이할 때 resolved_at 이 자동 기록되게 하세요. 발생 일시가 비어 있으면 그 케이스는 MTTR 평균에서 제외됩니다.

케이스 보드

케이스는 카드/행으로 나열되며 정렬은 서버(PHP)에서 다음 우선순위로 계산됩니다(컨트롤러 raw SQL 회피):

  1. 상태 순위: open → investigating → cause_identified → resolved → closed
  2. 심각도 순위: crit → warn → info
  3. 최신 id 우선

즉 미해결·심각·최근 케이스가 위로 올라옵니다.

각 카드에는 소스 배지(수동/Pulse/경보/관련기관/격리/탐지 승격), 심각도, 상태, 프로젝트명, 발생 시각, 다운타임, 이벤트 수가 표시됩니다.

상세 모달

카드를 열면 좌측 리포트(요약·직접원인·배경원인·해결·재발방지), 우측 타임라인(상태 변경·노트)이 나오고, 상단에 상태 파이프라인 전이 버튼이 있습니다.


사용법

1) 케이스 등록

[케이스 등록] 버튼 → 모달 폼. 등록 시 source='manual', area='service', status='open' 으로 생성되고 detected_at(감지 시각)이 서버 now() 로 기록되며 타임라인에 created 이벤트가 남습니다.

입력 필드와 검증 한도:

필드 필수 한도
title (제목) ✔ 최대 200자
severity (심각도) info/warn/crit — 비우면 기본 warn(운영 정책 incident_default_severity)
project_id (프로젝트) infr_project 존재 검증
service_id (서비스) infr_service 존재 검증
url 최대 255자
occurred_at (발생 일시) 날짜
summary (요약) 최대 4000자
direct_cause / root_cause / resolution / action_taken 각 최대 4000자
downtime_min (다운타임 분) 정수, 0 이상
impact (영향) 최대 191자

2) 상태 전이 (파이프라인)

상세 모달에서 다음 단계 버튼을 누르면 스텝 전이 대화상자가 뜹니다. 각 단계가 "이 단계에서 할 일"을 안내하고, 그 단계에 해당하는 리포트 필드를 함께 입력·저장할 수 있습니다. 보낸 필드만 갱신되므로, 전이 없이 원인만 채워 넣거나 리포트만 보강하는 것이 가능합니다.

단계 코드 이 단계에서 할 일
등록 open 접수 상태로 되돌림(분석 이전)
분석 investigating 증상 확인·영향 범위 파악·요약 정리
원인판명 cause_identified 직접 원인과 배경(근본) 원인 규명·기록
해결 resolved 적용한 복구 방법·재발 방지 대책 기록
종료 closed 조치·재발 방지 반영 확인 후 케이스 종료

전이 규칙:

날짜 표시는 활성 UI 로케일(window.__locale)을 사용하는 toLocaleString 으로 렌더됩니다 — EN 모드에서도 월/시간이 올바른 로케일로 표시됩니다.

3) 협업 노트

상세 모달에서 노트를 추가하면 타임라인에 kind='note' 이벤트가 append 됩니다(별도 노트 테이블 없이 infr_incident_event 로 통합). 본문은 최대 4000자.

4) 삭제

케이스를 삭제하면 해당 인시던트와 연결된 모든 infr_incident_event 행이 함께 삭제됩니다. 삭제는 되돌릴 수 없습니다.

5) 자동 유입 (Pulse / 예외 리포터)

외부 Laravel 앱이 인시던트 인제스트 API(POST /api/v1/incidents)로 자신의 예외를 밀어 넣으면, IncidentIngestService 가 다음처럼 처리해 이 보드에 자동 등재합니다.

키 발급·연동 코드는 Administration ▸ API 발급에서 관리합니다.


데이터 소스

테이블 역할
infr_incident 케이스 본체. 이 화면은 area='service' 만 조회. 마이그레이션 000719(+ 000723 인제스트 필드 · 000731 area · 000732 source 확장)
infr_incident_event 타임라인(append-only). kind = created/note/action/status_change/cause/resolution/alert_added. created_at 만 사용(마이그레이션 000720)
infr_project 프로젝트 드롭다운·카드 프로젝트명
infr_service 서비스 드롭다운
infr_audit_log 생성·수정·전이·삭제 감사 기록

주요 infr_incident 컬럼: source, area, title, severity, status, project_id, service_id, url, occurred_at, detected_at, resolved_at, closed_at, summary, direct_cause, root_cause, resolution, action_taken, downtime_min, impact, fingerprint, occurrence_count, last_occurred_at, api_key_id, created_by, updated_by.

기본값은 운영 정책 OpsPolicy::slaDefaults() 에서 읽습니다.

sla_uptime_target        = 99.9   # SLA 목표 가동률(%)
incident_default_severity = warn  # 케이스 등록 기본 심각도(info|warn|crit)

라우트:

GET  /service/resolution              index (보드)
GET  /service/resolution/live         live  (JSON 폴링 갱신)
POST /service/resolution/save         케이스 등록/수정
POST /service/resolution/transition   상태 전이(+ 단계별 리포트/closed_at)
POST /service/resolution/note         협업 노트 추가
POST /service/resolution/delete       케이스 삭제

권한

동작 필요 레벨
보드 조회 (index/live) 인증 사용자(infr.auth, 레벨 ≥ 1)
등록·수정·전이·노트·삭제 (save/transition/note/delete) 레벨 ≥ 2

모든 쓰기(등록·수정·전이·삭제)는 infr_audit_log 에 incident.create / incident.update / incident.transition / incident.delete 로 기록되며 행위자·전/후 상태가 함께 남습니다. 자세한 레벨 체계는 RBAC 레벨을 참고하세요.


문제해결 · FAQ

Q. MTTR이 계산되지 않아요(빈 값). 케이스에 발생 일시(occurred_at) 와 해결 시각(resolved_at) 이 둘 다 있어야 MTTR 평균에 포함됩니다. 발생 일시를 입력했는지, 해결 단계로 전이해 resolved_at 이 기록되었는지 확인하세요.

Q. 누적 다운타임이 0으로 보여요. 다운타임 KPI는 각 케이스의 downtime_min 필드를 단순 합산한 값입니다. 케이스에 다운타임(분)을 직접 입력해야 반영됩니다(발생~해결 시각으로 자동 계산하지 않습니다).

Q. Pulse로 들어온 케이스가 안 보여요. 인제스트 케이스는 source='pulse' 이고 area 를 지정하지 않아 기본값 service 로 이 보드에 등재됩니다. 그래도 안 보이면 (1) API 키가 유효한지, (2) 같은 지문이 이미 열린 케이스로 디둡되어 occurrence_count 만 올라갔는지 확인하세요. 보안 인시던트는 이 보드가 아니라 보안 문제 해결(area='security')에 나타납니다.

Q. 종료 시각을 과거로 지정할 수 있나요? 네. closed 전이 대화상자의 종료 일시(flatpickr) 필드에 원하는 시각을 입력하면 그 값이 저장됩니다. 비우면 서버 현재 시각(now())이 쓰입니다.

Q. 상태를 바꾸지 않고 원인만 보강하고 싶어요. 전이 대화상자는 보낸 필드만 갱신합니다. 같은 상태로 전이하면서 리포트 필드만 채우면 상태 변경 이벤트 없이 리포트만 업데이트됩니다(또는 케이스 편집으로 직접 수정).

Q. 삭제한 케이스를 복구할 수 있나요? 아니요. 삭제는 케이스와 모든 타임라인 이벤트를 함께 제거하며 되돌릴 수 없습니다. 삭제 행위 자체는 감사 로그에 남습니다.


관련 항목

문제 해결 — 인시던트 보드
문제 해결 — 인시던트 보드