Kubernetes Cluster Infra RCA Platform 개발 기록 1편

kubernetes-node-linux-rca-platform-01

Kubernetes Cluster Infra RCA Platform 개발 기록 1편
Photo by Growtika / Unsplash

Kubernetes를 공부하고 직접 클러스터를 구성하다 보면, 장애가 났을 때 처음 보이는 증상은 대부분 비슷합니다.

NodeNotReady, DiskPressure, MemoryPressure, CrashLoopBackOff, OOMKilled, HTTP 5xx 같은 것들이 먼저 눈에 들어옵니다.

겉으로 보면 Pod 문제 같고, Service 문제 같고, Ingress 문제처럼 보일 때도 있습니다.

그런데 실제로 파고들어 보면 원인은 더 아래에 있는 경우가 많았습니다.

Pod 장애처럼 보임
  -> kubelet 문제일 수 있음
  -> containerd socket 지연일 수 있음
  -> disk I/O, inode, conntrack, kernel log 문제일 수 있음

그래서 이 프로젝트는 처음부터 “Kubernetes 애플리케이션 장애 분석기”로 만들고 싶지는 않았습니다.

내가 만들고 싶었던 것은 Kubernetes 클러스터에서 발생한 장애를 노드와 Linux 시스템 레벨 증거 중심으로 분석하는 RCA 플랫폼이었습니다.

RCA는 Root Cause Analysis, 즉 근본 원인 분석입니다.

단순히 “Pod가 죽었다”에서 끝내는 것이 아니라, 왜 그 Pod가 영향을 받았는지, 그 아래 노드나 런타임 계층에서 무슨 일이 있었는지를 따라가 보는 것이 목표였습니다.


처음 잡은 방향

처음에는 프로젝트를 너무 크게 잡지 않으려고 했습니다.

완성된 제품을 한 번에 만들기보다는, 장애 알림이 들어왔을 때 보고서까지 이어지는 최소 흐름을 먼저 만들고 싶었습니다.

처음 잡은 흐름은 이랬습니다.

alert or manual request
  -> evidence request
  -> node agent collection
  -> evidence preprocessing
  -> rule-based RCA
  -> optional LLM diagnosis
  -> policy classification
  -> RCA report

여기서 중요하게 생각한 건 “AI가 전부 알아서 판단한다”가 아니었습니다.

오히려 원칙은 반대에 가까웠습니다.

Evidence first.
Rule first.
LLM은 보조.
조치는 Policy Engine을 통과해야 함.

즉, LLM은 진단 설명을 더 잘 정리해주는 역할을 할 수는 있지만, 클러스터를 직접 수정하거나 위험한 조치를 스스로 실행해서는 안 된다고 봤습니다.

이 프로젝트의 중심은 LLM이 아니라 증거, 규칙, 정책이어야 한다고 생각했습니다.


첫 번째로 한 일: 범위 정하기

가장 먼저 한 일은 “무엇을 볼 것인가”를 정하는 것이었습니다.

이 프로젝트에서 중심으로 보는 신호는 애플리케이션 로그가 아니라 노드와 Linux 계층의 증거입니다.

예를 들면 이런 것들입니다.

  • NodeNotReady
  • DiskPressure
  • MemoryPressure
  • PIDPressure
  • NetworkUnavailable
  • kubelet/runtime 장애
  • CNI/DNS 문제
  • disk I/O 병목
  • inode 고갈
  • conntrack 고갈
  • kernel error
  • systemd unit 장애
  • NIC link flap

반대로 CrashLoopBackOff, ImagePullBackOff, OOMKilled, HTTP 5xx 같은 증상은 바로 원인으로 보지 않기로 했습니다.

물론 중요하지 않다는 뜻은 아닙니다.

다만 이런 증상은 노드나 네트워크 장애가 위쪽 레이어에서 드러난 결과일 수도 있습니다. 그래서 “원인”이라기보다는 “보조 근거”로 보는 방향을 잡았습니다.

이 관점을 정한 것이 프로젝트의 출발점이었습니다.


Backend MVP 만들기

범위를 정한 다음에는 FastAPI 기반의 Backend MVP를 만들었습니다.

처음 Backend가 맡은 역할은 단순했습니다.

  • cluster 등록
  • agent 설치 명령 조회
  • Alertmanager webhook 수신
  • fake evidence 생성
  • rule-based RCA report 생성
  • policy classification

처음부터 실제 클러스터에 완벽하게 붙이기보다는, 장애 알림이 들어왔을 때 RCA report가 생성되는 구조를 먼저 검증하려고 했습니다.

이 단계에서 가장 중요했던 건 API 개수를 늘리는 것이 아니었습니다.

“알림을 받는다”와 “보고서를 만든다”가 따로 놀지 않고, 하나의 흐름으로 이어지게 하는 것이 더 중요했습니다.

Alertmanager webhook
  -> evidence 생성 또는 요청
  -> RCA 분석
  -> report 생성

아직은 fake evidence를 쓰더라도, 전체 파이프라인이 어떤 모양으로 흘러가야 하는지를 먼저 확인하고 싶었습니다.


저장소 구조 바꾸기

처음에는 빠르게 확인하기 위해 in-memory 저장소로 시작했습니다.

하지만 RCA report는 운영 기록입니다.

장애가 끝난 뒤에도 남아야 하고, 서버가 재시작되어도 사라지면 안 됩니다.

그래서 저장소 구조를 DB 기반으로 바꾸는 작업을 했습니다.

InMemoryStore
  -> SqlAlchemyStore
  -> PostgreSQL / MariaDB / SQLite 지원
  -> Alembic migration으로 schema 관리

여기서의 문제는 단순한 코드 에러라기보다는 운영 구조상의 한계였습니다.

문제: 프로세스가 재시작되면 cluster, evidence request, RCA report가 사라질 수 있음
수정: DB persistence 추가, Alembic migration으로 schema 관리, PostgreSQL/MariaDB/SQLite 개발 환경 지원

개발 초기에는 in-memory가 편하지만, 이 프로젝트는 장애 기록을 다루는 플랫폼입니다.

그래서 persistence는 초반에 반드시 들어가야 한다고 판단했습니다.


Agent와 evidence request 구조 잡기

다음으로는 Node Agent와 Backend 사이의 흐름을 잡았습니다.

Agent가 아무 때나 아무 데이터를 보내는 구조는 피하고 싶었습니다.

Backend가 어떤 노드에서 어떤 증거를 수집할지 요청하고, Agent는 그 요청을 받아 evidence를 제출하는 방식이 더 안전하다고 봤습니다.

흐름은 이렇게 잡았습니다.

Agent register
  -> node token 발급
  -> Backend evidence request 생성
  -> Agent poll
  -> Agent evidence submit
  -> RCA report 생성

이 구조를 둔 이유는 세 가지였습니다.

첫째, Agent가 임의로 데이터를 밀어 넣는 구조를 피할 수 있습니다.

둘째, Backend가 어느 노드에서 어떤 collector를 실행할지 통제할 수 있습니다.

셋째, 나중에 수동 수집, Alert 기반 수집, RCA 후속 수집을 같은 구조로 처리할 수 있습니다.

이 부분은 이후에 프로젝트를 확장할 때 꽤 중요한 기반이 될 것 같았습니다.


Node Agent collector 설계

Node Agent는 처음부터 read-only collector로 잡았습니다.

즉, 노드에 들어가서 무언가를 수정하는 도구가 아니라, 장애 분석에 필요한 정보를 읽어오는 도구입니다.

수집 대상으로 생각한 것은 다음과 같습니다.

  • /proc
  • /etc
  • /var/log
  • /run
  • systemd
  • kernel log
  • containerd/runtime
  • kubelet
  • CNI
  • DNS
  • disk/inode
  • network/conntrack

여기서 가장 중요하게 본 것은 collector의 실패 처리였습니다.

운영 환경은 늘 다릅니다. 어떤 노드는 journalctl 접근이 안 될 수 있고, 어떤 노드는 containerd socket 위치가 다를 수 있습니다. CNI 설정 파일 경로도 클러스터마다 다를 수 있습니다.

그래서 특정 collector가 실패했다고 Agent 전체가 죽으면 안 된다고 봤습니다.

피하고 싶었던 구조는 이런 것이었습니다.

raise RuntimeError("collector failed")

대신 목표로 한 구조는 이런 쪽이었습니다.

return {
    "collector": "runtime",
    "status": "partial",
    "error": "permission denied or file not found",
    "collected_fields": {...},
}

즉, 실패도 evidence의 일부로 남기는 방식입니다.

이 생각은 나중에 실제 collector permission 문제를 다룰 때도 이어졌습니다.

“접근 실패”와 “실제 장애”를 구분해야 잘못된 RCA를 줄일 수 있기 때문입니다.


rule-based RCA를 먼저 둔 이유

LLM을 붙이기 전에도 기본 분석은 가능해야 한다고 생각했습니다.

그래서 rule-based RCA Analyzer를 먼저 만들었습니다.

예를 들어 DiskPressure라면 이런 신호를 봅니다.

DiskPressure
  -> inode_usage_percent 높음
  -> disk usage 높음
  -> kubelet eviction 관련 log
  -> root cause candidate 생성

NodeNotReady라면 이런 쪽을 봅니다.

NodeNotReady
  -> kubelet 재시작 여부
  -> containerd socket 지연
  -> API Server 통신 실패
  -> network / conntrack 신호

LLM은 분명 도움이 될 수 있지만, LLM만 믿고 RCA를 구성하면 근거가 약해질 수 있습니다.

그래서 먼저 rule로 잡을 수 있는 신호를 정리하고, LLM은 그 결과를 보강하는 쪽으로 두었습니다.


AI와 Codex를 어떻게 사용했는가

이 프로젝트에서 AI를 사용한 방식은 두 가지로 나눌 수 있습니다.

첫 번째는 개발 과정에서의 보조 도구입니다.

처음 방향을 잡고, 어떤 증거를 봐야 하는지 정리하고, Backend와 Agent 사이의 큰 구조를 잡는 부분은 직접 고민하면서 진행했습니다.

사람이 직접 설계하고 검증할 수 있는 범위까지 최대한 밀어붙인 뒤, 반복 구현이 많거나 테스트 케이스를 넓혀야 하는 부분, 문서 정리와 보일러플레이트가 많은 부분은 Codex를 활용했습니다.

예를 들면 이런 부분입니다.

  • 반복적인 API route 구현
  • Pydantic model 정리
  • 테스트 케이스 확장
  • 문서 초안 정리
  • Helm/manifest 구조 보강
  • 에러 처리 패턴 보강

두 번째는 제품 기능으로서의 LLM Analyzer입니다.

제품 내부에서 LLM은 raw log를 그대로 받지 않습니다.

먼저 evidence를 전처리해서 compact JSON으로 만든 뒤, 그 결과를 선택적으로 LLM에 전달하는 구조로 잡았습니다.

raw evidence
  -> key metrics
  -> derived signals
  -> log summary
  -> config findings
  -> compact JSON
  -> optional LLM Analyzer

LLM이 하는 일은 다음 정도로 제한했습니다.

  • root cause candidate 보강
  • RCA report 설명 문장 보강
  • 추가 확인 포인트 제안
  • 운영자가 읽기 쉬운 요약 생성

반대로 LLM이 하지 못하게 한 일도 명확히 했습니다.

  • 클러스터 직접 수정
  • 무조건 자동 복구
  • 위험 조치 실행 판단

이 선을 초반에 정해두는 것이 중요했습니다.

AI를 쓰더라도 운영 시스템에서는 통제와 검증이 먼저라고 생각했기 때문입니다.


Policy Engine을 먼저 둔 이유

RCA 도구에서 가장 위험한 부분은 원인 분석보다 조치 제안이라고 생각했습니다.

예를 들어 분석 결과가 이런 조치를 제안할 수 있습니다.

  • kubelet 재시작
  • containerd 재시작
  • 노드 cordon/drain
  • CNI 설정 변경
  • Pod 삭제
  • 디스크 정리

이 중 어떤 것은 비교적 안전하지만, 어떤 것은 운영 중인 서비스에 직접 영향을 줄 수 있습니다.

그래서 모든 추천 조치는 Policy Engine을 통과하도록 했습니다.

  • AUTO_SAFE
  • APPROVAL_REQUIRED
  • GITOPS_PR_ONLY
  • NEVER_AUTO_EXECUTE
  • MANUAL_INVESTIGATION

이 구조의 핵심은 단순합니다.

LLM/Rule 추천 조치
  -> Policy Engine classify
  -> UI에는 policy level + reason 표시
  -> 위험 조치는 자동 실행 금지

이 프로젝트는 “AI가 알아서 고쳐주는 도구”라기보다는, 증거를 모으고, 원인을 좁히고, 조치 위험도를 분류해주는 운영 보조 도구에 가깝습니다.


가장 중요하게 고려했던 점

첫 단계에서 가장 중요하게 본 것은 기능의 개수보다 방향이었습니다.

정리하면 다음과 같습니다.

  1. 원인을 Pod에서 바로 찾지 말고 노드/Linux 계층에서 찾기
  2. LLM보다 evidence와 rule을 먼저 신뢰하기
  3. Agent는 read-only로 시작하기
  4. collector 실패를 전체 실패가 아니라 evidence로 남기기
  5. 자동 조치보다 guardrail과 승인 흐름을 먼저 만들기
  6. RCA report는 운영 기록이므로 DB에 남기기

개인적으로 가장 중요했던 문장은 이것이었습니다.

LLM은 진단 설명을 보강할 수 있지만, 클러스터를 직접 건드리면 안 된다.

이 원칙을 초기에 정해두지 않으면 프로젝트가 “멋있어 보이는 자동 복구 도구”로 흐를 수 있다고 생각했습니다.

하지만 실제 운영 환경에서는 자동 복구보다 근거, 통제, 승인, 기록이 더 중요합니다.


마무리

첫 번째 단계의 목표는 아이디어를 실제 코드 흐름으로 바꾸는 것이었습니다.

처음 만든 것은 단순한 API 몇 개가 아니라, 아래 흐름의 뼈대였습니다.

Alertmanager webhook
  -> evidence request
  -> Node Agent evidence
  -> RCA report
  -> policy decision

아직 완성형은 아니지만, 이 시점에서 프로젝트의 방향은 어느 정도 잡혔다고 생각합니다.

특히 중요한 것은 LLM을 중심에 두지 않았다는 점입니다.

이 프로젝트의 중심은 LLM이 아니라 evidence, rule, policy입니다. LLM과 Codex는 그 위에서 개발 속도를 높이고, 운영자가 이해하기 쉽게 설명을 보강하는 역할로 두었습니다.

다음 글에서는 인증, RBAC, webhook 인증, report drilldown, 그리고 실제 노드 증거 수집에서 마주친 permission 문제를 어떻게 다뤄봤는지 정리해보려고 합니다.