week 7 — Coroot 실습편

bocopile·2026년 5월 24일

AIOps

목록 보기
8/10

본 글의 소스코드 (manifests · scripts · rca-bridge 앱) 는 bocopile/documentaiops/week7 에 있습니다. 본문의 모든 파일 링크는 이 디렉터리 안 경로를 가리킵니다.

aiops/week7/
├── README.md                # 본 글
├── scripts/                 # 부트스트랩 · 설치 · 실습 헬퍼
│   ├── bootstrap-week7.sh   #   step 1~3d 를 묶어 한 번에 실행
│   ├── bootstrap-multipass.sh, native-k3s.sh, export-kubeconfig.sh
│   ├── install-coroot.sh    #   Coroot CE + Project webhook patch (자연 흐름의 핵심)
│   ├── install-otel-demo.sh, install-chaos-mesh.sh, install-rca-bridge.sh
│   ├── run-rca-demo.sh      #   수동 webhook 1 발 → AI RCA 결과 cat
│   └── run-scenarios.sh     #   chaos-mesh 5 시나리오 inject/recovery 러너
├── manifests/               # K8s 매니페스트 (Coroot values, CR patch, rca-bridge, heartbeat)
│   ├── coroot-values.yaml, coroot-webhook.yaml
│   ├── otel-demo-values.yaml
│   └── rca-bridge-{deployment,service,secret}.yaml, rca-bridge-heartbeat-cronjob.yaml
├── rca-bridge/              # FastAPI 앱 (incident webhook → OpenAI Structured Outputs)
│   ├── Dockerfile, requirements.txt
│   ├── app/{main,webhook_handler,prompting,openai_client,redaction,storage}.py
│   ├── schemas/rca_response.schema.json
│   └── tests/
└── scenarios/               # chaos-mesh CRD 5 종 (NetworkChaos / StressChaos / PodChaos)
    └── 01-network-delay.yaml ~ 05-pod-kill.yaml

TL;DR

  • Multipass + native K3s 위에 Coroot CE, OTel Demo, chaos-mesh 를 한 사이클로 올립니다.
  • metrics / logs / traces / profiles 가 어디서 들어와 어디에 쌓이는지 먼저 보고, CE 에 비어 있는 AI RCA 자리는 rca-bridge 로 바깥에서 메웁니다.
  • 마지막은 chaos-mesh 5개 시나리오입니다. Coroot 가 좁힌 후보와 LLM 요약이 실제 주입 원인과 얼마나 맞는지는 맨 끝 비교표 한 장으로 정리합니다.

실습 환경

기준 머신은 macOS Apple Silicon (arm64) 입니다. 호스트에는 VM 을 띄울 도구 만 있으면 되고, K3s · helm · docker 는 모두 VM 안에서 자동 설치됩니다.

항목권장 / 검증 값
호스트 OSmacOS 26.x (Apple Silicon arm64)
호스트 RAM16GB+ (VM 에 8GB 할당)
호스트 디스크 여유30GB+ (VM image + chart pull)
호스트 사전 도구multipass (설치), kubectl, ssh
VMUbuntu 24.04 arm64, 4 vCPU / 8GB / 30GB (bootstrap-multipass.sh default)
VM 안 자동 설치K3s 1.35.x, Helm 3.x, docker.io (rca-bridge 빌드용)
LLM 백엔드OpenAI Responses API (gpt-4o-mini 기본). OPENAI_API_KEY 호스트 env 로 전달

Docker Desktop필요 없습니다. 호스트에서 docker 는 안 씁니다. RCA 단계를 생략하면 OPENAI_API_KEY 도 필요 없습니다 (단 bootstrap-week7.sh 가 fail-fast 하므로 step 3d 만 수동 skip 하거나 RCA 제외 변형으로 실행해야 합니다).

Quick Start

cd <repo-root>
# openAI Key
export OPENAI_API_KEY=sk-... 
bash ./aiops/week7/scripts/bootstrap-week7.sh

export KUBECONFIG=~/.kube/coroot-w7.yaml
kubectl get pod -A 
kubectl -n coroot port-forward svc/coroot-coroot 8080:8080

경로 기준: 본 README 는 aiops/week7/README.md 이고, 아래 명령과 링크는 모두 repo root 기준 상대 경로(./aiops/week7/...)를 씁니다.

1. 들어가며

6주차가 Coroot 의 내부 책임 분담을 코드로 확인하는 주였다면,
7주차는 그 분담이 실제 클러스터에서 어떤 장면으로 보이는지 확인하는 주입니다.
이번 글에서는 추상적인 설명보다 배선과 흐름을 먼저 봅니다.
어떤 신호가 커널에서 올라오고, 어떤 신호가 OTLP 로 들어오며, 어디까지가 CE 의 몫인지가 중심입니다.

또 하나의 축은 AI RCA 의 빈자리입니다.
Coroot Community Edition 은 관찰과 후보 압축까지는 잘하지만, 마지막 설명 문단은 비워 둡니다.
그래서 이번 실습은 Coroot 를 대체하는 이야기가 아니라, Coroot 가 이미 좁혀 둔 후보를 LLM 으로 읽기 좋게 바꾸는 얇은 다리 하나를 더하는 이야기입니다.

2. 환경 부트스트랩

이번 구성에서 중요한 선택은 native K3s 입니다. eBPF 를 제대로 보려면 진짜 리눅스 커널이 필요하고, 그래서 호스트 쪽 진입점은 bootstrap-week7.sh 하나로 잡았습니다. 호스트에서는 Multipass, ssh, OPENAI_API_KEY 정도만 맞추면 되고, 나머지 K3s 와 실습 컴포넌트 설치는 체인 안으로 밀어 넣었습니다.

스크립트실행 위치입력 / 전제책임
bootstrap-week7.shhostOPENAI_API_KEY, 선택적으로 FLANNEL_BACKEND=host-gw전체 진입점입니다. VM 준비, VM 안 설치, kubeconfig export 를 순서대로 묶습니다.
bootstrap-multipass.shhostMultipassUbuntu VM 생성 또는 재사용, aiops/week7 마운트
native-k3s.shVMLinux 커널native K3s, 네 개 namespace, Helm 준비
install-coroot.sh / install-otel-demo.sh / install-chaos-mesh.sh / install-rca-bridge.shVMkubectl, helmCoroot, OTel Demo, chaos-mesh, rca-bridge 설치
export-kubeconfig.shhostVM 안 k3s.yaml호스트용 KUBECONFIG 내보내기
cd <repo-root>
# openAI Key
export OPENAI_API_KEY=sk-... 
bash ./aiops/week7/scripts/bootstrap-week7.sh

export KUBECONFIG=~/.kube/coroot-w7.yaml
kubectl get pod -A 

실습 입장에서는 이 순서만 기억하면 충분합니다.
Multipass 가 VM 을 만들고, VM 안에서 K3s 가 올라오고, 그 위에 Coroot, OTel Demo, chaos-mesh, rca-bridge 가 차례로 올라옵니다.
네트워크 카오스와 플래널 충돌이 있으면 FLANNEL_BACKEND=host-gw 우회로도 스크립트에 이미 들어 있습니다.

3. 데모 앱

데모 앱은 otel-demo-values.yaml 로 조정한 OTel Demo 입니다. 이유는 단순합니다.
애플리케이션 쪽에서는 metrics, logs, traces 를 보내고, 노드 쪽에서는 profiles 를 붙일 수 있어서 이번 글의 4시그널을 한 번에 보기 좋습니다.
OTEL_COLLECTOR_NAMEcoroot-coroot.coroot.svc.cluster.local 로 맞춰 OTLP gRPC 4317 로 바로 꽂는 것도 이 파일에서 처리합니다.

실습 기준선은 데모 앱 대부분이 Running, 비핵심 sidecar 한둘이 흔들리는 상태 입니다.
flagd 가 그 자리에 있는데, 이 글의 핵심인 Coroot 수집 경로, service map, 그리고 다섯 개 카오스 대상 frontend / product-catalog / checkout / recommendation / cart 의 관찰에는 직접 의존하지 않습니다.
그래서 첫 실습 파트에서는 이 상태를 허용 가능한 출발점으로 잡고 앞으로 갑니다.

화살표 위의 진한 선이 4 시그널을 모으는 경로 이고, 박스 안의 5 개 굵은 이름이 §8 의 카오스 대상입니다. flagd기타 ~15 서비스 는 트래픽이 흐르는 동안 배경 역할만 합니다.

4. Coroot CE + 4 시그널

Coroot 설치는 coroot-values.yaml 을 바탕으로 operator + coroot-ce chart 경로를 씁니다.
여기서 중요한 건 “무엇이 어디로 들어오는가”입니다.
service map 의 뼈대는 eBPF 가 만들고, 애플리케이션 계측은 그 위에 L7 문맥을 덧칠합니다.
values 쪽에서는 node-agent 메모리 limit 를 1Gi 로 올려 두었고, 그 외 host mount 는 chart default (/sys/fs/cgroup, /sys/kernel/tracing, /sys/kernel/debug) 만으로 충분합니다 (BTF 포함 6.x 커널 가정).

시그널출발지수집 메커니즘저장
metricsOTel SDK + node-agent애플리케이션 계측 + 노드 관찰Prometheus
logsstdout + OTLP컨테이너 로그 수집 + OTLPClickHouse
tracesOTel SDKOTLP gRPC 4317ClickHouse
profilescoroot-node-agenteBPF continuous profilerClickHouse

이 표 하나만 잡아도 이후 화면이 훨씬 잘 읽힙니다.
metrics 는 시계열로, logstraces 는 사건 문맥으로,
profiles 는 애플리케이션 수정 없이 CPU 핫패스를 보는 용도로 들어옵니다.
특히 profiles 는 왜 이 실습이 native K3seBPF 를 같이 요구하는지 가장 분명하게 보여 주는 지점입니다.

5. UI 한 바퀴

UI 는 네 자리만 보면 됩니다. service map, SLO, 서비스별 dashboard, 그리고 CE 에서 비어 있는 AI RCA 자리입니다. 네 시그널(metrics / logs / traces / profiles)과 이 네 화면이 같은 UI 한 곳 에서 묶이는 점이 Coroot 의 강점입니다. 따로 Grafana/Jaeger/Pyroscope 를 띄울 필요 없이 incident → 화면 → 근거 가 같은 페이지 안에서 이어집니다. 이번 글에서는 여기서 스크린샷 설명을 길게 늘이지 않습니다. 첫 실습편에서는 “어떤 화면이 있다”보다 “그 화면을 떠받치는 데이터가 어떻게 들어왔는가”가 더 중요하기 때문입니다.

UI 를 띄우는 명령은 한 줄입니다. kubectl 의 port-forward 를 백그라운드 로 띄워서 터미널을 점유하지 않게 한 뒤 브라우저로 엽니다. 종료는 PID 또는 pkill 로 합니다.

# 백그라운드 port-forward (로그는 버림, PID 만 기록)
kubectl -n coroot port-forward svc/coroot-coroot 8080:8080 > /dev/null 2>&1 &

# 호스트 브라우저
open http://localhost:8080

# 끝낼 때
pkill -f "port-forward svc/coroot-coroot"   # 또는 kill <PID>

첫 진입 화면에서 의외로 길게 보게 되는 자리는 Applications 페이지입니다. Coroot 가 별도 등록 절차 없이 네임스페이스/서비스를 자동 감지해 시작부터 service map 이 살아 있는 모습으로 띄웁니다. 본 실습에서는 coroot / demo-otel / chaos-mesh / rca-bridge 네 자리가 모두 자동으로 잡힙니다.

다음으로 들어갈 자리는 AnomaliesInspections 입니다. AnomaliesCoroot 가 자동으로 좁혀 둔 probable causes 후보 list 이고, Inspections 는 같은 incident 를 SLO / Network / Latency / CPU·Memory 4 가지 Check 축으로 다시 본 근거 화면 입니다. 이 두 페이지가 §6 의 출발점입니다. rca-bridge 가 받는 incident 는 결국 Anomalies 가 만든 객체이고, evidence 보강에 쓰는 컨텍스트는 Inspections 가 보여주는 근거와 같은 출처입니다.

특히 SLO 자리는 §6 의 다리이기도 합니다. Coroot 가 SLO 위반을 감지하면 coroot-webhook.yaml 에 적힌 incident webhook 으로 알림이 나가고, 그 알림이 곧 rca-bridge 의 진입점이 됩니다. 즉 UI 의 SLO 패널은 그냥 상태 화면 이 아니라 알림 경로의 시작점 입니다.

Profiles 자리도 짧게 짚고 넘어가야 합니다. coroot-node-agent 의 eBPF continuous profiler 가 애플리케이션 수정 없이 CPU/메모리 hot path 를 flame graph 로 그려 줍니다. §8 의 시나리오 3 (StressChaos cpu, 대상 recommendation) 이 가장 잘 드러나는 자리도 여기입니다. 별도 sidecar 나 코드 patch 없이 커널 시야 로 핫스택을 보여주는 게 eBPF 가 이 실습에 필요한 핵심 이유입니다.

대신 하나는 분명히 짚고 넘어갑니다. CEAI RCA 를 안에 품고 있지 않습니다. CE 와 EE 의 경계를 비교하면 이 글의 후반부가 왜 그 자리 인지가 더 분명해집니다.

영역CEEE
metrics / logs / traces / profiles 수집
service map, SLO, dashboard
Anomalies / Inspections (probable causes 좁힘)
incident webhook 송출
AI RCA (incident 마지막 요약/내러티브)없음
장기 retention / RBAC / SSO기본확장

그래서 이 글의 후반부는 Coroot 를 바꾸는 작업이 아니라, 위 표의 없음 칸 옆에 설명기를 하나 붙이는 작업으로 읽는 편이 정확합니다. 아직 채우지 않은 항목은 흩어 놓지 않고 §9 비교표의 pending 열에만 모아 둡니다.

6. CE 밖 AI RCA 설계

설계 원칙은 한 줄이면 됩니다. LLM 은 탐지기가 아니라 설명기입니다. 원인을 좁히는 일은 Coroot 가 하고, rca-bridgeincident webhook 을 받아 PrometheusClickHouse 에서 문맥을 조금 더 모은 뒤, 그 결과를 Responses API + Structured Outputs 로 요약합니다.

이렇게 나누면 책임도 선명합니다. Coroot 가 후보를 좁히고, rca-bridge 가 근거를 묶고, LLM 은 운영자가 읽을 수 있는 문장으로 바꿉니다. Coroot REST API 를 1순위로 두지 않는 이유도 여기 있습니다. 이번 실습에서 필요한 건 거대한 조회 API 가 아니라, 이미 만들어진 incident 주변의 얇은 문맥 보강이기 때문입니다.

LLM 백엔드는 이 글에서 OpenAI Responses API 한 가지만 다루지만, 설계 자체는 공급자 중립 입니다. Anthropic Messages API 처럼 다른 LLM 으로 치환하려면 openai_client.py 의 호출부만 갈아 끼우면 됩니다. Structured Outputs 에 대응하는 JSON 스키마 강제 + system/user prompt 분리 만 같은 모양으로 유지하면 rca-bridge 의 나머지(fingerprint dedupe, evidence pack, redaction, storage) 는 그대로 재사용됩니다.

CE 의 AI RCA 우회 옵션 (이 글의 선택은 (A))

방법동작장점단점
(A) rca-bridge이 글의 선택incident webhook + 외부 LLM (OpenAI / Anthropic 등)공급자 중립 / 비용 통제 (gpt-4o-mini) / git 친화 / redaction 제어직접 구현·관리
(B) Coroot Cloud 연동CE → Coroot Cloud 로 push, Cloud 의 AI RCA 사용추가 구현 0외부 (Coroot 사) 에 데이터 송출 + 계정·네트워크 의존
(C) Alertmanager + 자체 GenAIPrometheus AM → Slack/Webhook → 별도 LLM service표준 Prometheus stackCoroot 의 후보 압축 결과 안 씀 — 정확도 ↓ + 토큰 ↑
(D) EE 업그레이드Coroot Enterprise 의 native AI RCAUI 통합 / 추가 구현 0비용 + vendor lock-in

현재 상태

rca-bridge 자체는 bootstrap-week7.sh 의 step 3d 로 배포가 끝난 상태입니다. 그리고 *Coroot Project 의 incident/alert webhookcoroot-webhook.yaml 을 통해 step 3a (install-coroot.sh) 에서 자동으로 박힙니다. 즉 Coroot 가 anomaly 를 감지하면 추가 설정 없이 곧장 rca-bridge/webhook 으로 POST 됩니다. 실제 발사 → AI 답까지의 흐름은 §8 의 chaos 시나리오를 주입하면 자연스럽게 한 사이클 돌아갑니다. 또 하나, OPENAI_API_KEY호스트 env* 로 step 3d 까지 전달되어야 합니다. 키가 없으면 bootstrap-week7.sh 가 fail-fast 하여 step 3d 진입 자체가 막힙니다.

7. rca-bridge 구현

구현은 FastAPI 앱 하나로 단순하게 묶었습니다. 배포는 rca-bridge-deployment.yaml 이 맡고, Coroot ↔︎ rca-bridge 사이의 incident webhook 라우팅coroot-webhook.yamlCoroot CRstrategic merge patch 형태로 박아 둡니다. 핵심은 아래 네 함수에 거의 다 들어 있습니다.

함수위치책임
webhook()main.pyPOST /webhook 진입점입니다. JSON 을 받아 핸들러로 넘기고, 중복이면 deduped, 정상 처리면 결과 경로를 돌려줍니다.
handle_incident()webhook_handler.pyfingerprint 확인, Prometheus/ClickHouse 증거 수집, OpenAI 호출, 결과 저장까지 묶는 중심 함수입니다.
build_user_prompt()prompting.pyincident 와 증거 묶음을 redaction 뒤에 프롬프트로 조립하고, 길이도 잘라 토큰 예산을 넘지 않게 합니다.
call_structured()openai_client.pygpt-4o-mini 기본, gpt-4o 폴백 1회, 그리고 JSON Schema 검증까지 맡습니다.

핵심만 보면 구조는 꽤 절제돼 있습니다. 웹훅을 받고, 증거를 얹고, 스키마를 강제한 JSON 을 만들고, 사람용 Markdown 으로 바꾸는 흐름입니다. 실습 글로서는 이 정도가 가장 읽기 좋습니다.

Coroot ↔︎ rca-bridge 연결 — 자연 흐름

Coroot 가 anomaly 를 감지하면 그대로 rca-bridge 로 incident 가 흘러가도록, Coroot CRspec.projects[].notificationIntegrations.webhookIaC 로 박아 두었습니다. 핵심은 coroot-webhook.yaml 한 파일이 strategic merge patch 라는 점입니다. 별도 kubectl apply 대상이 아니라 install-coroot.shhelm install 직후에 kubectl patch coroot coroot --patch-file ... 형태로 한 번 박고 끝납니다 — Coroot operator 가 reconcile 해도 CR 의 입력값이라 보존 됩니다.

# manifests/coroot-webhook.yaml — 발췌
spec:
  projects:
    - name: default
      apiKeys:
        - description: "default agent key (single-cluster mode required field)"
          key: "default-key-do-not-share"
      notificationIntegrations:
        baseURL: "http://localhost:8080"
        webhook:
          url: "http://rca-bridge.rca-bridge.svc.cluster.local:8080/webhook"
          incidents: true   # incident 발생 시 자동 POST
          alerts: true      # SLO 등 alert 도 함께 송출
필드의미
apiKeys[]단일 cluster 모드 일 때 Coroot CR 이 요구하는 필수 필드입니다 (운영용은 keySecret 으로 분리 권장).
notificationIntegrations.webhook.urlrca-bridge 의 K8s Service DNS. 같은 cluster 안이라 외부 노출 불필요 합니다.
incidents / alerts어떤 종류의 이벤트를 보낼지 — 둘 다 켜 두면 RCA 후보를 가장 폭넓게 받습니다.

흐름은 이렇게 닫힙니다.

이 자연 흐름이 지금 잘 작성 있는지는 한 줄로 확인합니다.

kubectl -n coroot get coroot coroot -o yaml | grep -A 20 'webhook:'

url: http://rca-bridge.rca-bridge.svc.cluster.local:8080/webhook 이 보이면 OK 입니다. Coroot UI 에서도 Project Settings → Notifications 에서 같은 URL 이 박혀 있는지 한 번 보고 가면 안심됩니다.

RCA 결과 보는 법

rca-bridgeCoroot 에서 받은 incident 마다 결과를 컨테이너 안 /results/ 에 남깁니다. 같은 incident_id 가 다시 들어오면 fingerprint dedupe 로 1 회만 LLM 을 호출합니다.

# 1) 가장 빠른 확인 — rca-bridge 로그 (POST 수신 / OpenAI 호출 / 저장까지 한 줄씩)
kubectl -n rca-bridge logs deploy/rca-bridge --tail=30

# 2) 결과 파일 목록 (최근 순)
kubectl -n rca-bridge exec deploy/rca-bridge -- ls -lt /results

# 3) 사람용 Markdown 한 건만 보기 (Top hypothesis · Evidence · Recommended actions)
kubectl -n rca-bridge exec deploy/rca-bridge -- cat /results/scenario-01.md

# 4) /results 통째로 호스트 mac 으로 복사 (tar stream)
kubectl -n rca-bridge exec deploy/rca-bridge -- tar cf - -C / results | tar xf - -C ./

Coroot UI 쪽에서는 Incidents 페이지의 각 항목이 언제 webhook 으로 송출됐는지 까지 확인됩니다. rca-bridge 가 받은 incident_id 가 /results/<incident_id>.json 에 그대로 박히므로, UI 와 파일을 1:1 로 맞추기 좋습니다.

수동 webhook 호출로 AI RCA 답 직접 보기

본격적으로 chaos 시나리오를 돌리기 전에, rca-bridge 가 정말로 LLM 까지 다녀오는지 수동 webhook 으로 한 번 확인하고 가는 게 좋습니다. bootstrap-week7.sh 가 step 3d 까지 통과한 상태라면 OPENAI_API_KEY 는 이미 K8s Secret rca-bridge-openai 에 들어가 있으므로, 추가로 키를 노출할 필요는 없습니다.

가짜 incident 한 건을 만들어 rca-bridge/webhook 에 직접 던집니다.

kubectl -n rca-bridge exec deploy/rca-bridge -- sh -c "cat > /tmp/incident.json << 'EOF'
{
  \"incident_id\": \"test-frontend-latency-001\",
  \"service\": \"frontend\",
  \"severity\": \"critical\",
  \"timestamp\": \"2026-05-24T15:30:00Z\",
  \"summary\": \"frontend p99 latency exceeded SLO budget — > 500ms for 3 minutes\",
  \"probable_causes\": [
    {\"name\": \"Network delay between frontend and product-catalog\", \"score\": 0.7},
    {\"name\": \"Recommendation service CPU saturation\", \"score\": 0.2}
  ],
  \"extra\": {\"scenario_id\": \"scenario-01\", \"chaos\": \"NetworkChaos delay 200ms\"}
}
EOF
curl -sS -X POST http://localhost:8080/webhook \
  -H 'Content-Type: application/json' -d @/tmp/incident.json"

응답은 한 줄짜리 {\"status\":\"ok\",\"request_id\":\"...\",\"result_path\":\"/results/scenario-01.json\"} 입니다. 호출 흐름 자체는 로그로 확인할 수 있습니다.

kubectl -n rca-bridge logs deploy/rca-bridge --tail=20

내부에서는 Prometheus 5 query (rate / error / p99 / CPU / memory) → ClickHouse logs·traces 조회 (선택) → OpenAI Responses API 호출 → JSON 정본 + Markdown 변환 → /results/scenario-NN.{json,md} 저장 의 순서로 진행됩니다. ClickHouse 가 401 을 돌려주면 logs·traces evidence 만 비워지고 RCA 자체는 계속 갑니다 (fingerprint dedupe, evidence pack, redaction, storage 가 모두 fail-soft 설계입니다).

결과 파일은 다음과 같이 꺼냅니다.

kubectl -n rca-bridge exec deploy/rca-bridge -- cat /results/scenario-01.md

payload 생성 → POST → 결과 cat 의 3 단계를 한 명령으로 묶은 helper 도 두었습니다. 사용자가 incident_id 만 인자로 주면 sample payload 생성 · /webhook 호출 · Markdown 결과 출력까지 한 번에 끝납니다.

# 자동 incident_id 사용 (timestamp 기반)
bash ./aiops/week7/scripts/run-rca-demo.sh

# 또는 직접 지정
bash ./aiops/week7/scripts/run-rca-demo.sh my-incident-001

Markdown 결과는 Top hypothesis · Ranked causes · Evidence · Recommended actions 네 블록으로 정리되어, Coroot 가 좁힌 후보를 LLM 이 사람 말로 옮긴 모양 그대로 읽힙니다. 이 결과가 §9 비교표의 AI top hypothesis일치도 칸을 채우는 입력이 됩니다.

CronJob 으로 주기 호출 자동화 (Heartbeat)

수동 webhook 한 번을 본 다음에는 주기 호출 로 한 단계 더 자동화할 수 있습니다. bootstrap-week7.sh 가 만든 환경 위에서 K8s CronJob 한 개와 payload ConfigMap 한 개만 더 얹으면 됩니다. 매니페스트는 rca-bridge-heartbeat-cronjob.yaml 한 파일에 합쳐 두었습니다.

1. 적용

kubectl apply -f ./aiops/week7/manifests/rca-bridge-heartbeat-cronjob.yaml
kubectl -n rca-bridge get cronjob,configmap | grep heartbeat

기본 스케줄은 0 */1 * * * (매시간 1 회) 입니다. 매시간 1 회면 gpt-4o-mini 기준 비용이 약 $0.07/일, $2/월 정도로 첫 실습편의 학습 비용 으로 부담이 적습니다. 더 자주 호출하고 싶다면 spec.schedule 만 바꿉니다.

spec.schedule1 일 호출 수1 달 예상 비용 (gpt-4o-mini)
0 */1 * * * (매시간)24$2
*/15 * * * * (15 분)96$9
*/5 * * * * (5 분)288$26

2. 즉시 1 회 trigger 해서 동작 확인

다음 스케줄까지 기다리지 않고 그 자리에서 Job 하나를 즉시 만들어 확인합니다.

# CronJob 으로부터 일회용 Job 생성 (즉시 fire)
kubectl -n rca-bridge create job heartbeat-manual --from=cronjob/rca-bridge-heartbeat

# Job 완료까지 대기 (보통 10~20초)
kubectl -n rca-bridge wait --for=condition=complete job/heartbeat-manual --timeout=60s

# Job 의 curl 로그 확인 — POST 응답 본문 출력
kubectl -n rca-bridge logs job/heartbeat-manual

3. 결과 누적 확인

rca-bridge 가 받은 webhook 마다 /results/scenario-NN.{json,md} 가 생성되거나 fingerprint 가 다르면 timestamp suffix 가 붙어 누적됩니다. 데이터는 *rca-bridge Pod 안의 /results 경로* 에 저장됩니다.

# Pod 안 파일 목록
kubectl -n rca-bridge exec deploy/rca-bridge -- ls -lt /results

# 한 파일 내용 보기
kubectl -n rca-bridge exec deploy/rca-bridge -- cat /results/scenario-01.md | head -20

# 호스트 mac 으로 한 파일 복사
kubectl -n rca-bridge cp $(kubectl -n rca-bridge get pod -l app.kubernetes.io/name=rca-bridge \
  -o jsonpath='{.items[0].metadata.name}'):/results/scenario-01.md ./scenario-01.md

# /results 전체를 mac 으로 한 번에 (tar stream)
kubectl -n rca-bridge exec deploy/rca-bridge -- tar cf - -C / results | tar xf - -C ./
ls ./results

4. CronJob 상태 모니터링

# 최근 실행 이력
kubectl -n rca-bridge get jobs -l app.kubernetes.io/name=rca-bridge-heartbeat
# CronJob 상세 (다음 schedule, 마지막 schedule 시각 확인)
kubectl -n rca-bridge describe cronjob rca-bridge-heartbeat

5. 학습 종료 시 비용 정지

학습/데모 끝나면 즉시 정지 해야 OpenAI 비용이 더 누적되지 않습니다.

# 자동화 매니페스트 제거 (CronJob + ConfigMap + 누적된 Job 들 함께 정리)
kubectl delete -f ./aiops/week7/manifests/rca-bridge-heartbeat-cronjob.yaml
kubectl -n rca-bridge delete jobs -l app.kubernetes.io/name=rca-bridge-heartbeat --ignore-not-found

데이터 영구 보관 (선택)

기본 설정에서 /results컨테이너의 ephemeral writable layer 에 저장됩니다. 즉 rca-bridge Pod 가 재시작 (chart 업그레이드, OOM, kubectl delete pod) 되면 누적된 모든 `scenario-.{json,md}가 사라집니다*. Heartbeat 를 길게 돌려 보고 싶거나 결과를 *나중에 묶어서 분석* 하려면PersistentVolumeClaim` 한 개를 붙이면 됩니다.

# 1) PVC 생성 (K3s 내장 local-path storageclass 사용 — 별도 설치 불필요)
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: rca-bridge-results
  namespace: rca-bridge
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: local-path
  resources:
    requests:
      storage: 1Gi
EOF

# 2) Deployment 에 PVC mount 추가 (strategic merge patch)
kubectl -n rca-bridge patch deployment rca-bridge --type=strategic --patch='
spec:
  template:
    spec:
      containers:
        - name: rca-bridge
          volumeMounts:
            - name: results
              mountPath: /results
      volumes:
        - name: results
          persistentVolumeClaim:
            claimName: rca-bridge-results
'

# 3) rollout 대기 후 새 Pod 에서 다시 webhook 호출 — 이번엔 PVC 에 저장
kubectl -n rca-bridge rollout status deploy/rca-bridge --timeout=120s

이 시점 이후의 /results/*PVC 안 에 영구 저장되므로 Pod 재기동에도 보존됩니다. 학습 종료 시점에는 kubectl delete pvc -n rca-bridge rca-bridge-results 로 함께 정리하면 디스크도 깔끔하게 회수됩니다.

이 자동화는 synthetic 입니다. 매 호출이 동일한 가짜 incident 를 보내기 때문에 AI top hypothesis내용 자체 가 새로 발견되는 자리는 아닙니다. 대신 다음 두 가지를 지속적으로 확인할 수 있습니다.

  • rca-bridgeliveness — Pod 가 살아 있고 /webhook 이 200 으로 응답하는지
  • OpenAI quota / 응답 schema — 키가 만료되거나 응답이 schema 를 어기지 않는지

실 incident 자동 감지까지는 한 가지가 남아 있습니다. Project webhook 활성 자체는 위 Coroot ↔︎ rca-bridge 연결 — 자연 흐름 단락에서 Coroot CR patch 로 끝났고, 남은 일은 지속적으로 incident 가 발생할 워크로드 를 자동 주입하는 것뿐입니다.

    1. chaos-mesh Schedule CRD 로 시나리오 주기 주입 — Coroot 의 SLO 정의만 UI 또는 별도 IaC 로 한 번 박아 두면, 그 다음부터는 자연 흐름이 자동으로 돕니다.

8. chaos-mesh 5 시나리오

chaos-mesh 는 K8s 위에서 제어된 장애 (controlled failure) 를 주입하는 CNCF incubating 프로젝트입니다. 이번 실습에서는 NetworkChaos, StressChaos, PodChaos 세 가지 CRD 만 씁니다. 이유는 단순합니다 — §4 의 4 시그널과 깔끔히 묶이기 때문입니다. NetworkChaoslatency/error 를, StressChaosCPU/memory profile 을, PodChaosavailability (재시작·복구) 를 흔듭니다. IOChaos/DNSChaos/TimeChaos 도 있지만 이번 비교표의 축과 겹치지 않아 제외했습니다.

chaos-mesh주입을 실제로 박는 자리 는 노드의 컨테이너 런타임 소켓입니다. K3s 는 자체 containerd 를 embed 하므로 install-chaos-mesh.shchaosDaemon.runtime=containerd + chaosDaemon.socketPath=/run/k3s/containerd/containerd.sock 를 박아 둡니다. 이 두 값이 빠지면 chaos-daemon 이 Pod 의 cgroup/네임스페이스에 진입하지 못해 주입이 noop 으로 끝납니다.

시나리오 한 건은 run-scenarios.sh 가 4 phase 로 돌립니다. 기본 phase 길이는 WINDOW_SECONDS=300 (5 분) 입니다.

phase길이역할
baseline5 분chaos 없는 평시 신호를 Coroot 에 쌓아 둡니다. inject 와 비교할 기준선 입니다.
inject5 분kubectl apply 로 chaos 주입. 매니페스트의 duration: "5m" 와 윈도가 맞습니다.
delete(즉시)kubectl delete -f ... --ignore-not-found. duration 으로 자동 정리됐어도 한 번 더 지워 안전합니다.
recovery5 분chaos 가 빠진 뒤 신호가 평시로 돌아오는지 확인. 이 윈도 안에 rca-bridge/results/scenario-NN.{json,md} 를 써야 검증 통과입니다.

첫 실습에서는 결과 수집보다 주입 명세를 먼저 분명히 보는 편이 낫습니다. 어떤 장애를 어디에 넣는지가 선명해야 Coroot 화면과 AI RCA 해석도 덜 흔들립니다.

#주입대상예상 핵심 신호매니페스트
1NetworkChaos delay 200ms ± 50msproduct-catalog ↔ frontendlatency p99 spike, SLO 소진01-network-delay.yaml
2NetworkChaos loss 10%checkout 인바운드error rate spike, retry pattern02-network-loss.yaml
3StressChaos cpu 4 workers / 80%recommendationCPU saturation, profile hot stack03-stress-cpu.yaml
4StressChaos memory 200MBcart (limit 192Mi)OOMKilled, restart 증가04-stress-memory.yaml
5PodChaos kill fixed-percent 50frontendpod restart, 일시적 5xx, auto recovery05-pod-kill.yaml

매니페스트 한 컷을 보고 가겠습니다. 시나리오 1의 NetworkChaos 는 다음과 같이 양방향 selector / target 으로 product-catalog ↔ frontend 트래픽에 지연을 주입합니다.

apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: scenario-01-network-delay
  namespace: demo-otel
spec:
  action: delay
  mode: all
  selector:
    namespaces: [demo-otel]
    labelSelectors: { app.kubernetes.io/component: product-catalog }
  target:
    mode: all
    selector:
      namespaces: [demo-otel]
      labelSelectors: { app.kubernetes.io/component: frontend }
  direction: both
  delay: { latency: 200ms, jitter: 50ms }
  duration: "5m"
필드의미
selectorchaos 를 주입할 Pod (product-catalog). chaos-daemon 이 이 Pod 의 네트워크 네임스페이스에 진입해 tc qdisc 를 박습니다.
target그 chaos 가 영향을 주는 상대 Pod (frontend). product-catalog ↔ frontend 트래픽에만 지연이 걸리고, 같은 namespace 의 cart/checkout 은 영향을 받지 않습니다.
direction: both들어오는 방향 + 나가는 방향 둘 다. 한쪽만 걸면 RTT 중 절반에만 지연이 잡혀 증상이 비대칭 으로 보입니다.
mode: allselector 가 매칭하는 모든 Pod 에 주입. PodChaos 처럼 일부만 죽이는 경우는 mode: fixed-percent + value: "50" 으로 비율을 지정합니다 (아래 참조).

다른 두 CRD 도 한 컷씩 보고 가겠습니다. StressChaosstressors.cpu / stressors.memory 로 부하 형태가 갈리고, PodChaosaction: pod-kill + mode: fixed-percent 로 일부 replica 만 죽입니다.

# scenarios/03-stress-cpu.yaml — 발췌 (recommendation CPU saturation)
kind: StressChaos
spec:
  mode: all
  selector:
    namespaces: [demo-otel]
    labelSelectors: { app.kubernetes.io/component: recommendation }
  stressors:
    cpu: { workers: 4, load: 80 }
  duration: "5m"
# scenarios/05-pod-kill.yaml — 발췌 (frontend pod 절반 강제 종료)
kind: PodChaos
spec:
  action: pod-kill
  mode: fixed-percent
  value: "50"   # selector 매칭 Pod 중 50%
  selector:
    namespaces: [demo-otel]
    labelSelectors: { app.kubernetes.io/component: frontend }
  duration: "5m"

StressChaos 의 핵심은 컨테이너 cgroup quota 안에서 부하가 걸린다는 점입니다. recommendationlimits.cpu=300m 안에서 worker 4 가 throttle 받으며 saturation 을 만들기 때문에 호스트 전체 CPU 가 출렁이지 않습니다. PodChaospod-kill 은 본질적으로 one-shot 이라 duration: "5m" 은 experiment 객체 lifecycle 보존용일 뿐 — 5 분 동안 계속 죽이는 게 아닙니다. 반복 kill 이 필요하면 chaos-mesh Schedule CRD 로 감싸야 합니다 (§7 의 heartbeat CronJob 과 유사한 패턴).

5 시나리오를 한 번에 돌리려면 wrapping 스크립트가 편합니다.

bash ./aiops/week7/scripts/run-scenarios.sh all

개별로 한 시나리오만 보고 싶을 때는 매니페스트를 직접 apply 하고 상태와 회복까지 따라갑니다.

kubectl apply -f ./aiops/week7/scenarios/01-network-delay.yaml
kubectl describe networkchaos -n demo-otel scenario-01-network-delay
# (5 분 inject window 동안 Coroot UI 에서 관찰)
kubectl delete -f ./aiops/week7/scenarios/01-network-delay.yaml

주입 직후 Coroot 에서는 세 자리를 같이 봅니다. service map 패널에서 product-catalog ↔ frontend 엣지에 지연 색이 들어오는지, 서비스별 dashboard 의 latency p99 시계열에 spike 가 잡히는지, SLO 패널에서 error budget 소진이 시작되는지입니다. UI 진입은 §2 의 port-forward svc/coroot-coroot 8080:8080 으로 호스트 브라우저에서 봅니다.

이 다섯 개가 좋은 이유는 서로 남기는 신호가 다르기 때문입니다. 앞의 둘은 네트워크 계열이라 latencyerror 로, 가운데 둘은 자원 압박이라 CPUOOM 으로, 마지막 하나는 가용성 계열이라 재시작과 복구로 드러납니다. 관찰 결과와 AI 요약은 여기서 길게 풀지 않고 마지막 표로 모읍니다.

9. 마치며

이제 남은 건 비교뿐입니다. 시나리오별 실제 주입과 예상 핵심 신호는 이미 정리됐고, AI top hypothesis일치도rca-bridge 를 연결한 뒤 같은 시나리오를 다시 돌리면 채울 수 있습니다. 첫 실습편에서는 이 정도의 빈칸이 오히려 정직합니다.

시나리오실제 주입Coroot/K8s 핵심 신호 (예상)AI top hypothesis일치도
1NetworkChaos delay 200mslatency p99 spike, SLO 소진pendingpending
2NetworkChaos loss 10%error rate spike, retry patternpendingpending
3StressChaos cpuCPU saturation, profile hot stackpendingpending
4StressChaos memoryOOMKilled, restart 증가pendingpending
5PodChaos killpod restart, 일시적 5xx, auto recoverypendingpending

이번 글에서 확인한 것은 세 가지입니다. native K3s 위에서 Coroot CEOTel Demo, chaos-mesh 를 한 사이클로 묶는 뼈대는 이미 갖춰져 있고, CE 의 빈 AI RCA 자리는 rca-bridge 로 메울 수 있으며, 다섯 장애 시나리오는 각각 다른 관찰 신호를 남길 준비가 되어 있습니다. 다음 단계는 더 많은 설명이 아니라, 같은 경로로 다시 한 번 돌려 §9의 두 pending 열을 채우는 일입니다.

profile
DevOps Engineer

0개의 댓글