← 개발 로그 목록

website: 어드민 알람 상태(FIRING/OK) 조회 화면

/ 5분 분량 / 개발 로그

OCI Monitoring이 판정하는 9개 알람의 현재 상태를 어드민 페이지(`/admin/infra/alarms`)에서 바로 볼 수 있게 만든 PR이다.

시스템 지표 화면(#451)을 만들 때는 CPU/메모리/디스크처럼 호스트에서 직접 측정 가능한 값을 다뤘는데, 이번엔 알람의 FIRING/OK 판정 자체가 문제였다. 이 판정은 로컬에서 계산할 수 있는 값이 아니다. threshold, period, 지속시간을 따져서 최종적으로 "지금 울리는 중인지 아닌지"를 결정하는 건 OCI Monitoring 쪽이라서, 결국 그 API를 호출해서 물어보는 수밖에 없다.

그래서 남는 선택은 "어디서 그 호출을 하느냐"였다. 백엔드 컨테이너에서 직접 OCI SDK를 붙이는 방법도 있었지만, 그러면 새 의존성이 늘고 IAM 권한도 컨테이너 쪽에 새로 얹어야 한다. 이미 신뢰하고 쓰고 있는 호스트 크론(~/oci-monitor-venv, instance principal)이 있으니, 여기서 조회하고 백엔드는 파일만 읽게 하는 게 낫다고 판단했다. snapshot-system-metrics.py가 쓰던 것과 완전히 같은 패턴이다 — 크론이 5분마다 로컬 JSON Lines(infra/logs/alarm-status/snapshot.jsonl)에 한 줄씩 남기면, AlarmStatusController/AlarmStatusService는 그 파일의 마지막 줄만 읽어서 응답한다.

snapshot-alarm-status.py를 짜면서 기존 스크립트와 다르게 가져간 부분도 있고 그대로 가져간 부분도 있다. IMDS로 컴파트먼트 ID와 리전을 조회하는 부분, 파일이 커지면 원자적으로 교체하며 트림하는 부분은 push-disk-metric.py, snapshot-system-metrics.py와 동일하게 유지했다. 반면 snapshot-system-metrics.py가 쓰던 2분 오프셋(CPU 1초 샘플링이 동시 기동 시 리소스 경쟁에 취약해서 필요했던 것)은 여기엔 필요 없었다. list_alarms_status는 API 호출 한 번이라 로컬 리소스를 두고 경쟁할 일이 없기 때문이다.

백엔드 쪽 AlarmStatusService.latest()는 파일 끝에서부터 거슬러 올라가며 파싱되는 첫 줄을 찾도록 짰다. 크론이 파일을 쓰는 도중에 요청이 들어와서 마지막 줄이 깨져 있을 가능성을 염두에 둔 것이다. 그 경우 그냥 에러를 내는 대신 그 이전 줄로 폴백하게 했고, 이건 AlarmStatusServiceTest의 latest_LastLineMalformed_FallsBackToPreviousLine 테스트로 확인해뒀다.

프론트 쪽 AlarmStatusPanel을 만들 때 걸린 부분이 하나 있었다. 마지막 확인 시각이 얼마나 오래됐는지 판단하려고 Date.now()를 썼는데, 처음엔 이걸 렌더 함수 안에서 직접 불렀다. CI에서 react-hooks/purity 규칙 위반으로 걸렸다 — 렌더링 중에 Date.now()를 부르면 리렌더될 때마다 값이 달라져서 컴포넌트가 순수하지 않게 된다. fetch 응답이 온 시점의 콜백에서 한 번만 계산해서 stale state로 저장하는 식으로 고쳤다. 커밋 로그에도 이 수정이 별도로 남아있다(fix(frontend): AlarmStatusPanel의 Date.now() 렌더 중 직접 호출 제거).

15분(크론 주기 5분의 3배)을 stale 기준으로 잡은 것도 나름의 판단이었다. 크론이 정상이어도 최대 5분 정도 시차는 생기니까, 그보다 훨씬 못 들어오면 "값이 오래됐다"가 아니라 "크론 자체가 멈췄을 수 있다"는 신호로 보는 게 맞다고 봤다. 이 화면이 답해야 하는 질문이 "지금 알람이 진짜 돌고 있는가"이기 때문에 이 구분이 필요했다.

PR은 올린 지 10분 만에 머지됐다. 다만 이게 끝이 아니라, PR 본문에 인프라 오너가 서버 반영 전에 확인해야 할 체크리스트를 남겨뒀다. 지금 IAM 정책(likelion-monitoring-policy)엔 use metrics 권한만 있고 알람 읽기 권한은 없어서, 그대로 서버에 올리면 스크립트가 인증 오류로 죽는다. list_alarms_status가 기본 엔드포인트로 응답하는지도 실측이 안 된 상태고, 서버 크론 등록과 docker-compose.yml 마운트 반영도 남아있다. 코드와 테스트는 다 준비됐지만 실제로 알람 상태가 뜨는 건 이 후속 작업들이 끝나야 확인 가능한 상태다. 이런 미결 사항은 infra/CLAUDE.md와 infra/docs/observability.md에도 똑같이 남겨서, 나중에 누가 봐도 뭐가 남았는지 바로 알 수 있게 해뒀다.