> For the complete documentation index, see [llms.txt](https://uclix.gitbook.io/run-ai-rca-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://uclix.gitbook.io/run-ai-rca-docs/ko/operations.md).

# 운영 및 트러블슈팅

> **관점:** RCA 플랫폼 자체를 운영하는 것 — 정상 작동을 확인하는 방법과 그렇지 않을 때 무엇을 점검할지. **이 문서에서 다루는 것:** 헬스 체크 · "RCA 리포트 없음" 원인 · TypeDB / pgvector / Slack 진단 · 지식 그래프 검사 · 일반적인 실패 시그니처.

이 문서는 **Run:AI RCA** 자체를 운영하는 것에 대한 것이지, 그것이 분석하는 인시던트에 대한 것이 아닙니다. 분석 흐름은 [RCA 파이프라인](/run-ai-rca-docs/ko/rca-pipeline.md)을, 스토어는 [데이터 스토어](/run-ai-rca-docs/ko/database.md)를 참고하십시오.

**이 문서는 누구를 위한가:** RCA 서비스 자체를 운영하는 온콜 담당자를 위한 문서입니다. 먼저 “Is it actually working?”에서 alert 접수 경로를 따라가고, 그 뒤 실패한 의존성의 하위 섹션만 보세요. 프로세스가 건강하다고 해서 evidence collection이 끝났다는 뜻은 아닙니다.

## Is it actually working?

자동 RCA는 **Alertmanager가 Backend 웹훅에 POST한 이후**에만 시작됩니다 — Alertmanager의 Slack 알림만으로는 RCA 수신기가 라우팅되었음을 증명하지 못합니다. 실제 경로를 확인하십시오:

```bash
# Alerts and analysis runs the backend has actually received/started
curl -s http://<backend-or-frontend>/api/v1/alerts | jq '.data[0]'
curl -s http://<backend-or-frontend>/api/v1/analysis-runs | jq '.data[0]'

# Agent process liveness (means the API is up, NOT that a collector produced evidence)
curl -s http://<agent>/healthz
```

* UI의 수집기 카드는 **실행이 수집기 `artifacts`를 저장한 이후에만** `ok`로 바뀝니다 — `Running` 파드나 `200` 헬스 체크만으로는 충분하지 않습니다.
* Agent 헬스 응답에는 `collectors.active`와 `collectors.unknown`도 포함됩니다. 알 수 없는 구성 이름은 해당 증거 평면이 없다는 뜻이며, 같은 상태가 모든 분석에 경고로 추가됩니다.
* `ENABLE_NAT_RUNTIME=true`는 `/analyze` 합성에 영향을 줍니다. `/chat`은 결정론적 컨텍스트 답변을 반환하며 LLM 경로를 직접 호출하지 않습니다.

## "No RCA report was produced"

이 목록을 위에서부터 따라가십시오 — 흔한 원인을 가장 흔한 것부터 나열했습니다:

1. **Alertmanager가 웹훅으로 라우팅되지 않았습니다.** 알림이 Slack에는 도달했지만 `POST /webhook/alertmanager`에는 도달하지 않았습니다. Alertmanager 수신기/라우트를 확인하십시오([배포](/run-ai-rca-docs/ko/deployment.md) 참고). 그 알림은 `/api/v1/alerts`에 나타나지 않습니다.
2. **알림이 해결되어 건너뛰어졌습니다.** 해결된 알림은 의도적으로 분석하지 않습니다. 예상된 동작입니다.
3. **팬아웃 / 속도 제한.** 급증으로 인해 `MAX_AUTO_ANALYZE_FANOUT`(웹훅당) 또는 `MAX_CONCURRENT_AGENT_RUNS`를 초과했습니다. 백필 루프 (`ANALYSIS_BACKFILL_INTERVAL_SECONDS`)가 누락된 알림을 다시 구동합니다 — 한 사이클을 기다리거나 제한을 올리십시오.
4. **자동 재분석 쿨다운, 또는 새 정보 없는 재전송.** 알림이 재발했는데 새 실행이 나타나지 않았다면, 아직 자동 재분석 쿨다운(기본 360분) 이내여서 기존 실행이 재사용되었을 수 있습니다. 쿨다운이 지난 뒤라도 같은 에피소드의 Alertmanager *재전송* — 동일 fingerprint와 `StartsAt`, severity 상승 없음, occurrence 증가 없음 — 은 설계상 건너뜁니다. 새 증거가 없을 뿐 아니라, 재분석하면 운영자 승인과 거기서 파생된 지식이 해제되기 때문입니다.
5. **에이전트가 끝나기 전에 Backend가 연결을 끊었습니다.** `AGENT_REQUEST_TIMEOUT_SECONDS` (960)가 에이전트의 `ANALYSIS_DEADLINE_SECONDS`(900)보다 낮게 설정되면, 백엔드가 분석 도중에 취소하고 저하된 리포트가 유실됩니다. 백엔드 > 에이전트를 유지하십시오.
6. **Persist 실패는 의도된 조기 반환입니다.** 백엔드는 실행을 영속화할 수 없으면 조기 반환합니다. 이것은 설계상 의도된 것이며(테스트도 되어 있음) 버그가 아닙니다. 백엔드 로그와 Postgres 상태를 확인하십시오.

타임아웃을 지나 `analyzing` 상태에 갇힌 실행은 다음 백엔드 시작 시 `failed`로 정리됩니다 (`ReapStaleAnalyzingRuns`). 영원히 멈춰 있지 않습니다.

## TypeDB (ontology) diagnostics

그래프는 선택 사항입니다 — 꺼져 있거나 도달할 수 없을 때에도 분석은 계속 실행되며, 리포트는 단순히 Knowledge Base 섹션을 생략합니다(그 이유는 `warnings`에 기록됩니다).

```bash
# Did the schema/knowledge load job run?
kubectl get jobs -n <ns> | grep typedb
kubectl logs -n <ns> job/<release>-typedb-load-schema

# Did the ingest cron project incidents?
kubectl get cronjob,jobs -n <ns> | grep ingest
kubectl logs -n <ns> job/$(kubectl get jobs -n <ns> -o name | grep ingest | tail -1 | cut -d/ -f2)
# → "fetched N incident(s); ingesting M ... done: X written"

# Inspect the graph without writing TypeQL
kubectl exec -n <ns> deploy/<release>-agent -- python -m ontology.query --recent 20
kubectl exec -n <ns> deploy/<release>-agent -- python -m ontology.query --incident INC-...
kubectl exec -n <ns> deploy/<release>-agent -- python -m ontology.query --count
```

### 예상 밖 그래프 응답 조사

`--count`는 프로젝션된 인시던트, 알림, 노드만 다룹니다. 아래 읽기 전용 쿼리는 이 데이터베이스에 현재 들어 있는 큐레이션 지식과 실행형 runbook을 점검하며, `reduce`는 단일 `count` 행을 반환합니다.

```bash
# 큐레이션 지식 symptom(원인과 action 엣지를 모두 가져야 함).
kubectl exec -n <ns> deploy/<release>-agent -- python -m ontology.query --raw 'match $s isa symptom; (symptom: $s, cause: $c) isa indicates; (symptom: $s, remedy: $a) isa resolved_by; reduce $count = count($s);'
# 나쁜 결과: `{'count': 0}`이면 큐레이션 symptom/action 지식이 로드되지 않았습니다.

# 해당 knowledge symptom에서 도달 가능한 큐레이션 action.
kubectl exec -n <ns> deploy/<release>-agent -- python -m ontology.query --raw 'match $s isa symptom; (symptom: $s, cause: $c) isa indicates; (symptom: $s, remedy: $a) isa resolved_by; reduce $count = count($a);'
# 나쁜 결과: `{'count': 0}`이면 큐레이션 remediation action이 없습니다.

# 런타임이 사용하는 이름 있는 diagnostic runbook의 step.
kubectl exec -n <ns> deploy/<release>-agent -- python -m ontology.query --raw 'match $r isa runbook, has name "k8s-senior-troubleshooting"; (runbook: $r, step: $s) isa runbook_contains; reduce $count = count($s);'
# 나쁜 결과: `{'count': 0}`이면 실행형 runbook이 로드되지 않았습니다.

# 추론 함수 canary: 이 catalog symptom은 해당 family로 해석되어야 합니다.
kubectl exec -n <ns> deploy/<release>-agent -- python -m ontology.query --raw 'match let $x in causes_for_symptom("NVML Driver/Library Version Mismatch"); select $x;'
# 나쁜 결과: `gpu_hardware_error` 행이 없거나 `query failed`이면 catalog 또는 함수 정의가 없거나 오래되었습니다.
```

그래프에는 ingest watermark가 없습니다. 마지막으로 성공한 `typedb-ingest` Job의 완료 시각이 freshness 신호이지만, 그래프 freshness의 **PROXY**일 뿐입니다. 즉 Job이 완료된 시점만 알려 주고 어떤 레코드를 프로젝션했는지는 알려 주지 않으며 Kubernetes Job history retention이 적용됩니다.

```bash
kubectl get jobs -n <ns> -l app.kubernetes.io/component=typedb-ingest -o json | jq -r '[.items[] | select(.status.succeeded == 1) | .status.completionTime] | max // "no successful retained Job"'
```

`runs_on` 엣지가 없는 workload(고립된 topology)를 찾으려면 다음을 실행하십시오:

```bash
kubectl exec -n <ns> deploy/<release>-agent -- python -m ontology.query --raw 'match $w isa workload, has workload_uid $uid, has name $name; not { (host: $n, guest: $w) isa runs_on; }; select $uid, $name;'
# 나쁜 결과: 반환된 workload가 하나라도 있으면 node topology에서 고립되어 있습니다.
```

* **그래프가 비어 보이나요?** 인제스트는 **해결된 지 `resolvedGraceHours`(6h) 이상 지난** 인시던트만 프로젝션합니다. 새로 만든 클러스터에는 아직 적격한 대상이 없을 뿐입니다. `--recent`가 행을 반환하는데 인시던트 하나가 누락되었다면, 그 인시던트의 `resolved_at`이 null일 수 있습니다(UI "Resolved" ≠ DB `resolved_at` 설정됨).
* **`warnings`에 "TypeDB knowledge-graph query failed (...)"가 표시되나요?** — 메시지가 원인을 명시합니다(연결 거부 vs 인증 vs `[TQLxx]` 쿼리 오류). 이것은 결코 조용히 삼켜지지 않습니다.
* 필요 시 인제스트를 다시 실행하십시오: `kubectl create job -n <ns> --from=cronjob/<release>-typedb-ingest manual-ingest-1`.

### Trace-v3 backfill과 비어 있는 조사 trace

`typedb-trace-v3-backfill` Job은 Helm `post-install` / `post-upgrade` hook으로 실행됩니다. Helm은 성공한 hook Job을 삭제하므로, 나중에 완료된 Job이 보이지 않는 것은 정상입니다. 감사 로그가 필요하면 release event를 확인하거나 backfill 명령을 직접 실행하세요.

```bash
# 멱등적인 한 페이지를 수동 실행합니다. legacy trace는 변환하지 않습니다.
kubectl exec -n <ns> deploy/<release>-agent -- \
  python -m ontology.backfill_trace_v3 --batch-size 200 --max-batches 1
```

`0 written`만으로 오류를 뜻하지는 않습니다. `hypothesis`와 `probe_execution`은 명시적인 `reasoning_trace_v3`(또는 `trace_v3`)를 가진, Dashboard 승인된 active CaseSnapshot에서만 만들어집니다. legacy v1/v2 결과와 active가 아닌 snapshot은 의도적으로 변환하지 않습니다. 적격 trace-v3 snapshot을 만들려면 case를 재분석하고 승인한 뒤 backfill을 다시 실행하세요.

TypeDB Studio 접근은 [Knowledge Base → Querying the graph](/run-ai-rca-docs/ko/knowledge-base.md)를 참고하십시오.

## Grafana MCP (Prometheus / Loki) 진단

Prometheus와 Loki는 같은 managed `grafanaMcp` 서비스를 쓰지만 Grafana에서는 별도 datasource입니다. 따라서 Prometheus 쿼리가 성공해도 Loki datasource가 보이고 쿼리된다는 뜻은 아닙니다. 수집기는 MCP를 먼저 시도한 뒤 `PROMETHEUS_URL` 또는 `LOKI_URL`로 폴백하며, 수집기 artifact에 실제 증거를 제공한 경로가 남습니다.

Alertmanager alert에 `startsAt`이 있으면 Loki는 인시던트 시간 창을 조회합니다. 즉 알림 전 5분부터 `endsAt` 후 5분까지이며, 종료 시각이 없는 firing alert은 시작 후 15분에서 끝냅니다. 직접 Loki API에는 `start`/`end`를, Grafana MCP에는 `startRfc3339`/`endRfc3339`을 전달합니다. 파싱 가능한 시작 시각이 없는 alert은 datasource의 일반 recent window를 그대로 사용합니다.

직접 Prometheus/Loki 응답은 전송 경로 출처를 유지합니다. 비어 있는 native Prometheus vector는 범위가 확인된 부재를 수립할 수 있지만, 비어 있는 MCP/proxy 결과는 문맥일 뿐입니다. Loki 검증은 표시용 샘플이 아니라 전체 반환 라인을 검사하며, GPU 실패 쿼리는 OOM, killed process, NCCL WARN/ERROR, CUDA error, Xid, NVRM, panic, segfault 형식을 포함합니다.

```bash
# Grafana MCP가 설정한 조직에서 두 datasource UID를 모두 보는지 확인합니다.
kubectl logs -n <ns> deploy/<release>-grafana-mcp --tail=200

# 서비스 엔드포인트와 MCP 파드가 받은 Grafana URL을 확인합니다.
kubectl get svc -n <ns> <release>-grafana-mcp
kubectl get pod -n <ns> -l app.kubernetes.io/component=grafana-mcp \
  -o jsonpath='{range .items[*]}{.spec.containers[0].env[?(@.name=="GRAFANA_URL")].value}{"\n"}{end}'
```

* **`get datasource by uid ... 400 id is invalid`** — chart secret에 `GRAFANA_SERVICE_ACCOUNT_TOKEN`이 있는지, `grafanaMcp.grafanaUrl`이 클러스터 내부에서 Grafana에 도달하는지, `grafanaMcp.grafanaOrgId`가 Loki datasource의 조직과 같은지 확인하세요. datasource UID는 Grafana가 반환한 실제 값이어야 하며, 리터럴 `{uid}` 경로는 유효하지 않습니다.
* **MCP가 Prometheus만 나열하고 Loki는 나열하지 않음** — 해당 Grafana 조직에 Loki datasource를 만들거나 노출하고, service account에 datasource query 권한을 부여하세요. 이는 Loki gateway 자격 증명이 아니라 datasource/RBAC 문제입니다.
* **MCP는 실패하지만 direct Loki 증거는 성공함** — Grafana MCP를 고치는 동안 `LOKI_URL`은 클러스터 내부 read 서비스(보통 `loki-read:3100`)를 가리키게 유지하세요. 이는 의도된 graceful fallback이지만 MCP 고유의 datasource 의미 정보는 제공하지 못합니다.

## pgvector diagnostics

pgvector는 **백엔드**가 소유합니다. JSONB 희소 벡터 코사인 폴백으로 우아하게 저하되므로, 유사 인시던트 검색은 항상 작동합니다.

* 시작 로그는 `pgvector=enabled` 또는 `pgvector=unavailable, fallback=jsonb`를 보고합니다.
* `unavailable`은 `vector` 확장이 설치되지 않았거나 앱 사용자가 `CREATE EXTENSION vector`를 할 수 없다는 의미입니다. 번들된 `pgvector/pgvector:pg16` 이미지에는 이것이 포함되어 있습니다. 외부 Postgres의 경우 DBA가 설치해야 합니다([Backend README](/run-ai-rca-docs/readme/backend.md) 참고).
* 유사 인시던트는 `/analyze` 요청 페이로드(`similar_incidents` + `feedback_hints`)를 통해 에이전트에 공급됩니다 — 에이전트는 pgvector를 직접 쿼리하지 않습니다.

## Slack diagnostics

알림에는 인커밍 웹훅이 아니라 **봇 토큰**(`SLACK_BOT_TOKEN` + `SLACK_CHANNEL_ID`)이 필요합니다(`chat.postMessage`는 스레딩에 필요한 `ts`를 반환합니다).

* **아무것도 게시되지 않나요?** 두 환경 변수가 모두 설정되었는지, 토큰에 `chat:write` 권한이 있는지, 그리고 **봇이 채널에 초대되었는지** 확인하십시오. 전달은 파이어 앤 포겟 방식입니다 — 실패는 로그에 기록되며(`slack notify failed for incident ...`) 실행을 절대 차단하지 않습니다.
* **일부 실행만 게시됩니다.** 설계상 그렇습니다: 인시던트의 **첫 번째** 완료된 분석(루트 메시지)과 이후의 **운영자 주도** 재분석(`manual`/`comment`/`feedback`/`chat`, 스레드 답글로)만 게시됩니다. 자동/백필 후속 및 실패한 실행은 의도적으로 조용합니다.
* **resolved 메시지가 별도로 보입니다.** Alertmanager의 직접 Slack 리시버에는 `send_resolved: false`, RCA 웹훅에는 `send_resolved: true`를 설정하세요. 그러면 백엔드가 resolved 전환을 최초 분석 스레드에 게시합니다.
* **Open Incident** 버튼에는 `DASHBOARD_URL` 설정이 필요합니다. **Re-analyze** 버튼에는 `SLACK_APP_TOKEN`이 필요합니다(앱에서 Socket Mode + Interactivity 활성화).

### Slack notifications fail with invalid\_auth

증상: 백엔드 로그에는 `slack socket mode connected`가 보이지만, 완료된 모든 분석에서 `slack notify failed for incident ...: slack API error: invalid_auth`가 반복됩니다. Socket Mode는 앱 레벨 `SLACK_APP_TOKEN`(`xapp-`)을 사용하고, 메시지 게시에는 봇 토큰 `SLACK_BOT_TOKEN`(`xoxb-`)을 사용하므로 버튼 흐름은 연결되면서 게시만 실패할 수 있습니다.

흔한 원인은 Slack 앱 재설치입니다. 재설치하면 Bot User OAuth Token이 다시 발급되고 기존 `xoxb-`가 무효화됩니다. 또는 `xapp-` 토큰을 봇 토큰 슬롯에 잘못 넣은 경우입니다.

클러스터에서 진단:

```bash
SECRET=$(kubectl get secret -n runai-rca -o name | grep -i secret | head -1)
kubectl get $SECRET -n runai-rca -o jsonpath='{.data.SLACK_BOT_TOKEN}' | base64 -d | cut -c1-5   # expect xoxb-
TOKEN=$(kubectl get $SECRET -n runai-rca -o jsonpath='{.data.SLACK_BOT_TOKEN}' | base64 -d)
curl -s -H "Authorization: Bearer $TOKEN" https://slack.com/api/auth.test                        # {"ok":false,"error":"invalid_auth"} = reissue needed
```

해결: `api.slack.com/apps` -> **OAuth & Permissions**에서 Bot User OAuth Token을 다시 발급합니다. 앱을 재설치하면 `xoxb-` 토큰이 회전됩니다. 그런 다음 클러스터 시크릿을 업데이트합니다:

```bash
helm upgrade --reuse-values --set secrets.slackBotToken='xoxb-...' <release> <chart>
# 또는:
kubectl patch secret $SECRET -n runai-rca --type merge -p '{"stringData":{"SLACK_BOT_TOKEN":"xoxb-..."}}'
kubectl rollout restart deploy/<backend> -n runai-rca
```

`/healthz`에서 `slack.auth=ok`를 확인하고, 새로 완료된 분석이 채널에 도달하는지 검증합니다. 게시가 처음 실패하면 백엔드는 `slack: notifications are FAILING (invalid_auth) since ...` 같은 전환 로그도 남깁니다.

## Evidence looks thin

수집기 카드가 `unavailable`이거나 리포트에 \*"증거를 찾기 어렵습니다"\*라고 표시되는 경우:

* 해당 수집기의 데이터 소스가 구성되지 않았거나 도달할 수 없습니다(예: `LOKI_URL`, `PROMETHEUS_URL`, `SYSTEM_AGENT_URL` 미설정). 리포트는 원인을 지어내는 대신 누락된 소스를 명시합니다 — 이것은 정직성 게이트이지 버그가 아닙니다.
* 스텝별 상한은 의도적으로 넉넉합니다(120s). "최적화"를 위해 줄이지 마십시오 — 그러면 얕은 증거가 다시 도입됩니다. 지연 시간이 중요하다면 대신 `ANALYSIS_DEADLINE_SECONDS`를 조정하십시오.
* 에이전트가 더 깊이 파고들게 하려면 `ENABLE_INVESTIGATION_LOOP`와 `ENABLE_AGENT_DRILLDOWN`이 켜져 있고(Helm 기본값 true) LLM이 구성되어 있는지 확인하십시오 — LLM이 없으면 이 루프들은 건너뛰어지고 증거는 일회성이 됩니다.

## Where to look

| 증상                       | 먼저 확인할 것                                            |
| ------------------------ | --------------------------------------------------- |
| 알림이 전혀 없음                | Alertmanager 라우트 → `/api/v1/alerts`                 |
| 알림은 있으나 실행 없음            | `/api/v1/analysis-runs`, 팬아웃/속도 제한, 에이전트 `/healthz` |
| 실행이 `failed`             | 백엔드 로그, 에이전트 데드라인 vs 백엔드 타임아웃                       |
| Knowledge Base 섹션이 비어 있음 | TypeDB 도달 가능? 인제스트 실행됨? `warnings` 필드               |
| 유사 인시던트 없음               | pgvector 시작 로그, embeddings 테이블 채워짐 여부               |
| Slack 메시지 없음             | 봇 토큰 + 채널 + 봇 초대됨; 실행 소스 적격 여부                      |
| 얕은 증거                    | 데이터 소스 URL 설정됨; 드릴다운용 LLM 구성됨                       |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://uclix.gitbook.io/run-ai-rca-docs/ko/operations.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
