RCA 2026.11.05 6 min read

합성 경고가 RCA 보고서와 resolved까지 이어지는지 확인하기

실제 노드 장애를 만드는 대신 최대 5분 안에 자동 만료되는 합성 신호로 경로를 시험했다. Agent도 Worker 한 대에만 safe 모드 canary로 배치했다. 관찰 경로를 확인하는 동안 호스트 수정 권한이나 자동 복구 기능을 추가하지 않는 범위였다.

RCA Platform을 Kubernetes에 배포한 뒤에는 Pod가 Ready인 것만으로 연동이 끝났다고 보기 어려웠다. Prometheus의 신호가 Alertmanager를 거쳐 증거 수집과 보고서 생성으로 이어지고, 신호를 해제했을 때 incident도 해제되는지 확인하려 했다.

실제 노드 장애를 만드는 대신 최대 5분 안에 자동 만료되는 합성 신호로 경로를 시험했다. Agent도 Worker 한 대에만 safe 모드 canary로 배치했다. 관찰 경로를 확인하는 동안 호스트 수정 권한이나 자동 복구 기능을 추가하지 않는 범위였다.

서비스 기동과 관찰 Agent의 범위를 먼저 맞췄다

Platform은 Spring Boot·React 통합 이미지를 실행하는 Pod 두 개와 전용 PostgreSQL로 구성했다. namespace에는 Restricted Pod Security를 적용했고 Platform은 읽기 전용 루트 파일시스템으로 실행했다. DB의 관리자와 앱 계정도 분리했다.

HTTPS 점검에서는 명시적인 사설 CA와 SNI를 사용해 실제 Traefik VIP 경로로 접근했다. health와 ready, React 파일, 로그인 후 사용자 API와 인증 metrics는 200이었고 인증 없는 사용자 API와 metrics는 401이었다. 브라우저를 열기 전에 TLS와 앱 인증 경로를 따로 확인한 것이다. PC의 DNS나 전역 신뢰 저장소를 변경한 상태까지 포함하지 않았다.

RCA Platform HTTPS 인증서 검증과 readiness 200
RCA Platform HTTPS 인증서 검증과 readiness 200.

Agent를 띄우는 과정에서는 non-root import 오류도 만났다. 빌드할 때 사용한 제한적인 umask가 소스 COPY 권한에 반영돼 UID 65532가 Python 소스를 읽지 못한 문제였다. 공개 소스의 읽기와 디렉터리 탐색 권한을 수정하고 non-root import 검사를 추가한 이미지로 해결했다. 인증 파일이나 비밀 작업 디렉터리까지 권한을 넓히지는 않았다.

최종 canary는 UID 65532, RuntimeDefault seccomp, capability ALL drop으로 실행했다. privilege escalation을 허용하지 않았고 hostNetwork, hostPID, hostPath도 사용하지 않았다. eBPF와 dmesg 수집은 비활성화했다.

403도 설정한 권한의 결과였다

Agent의 Kubernetes 권한은 지정한 자기 Node에 대한 GET 하나로 좁혔다. 노드 목록, 다른 노드, 전체 Pod 목록, Secret 조회와 Kubernetes 쓰기 권한을 주지 않았다. 실제 검사에서는 자기 Node GET이 200이었고 제한한 일곱 종류의 조회가 403으로 거부됐다.

Worker02 Agent canary와 자기 Node GET 전용 RBAC
Worker02 Agent canary와 자기 Node GET 전용 RBAC.

수집 결과가 partial이라고 해서 곧바로 Agent 고장으로 판단하지 않았다. 컨테이너에서 볼 수 있는 정보와 허용한 Node 조회만 사용하도록 만든 범위에서 어떤 항목이 비었는지 확인했다. readyz와 livez의 200 응답도 광범위한 관리 권한이 있다는 뜻으로 해석하지 않았다. 해당 응답은 기존 공개 정보 권한의 범위였다.

Alertmanager가 받을 경고를 좁혔다

Platform의 ServiceMonitor에는 인증 Secret 참조와 monitoring에서 접근하는 NetworkPolicy를 연결했다. 최종 두 Platform Pod의 metrics가 모두 UP인 상태를 확인했다. Alertmanager webhook은 내부 Service 주소를 사용했고 다음 matcher 범위로 전달 대상을 한정했다.

namespace=rca-system
rca_receiver=platform
alertname=~RcaInfrastructure.*

기존 Watchdog와 null route를 유지하면서 RCA 관련 신호만 플랫폼으로 보냈다. 전역 경고 흐름 전체를 새 webhook으로 바꾸지는 않았다. Worker 전용 NotReady와 압력 규칙 두 개도 정상 평가 상태인지 확인했지만, 이 검증을 위해 실제 Worker를 NotReady로 만들거나 호스트 설정을 바꾸지는 않았다.

Prometheus에서 UP으로 표시된 Platform 수집 대상 두 개
RCA Platform 수집 대상 2개 모두 UP.

이후 일시적인 Prometheus 합성 신호를 만들었다. 목표는 경고의 내용 자체보다 전달 경로와 상태 변화를 확인하는 것이었다. 최대 5분 자동 만료 조건을 두고 Prometheus가 firing 상태로 평가하는 동안 health가 정상인지도 함께 봤다.

증거 요청과 보고서를 같은 흐름으로 추적했다

배포 연동 점검에서 확인한 경로는 다음과 같았다.

Prometheus firing
  → Alertmanager
  → evidence request 완료
  → evidence 생성
  → analysis 완료, 시도 1회
  → report completed
  → incident 생성

최종 보고서가 생겼다는 사실만 보고 Alertmanager 연동을 판단하지 않았다. 증거 요청의 reason과 context를 함께 확인해 어느 경로에서 들어온 요청인지 이어 봤다. 보고서의 trigger.source는 코드 계약에 따라 evidence였고, Alertmanager 유입은 요청 metadata와 뒤이은 upstream 해제 결과로 확인했다.

LLM은 비활성화한 상태였다. 이 결과는 경고에서 증거 수집과 보고서 생성까지 연결된 처리 경로를 검증한 것이었다. 외부 LLM이 실제 장애 원인을 추론하거나 인프라를 자동 복구했다고 설명할 근거는 아니었다.

합성 신호를 해제한 뒤에는 Prometheus가 inactive로 돌아가고 해당 incident가 resolved, resolution_source=alertmanager로 바뀌는 결과를 확인했다. 단순히 화면에서 incident를 닫은 결과와 구분되는 지점이었다. 시험이 끝난 임시 규칙은 정리하고 영구 Worker 규칙은 유지했다.

재등록 성공에도 유효 조건이 있었다

Agent Pod를 교체한 뒤에는 새 Pod가 생성된 시각보다 늦은 healthy heartbeat와 재등록을 확인했다. 다만 이때 bootstrap token은 아직 유효했다. identity를 emptyDir에 보관했기 때문에 Pod가 교체되면 해당 identity가 사라지는 조건도 남아 있었다.

따라서 bootstrap 만료 후 교체, 장기 인증 회전, mTLS, 다른 노드로의 확대는 후속 과제로 구분했다. 이번에는 한 Worker의 제한된 관찰 Agent와 합성 신호로 경고 처리의 닫힌 흐름을 확인했다. 운영 중인 노드 전체를 진단하거나 자동으로 수정하는 시스템까지 완성한 것은 아니었다.

firing → evidence → analysis → report → resolved의 연결은 보존한 검증 JSON에서 확인했다. 보고서 본문이나 수집 원문, 인증 토큰은 게시하지 않았다.

SEARCH JOURNAL

기록 검색

글 제목, 요약, 태그에서 찾습니다.

검색을 준비하고 있어요.

검색어를 입력해 주세요.

    Ctrl/⌘ K 검색 Esc 닫기 ↑ ↓ 결과 이동