본 글의 소스코드 (manifests · scripts ·
rca-bridge앱) 는bocopile/document—aiops/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
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 안에서 자동 설치됩니다.
| 항목 | 권장 / 검증 값 |
|---|---|
| 호스트 OS | macOS 26.x (Apple Silicon arm64) |
| 호스트 RAM | 16GB+ (VM 에 8GB 할당) |
| 호스트 디스크 여유 | 30GB+ (VM image + chart pull) |
| 호스트 사전 도구 | multipass (설치), kubectl, ssh |
| VM | Ubuntu 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 제외 변형으로 실행해야 합니다).
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/...)를 씁니다.
6주차가 Coroot 의 내부 책임 분담을 코드로 확인하는 주였다면,
7주차는 그 분담이 실제 클러스터에서 어떤 장면으로 보이는지 확인하는 주입니다.
이번 글에서는 추상적인 설명보다 배선과 흐름을 먼저 봅니다.
어떤 신호가 커널에서 올라오고, 어떤 신호가 OTLP 로 들어오며, 어디까지가 CE 의 몫인지가 중심입니다.
또 하나의 축은 AI RCA 의 빈자리입니다.
Coroot Community Edition 은 관찰과 후보 압축까지는 잘하지만, 마지막 설명 문단은 비워 둡니다.
그래서 이번 실습은 Coroot 를 대체하는 이야기가 아니라, Coroot 가 이미 좁혀 둔 후보를 LLM 으로 읽기 좋게 바꾸는 얇은 다리 하나를 더하는 이야기입니다.

이번 구성에서 중요한 선택은 native K3s 입니다. eBPF 를 제대로 보려면 진짜 리눅스 커널이 필요하고, 그래서 호스트 쪽 진입점은 bootstrap-week7.sh 하나로 잡았습니다. 호스트에서는 Multipass, ssh, OPENAI_API_KEY 정도만 맞추면 되고, 나머지 K3s 와 실습 컴포넌트 설치는 체인 안으로 밀어 넣었습니다.
| 스크립트 | 실행 위치 | 입력 / 전제 | 책임 |
|---|---|---|---|
bootstrap-week7.sh | host | OPENAI_API_KEY, 선택적으로 FLANNEL_BACKEND=host-gw | 전체 진입점입니다. VM 준비, VM 안 설치, kubeconfig export 를 순서대로 묶습니다. |
bootstrap-multipass.sh | host | Multipass | Ubuntu VM 생성 또는 재사용, aiops/week7 마운트 |
native-k3s.sh | VM | Linux 커널 | native K3s, 네 개 namespace, Helm 준비 |
install-coroot.sh / install-otel-demo.sh / install-chaos-mesh.sh / install-rca-bridge.sh | VM | kubectl, helm | Coroot, OTel Demo, chaos-mesh, rca-bridge 설치 |
export-kubeconfig.sh | host | VM 안 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 우회로도 스크립트에 이미 들어 있습니다.
데모 앱은 otel-demo-values.yaml 로 조정한 OTel Demo 입니다. 이유는 단순합니다.
애플리케이션 쪽에서는 metrics, logs, traces 를 보내고, 노드 쪽에서는 profiles 를 붙일 수 있어서 이번 글의 4시그널을 한 번에 보기 좋습니다.
OTEL_COLLECTOR_NAME 을 coroot-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 서비스 는 트래픽이 흐르는 동안 배경 역할만 합니다.
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 커널 가정).
| 시그널 | 출발지 | 수집 메커니즘 | 저장 |
|---|---|---|---|
| metrics | OTel SDK + node-agent | 애플리케이션 계측 + 노드 관찰 | Prometheus |
| logs | stdout + OTLP | 컨테이너 로그 수집 + OTLP | ClickHouse |
| traces | OTel SDK | OTLP gRPC 4317 | ClickHouse |
| profiles | coroot-node-agent | eBPF continuous profiler | ClickHouse |
이 표 하나만 잡아도 이후 화면이 훨씬 잘 읽힙니다.
metrics 는 시계열로, logs 와 traces 는 사건 문맥으로,
profiles 는 애플리케이션 수정 없이 CPU 핫패스를 보는 용도로 들어옵니다.
특히 profiles 는 왜 이 실습이 native K3s 와 eBPF 를 같이 요구하는지 가장 분명하게 보여 주는 지점입니다.
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 네 자리가 모두 자동으로 잡힙니다.
다음으로 들어갈 자리는 Anomalies 와 Inspections 입니다. Anomalies 는 Coroot 가 자동으로 좁혀 둔 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 가 이 실습에 필요한 핵심 이유입니다.
대신 하나는 분명히 짚고 넘어갑니다. CE 는 AI RCA 를 안에 품고 있지 않습니다. CE 와 EE 의 경계를 비교하면 이 글의 후반부가 왜 그 자리 인지가 더 분명해집니다.
| 영역 | CE | EE |
|---|---|---|
metrics / logs / traces / profiles 수집 | ✅ | ✅ |
service map, SLO, dashboard | ✅ | ✅ |
Anomalies / Inspections (probable causes 좁힘) | ✅ | ✅ |
incident webhook 송출 | ✅ | ✅ |
AI RCA (incident 마지막 요약/내러티브) | 없음 | ✅ |
| 장기 retention / RBAC / SSO | 기본 | 확장 |
그래서 이 글의 후반부는 Coroot 를 바꾸는 작업이 아니라, 위 표의 없음 칸 옆에 설명기를 하나 붙이는 작업으로 읽는 편이 정확합니다. 아직 채우지 않은 항목은 흩어 놓지 않고 §9 비교표의 pending 열에만 모아 둡니다.
설계 원칙은 한 줄이면 됩니다. LLM 은 탐지기가 아니라 설명기입니다. 원인을 좁히는 일은 Coroot 가 하고, rca-bridge 는 incident webhook 을 받아 Prometheus 와 ClickHouse 에서 문맥을 조금 더 모은 뒤, 그 결과를 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) 는 그대로 재사용됩니다.
| 방법 | 동작 | 장점 | 단점 |
|---|---|---|---|
| (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 + 자체 GenAI | Prometheus AM → Slack/Webhook → 별도 LLM service | 표준 Prometheus stack | Coroot 의 후보 압축 결과 안 씀 — 정확도 ↓ + 토큰 ↑ |
| (D) EE 업그레이드 | Coroot Enterprise 의 native AI RCA | UI 통합 / 추가 구현 0 | 비용 + vendor lock-in |
현재 상태
rca-bridge 자체는 bootstrap-week7.sh 의 step 3d 로 배포가 끝난 상태입니다. 그리고 *Coroot Project 의 incident/alert webhook 도 coroot-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 진입 자체가 막힙니다.
구현은 FastAPI 앱 하나로 단순하게 묶었습니다. 배포는 rca-bridge-deployment.yaml 이 맡고, Coroot ↔︎ rca-bridge 사이의 incident webhook 라우팅 은 coroot-webhook.yaml 이 Coroot CR 의 strategic merge patch 형태로 박아 둡니다. 핵심은 아래 네 함수에 거의 다 들어 있습니다.
| 함수 | 위치 | 책임 |
|---|---|---|
webhook() | main.py | POST /webhook 진입점입니다. JSON 을 받아 핸들러로 넘기고, 중복이면 deduped, 정상 처리면 결과 경로를 돌려줍니다. |
handle_incident() | webhook_handler.py | fingerprint 확인, Prometheus/ClickHouse 증거 수집, OpenAI 호출, 결과 저장까지 묶는 중심 함수입니다. |
build_user_prompt() | prompting.py | incident 와 증거 묶음을 redaction 뒤에 프롬프트로 조립하고, 길이도 잘라 토큰 예산을 넘지 않게 합니다. |
call_structured() | openai_client.py | gpt-4o-mini 기본, gpt-4o 폴백 1회, 그리고 JSON Schema 검증까지 맡습니다. |
핵심만 보면 구조는 꽤 절제돼 있습니다. 웹훅을 받고, 증거를 얹고, 스키마를 강제한 JSON 을 만들고, 사람용 Markdown 으로 바꾸는 흐름입니다. 실습 글로서는 이 정도가 가장 읽기 좋습니다.
Coroot 가 anomaly 를 감지하면 그대로 rca-bridge 로 incident 가 흘러가도록, Coroot CR 의 spec.projects[].notificationIntegrations.webhook 을 IaC 로 박아 두었습니다. 핵심은 coroot-webhook.yaml 한 파일이 strategic merge patch 라는 점입니다. 별도 kubectl apply 대상이 아니라 install-coroot.sh 가 helm 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.url | rca-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-bridge 가 Coroot 에서 받은 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 로 맞추기 좋습니다.
본격적으로 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 와 일치도 칸을 채우는 입력이 됩니다.
수동 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.schedule | 1 일 호출 수 | 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-bridge 의 liveness — Pod 가 살아 있고 /webhook 이 200 으로 응답하는지OpenAI quota / 응답 schema — 키가 만료되거나 응답이 schema 를 어기지 않는지실 incident 자동 감지까지는 한 가지가 남아 있습니다. Project webhook 활성 자체는 위 Coroot ↔︎ rca-bridge 연결 — 자연 흐름 단락에서 Coroot CR patch 로 끝났고, 남은 일은 지속적으로 incident 가 발생할 워크로드 를 자동 주입하는 것뿐입니다.
chaos-mesh Schedule CRD 로 시나리오 주기 주입 — Coroot 의 SLO 정의만 UI 또는 별도 IaC 로 한 번 박아 두면, 그 다음부터는 자연 흐름이 자동으로 돕니다.chaos-mesh 는 K8s 위에서 제어된 장애 (controlled failure) 를 주입하는 CNCF incubating 프로젝트입니다. 이번 실습에서는 NetworkChaos, StressChaos, PodChaos 세 가지 CRD 만 씁니다. 이유는 단순합니다 — §4 의 4 시그널과 깔끔히 묶이기 때문입니다. NetworkChaos 는 latency/error 를, StressChaos 는 CPU/memory profile 을, PodChaos 는 availability (재시작·복구) 를 흔듭니다. IOChaos/DNSChaos/TimeChaos 도 있지만 이번 비교표의 축과 겹치지 않아 제외했습니다.
chaos-mesh 가 주입을 실제로 박는 자리 는 노드의 컨테이너 런타임 소켓입니다. K3s 는 자체 containerd 를 embed 하므로 install-chaos-mesh.sh 는 chaosDaemon.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 | 길이 | 역할 |
|---|---|---|
baseline | 5 분 | chaos 없는 평시 신호를 Coroot 에 쌓아 둡니다. inject 와 비교할 기준선 입니다. |
inject | 5 분 | kubectl apply 로 chaos 주입. 매니페스트의 duration: "5m" 와 윈도가 맞습니다. |
delete | (즉시) | kubectl delete -f ... --ignore-not-found. duration 으로 자동 정리됐어도 한 번 더 지워 안전합니다. |
recovery | 5 분 | chaos 가 빠진 뒤 신호가 평시로 돌아오는지 확인. 이 윈도 안에 rca-bridge 가 /results/scenario-NN.{json,md} 를 써야 검증 통과입니다. |
첫 실습에서는 결과 수집보다 주입 명세를 먼저 분명히 보는 편이 낫습니다. 어떤 장애를 어디에 넣는지가 선명해야 Coroot 화면과 AI RCA 해석도 덜 흔들립니다.
| # | 주입 | 대상 | 예상 핵심 신호 | 매니페스트 |
|---|---|---|---|---|
| 1 | NetworkChaos delay 200ms ± 50ms | product-catalog ↔ frontend | latency p99 spike, SLO 소진 | 01-network-delay.yaml |
| 2 | NetworkChaos loss 10% | checkout 인바운드 | error rate spike, retry pattern | 02-network-loss.yaml |
| 3 | StressChaos cpu 4 workers / 80% | recommendation | CPU saturation, profile hot stack | 03-stress-cpu.yaml |
| 4 | StressChaos memory 200MB | cart (limit 192Mi) | OOMKilled, restart 증가 | 04-stress-memory.yaml |
| 5 | PodChaos kill fixed-percent 50 | frontend | pod restart, 일시적 5xx, auto recovery | 05-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"
| 필드 | 의미 |
|---|---|
selector | chaos 를 주입할 Pod (product-catalog). chaos-daemon 이 이 Pod 의 네트워크 네임스페이스에 진입해 tc qdisc 를 박습니다. |
target | 그 chaos 가 영향을 주는 상대 Pod (frontend). product-catalog ↔ frontend 트래픽에만 지연이 걸리고, 같은 namespace 의 cart/checkout 은 영향을 받지 않습니다. |
direction: both | 들어오는 방향 + 나가는 방향 둘 다. 한쪽만 걸면 RTT 중 절반에만 지연이 잡혀 증상이 비대칭 으로 보입니다. |
mode: all | selector 가 매칭하는 모든 Pod 에 주입. PodChaos 처럼 일부만 죽이는 경우는 mode: fixed-percent + value: "50" 으로 비율을 지정합니다 (아래 참조). |
다른 두 CRD 도 한 컷씩 보고 가겠습니다. StressChaos 는 stressors.cpu / stressors.memory 로 부하 형태가 갈리고, PodChaos 는 action: 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 안에서 부하가 걸린다는 점입니다. recommendation 의 limits.cpu=300m 안에서 worker 4 가 throttle 받으며 saturation 을 만들기 때문에 호스트 전체 CPU 가 출렁이지 않습니다. PodChaos 의 pod-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 으로 호스트 브라우저에서 봅니다.
이 다섯 개가 좋은 이유는 서로 남기는 신호가 다르기 때문입니다. 앞의 둘은 네트워크 계열이라 latency 와 error 로, 가운데 둘은 자원 압박이라 CPU 와 OOM 으로, 마지막 하나는 가용성 계열이라 재시작과 복구로 드러납니다. 관찰 결과와 AI 요약은 여기서 길게 풀지 않고 마지막 표로 모읍니다.
이제 남은 건 비교뿐입니다. 시나리오별 실제 주입과 예상 핵심 신호는 이미 정리됐고, AI top hypothesis 와 일치도 는 rca-bridge 를 연결한 뒤 같은 시나리오를 다시 돌리면 채울 수 있습니다. 첫 실습편에서는 이 정도의 빈칸이 오히려 정직합니다.
| 시나리오 | 실제 주입 | Coroot/K8s 핵심 신호 (예상) | AI top hypothesis | 일치도 |
|---|---|---|---|---|
| 1 | NetworkChaos delay 200ms | latency p99 spike, SLO 소진 | pending | pending |
| 2 | NetworkChaos loss 10% | error rate spike, retry pattern | pending | pending |
| 3 | StressChaos cpu | CPU saturation, profile hot stack | pending | pending |
| 4 | StressChaos memory | OOMKilled, restart 증가 | pending | pending |
| 5 | PodChaos kill | pod restart, 일시적 5xx, auto recovery | pending | pending |
이번 글에서 확인한 것은 세 가지입니다. native K3s 위에서 Coroot CE 와 OTel Demo, chaos-mesh 를 한 사이클로 묶는 뼈대는 이미 갖춰져 있고, CE 의 빈 AI RCA 자리는 rca-bridge 로 메울 수 있으며, 다섯 장애 시나리오는 각각 다른 관찰 신호를 남길 준비가 되어 있습니다. 다음 단계는 더 많은 설명이 아니라, 같은 경로로 다시 한 번 돌려 §9의 두 pending 열을 채우는 일입니다.