Confluence의 증분 수집 이후 Markdown 변환, Git 저장, Chunking 및 OpenSearch/Qdrant 하이브리드 인덱싱으로 이어지는 파이프라인(2~4단계)을 구현하기 위한 언어 선정, 필요 솔루션, 단계별 실전 구현 코드 및 K8s 배포 가이드입니다.
텍스트 파싱, 벡터 임베딩, 검색 엔진 연동 라이브러리 생태계가 가장 성숙한 Python 3.11+을 메인 언어로 채택합니다.
beautifulsoup4, html2text, GitPython, langchain-text-splitters, opensearch-py, qdrant-client, requests망분리 환경의 K8s 클러스터 내에 다음과 같은 파이프라인 및 런타임 요소가 준비되어야 합니다.
| 구 분 | 필요 솔루션 | 역할 및 비고 |
|---|---|---|
| Git Engine | Bitbucket Enterprise / GitLab | Markdown 파일 싱크 및 버전 관리 (Single Source of Truth) |
|
| Text Index Engine | OpenSearch v2.x | BM25 키워드, 에러 코드, Exact CLI 검색용 (Sparse Index)
|
| Vector Engine | Qdrant v1.x (또는 Milvus v2.x) | Dense Vector 검색용 Vector Database
|
| Embedding Runtime | HuggingFace TEI (Text Embeddings Inference) | bge-m3 등 로컬 임베딩 모델의 GPU/CPU 고속 인퍼런스 서버 (REST API)
|
| Task Orchestrator | K8s CronJob 또는 Argo Workflows | 파이프라인 스케줄링 및 이벤트 드라이브 실행
|
Confluence HTML에서 불필요한 매크로 및 태그를 정제하고, YAML Front-Matter가 포함된 Clean Markdown으로 변환합니다.
import re
import yaml
from bs4 import BeautifulSoup
import html2text
def convert_confluence_html_to_md(page_data: dict, raw_html: str) -> str:
# 1. BeautifulSoup을 이용한 HTML Cleaning
soup = BeautifulSoup(raw_html, 'html.parser')
# Confluence 전용 매크로, Sidebar, Table of Contents 등 불필요 태그 제거
for tag in soup.find_all(['script', 'style', 'nav', 'header', 'footer']):
tag.decompose()
for tag in soup.find_all('div', {'class': [ re.compile('toc.*'), re.compile('macro-.*')] }):
tag.decompose()
# 2. html2text 옵션 설정 및 AST 기반 Markdown 변환
h2t = html2text.HTML2Text()
h2t.ignore_links = False
h2t.ignore_images = False
h2t.ignore_tables = False
h2t.body_width = 0 # 줄바꿈 강제 적용 해제
clean_md_content = h2t.handle(str(soup))
# 3. YAML Front-Matter 주입
metadata = {
"id": page_data["id"],
"title": page_data["title"],
"space": page_data["space"]["key"],
"version": page_data["version"]["number"],
"author": page_data["history"]["createdBy"]["displayName"],
"last_modified": page_data["history"]["lastUpdated"]["when"],
"url": page_data["_links"]["base"] + page_data["_links"]["webui"],
"tags": [tag["name"] for tag in page_data.get("metadata", {}).get("labels", {}).get("results", [])]
}
yaml_frontmatter = f"---\n{yaml.dump(metadata, allow_unicode=True)}---\n\n"
return yaml_frontmatter + clean_md_content
변환된 Markdown 파일을 Git 저장소로 동기화하고 git commit_hash를 반환받습니다.
import os
import re
from git import Repo
GIT_REPO_PATH = "/workspace/ops-knowledge-repo"
def sync_md_to_git(doc_id: str, title: str, space: str, md_content: str) -> str:
repo = Repo(GIT_REPO_PATH)
repo.remotes.origin.pull() # 원격 변경 사항 동기화
# 파일명 내 특수문자 정제 (Slugify)
safe_title = re.sub(r'[\/:*?"<>|]', '_', title).replace(' ', '_')
dir_path = os.path.join(GIT_REPO_PATH, "ops-knowledge", space)
os.makedirs(dir_path, exist_ok=True)
file_path = os.path.join(dir_path, f"{doc_id}_{safe_title}.md")
# 파일 쓰기
with open(file_path, "w", encoding="utf-8") as f:
f.write(md_content)
# Git Commit & Push
repo.git.add(file_path)
commit_message = f"docs(sync): update Confluence doc {doc_id} - {title}"
# 변경 사항이 있는 경우만 커밋
if repo.is_dirty(untracked_files=True):
repo.index.commit(commit_message)
repo.remotes.origin.push()
# 인덱싱 타임스탬프 추적을 위해 최신 Commit Hash 취득
latest_commit_hash = repo.head.commit.hexsha
return latest_commit_hash
Header 기준 Chunking 후, OpenSearch(키워드 검색)와 Qdrant(벡터 검색)에 동시에 Bulk Upsert합니다.
import requests
from langchain_text_splitters import MarkdownHeaderTextSplitter
from opensearchpy import OpenSearch
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, Distance, VectorParams
# 클라이언트 초기화
opensearch_cli = OpenSearch(hosts=[{'host': 'opensearch.internal', 'port': 9200}])
qdrant_cli = QdrantClient(host="qdrant.internal", port=6333)
TEI_EMBEDDING_URL = "http://tei-embedding.internal/embed"
def generate_local_embedding(text: str) -> list[float]:
"""망분리 내부 TEI(Text Embeddings Inference) 호출"""
response = requests.post(TEI_EMBEDDING_URL, json={"inputs": text})
return response.json()[0]
def chunk_and_dual_index(doc_id: str, md_content: str, commit_hash: str):
# 1. Header 기반 Semantic Chunking
headers_to_split_on = [("#", "Header 1"), ("##", "Header 2"), ("###", "Header 3")]
markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
chunks = markdown_splitter.split_text(md_content)
# 2. 기존 doc_id 기반 삭제 (수정 문서 정합성 보장)
opensearch_cli.delete_by_query(
index="ops-knowledge-sparse",
body={"query": {"term": {"doc_id.keyword": doc_id}}}
)
qdrant_cli.delete(
collection_name="ops-knowledge-dense",
points_selector={"filter": {"must": [{"key": "doc_id", "match": {"value": doc_id}}]}}
)
# 3. Dual-Indexing Loop
qdrant_points = []
for idx, chunk in enumerate(chunks):
chunk_id = f"{doc_id}#{idx}"
chunk_text = chunk.page_content
metadata = chunk.metadata
metadata.update({"doc_id": doc_id, "commit_hash": commit_hash, "chunk_id": chunk_id})
# A. OpenSearch Indexing (Sparse - BM25)
opensearch_body = {
"doc_id": doc_id,
"chunk_id": chunk_id,
"content": chunk_text,
"commit_hash": commit_hash,
"metadata": metadata
}
opensearch_cli.index(index="ops-knowledge-sparse", id=chunk_id, body=opensearch_body)
# B. Dense Embedding & Qdrant Indexing
vector = generate_local_embedding(chunk_text)
qdrant_points.append(
PointStruct(
id=chunk_id,
vector=vector,
payload={"content": chunk_text, **metadata}
)
)
# Qdrant Bulk Upsert
if qdrant_points:
qdrant_cli.upsert(collection_name="ops-knowledge-dense", points=qdrant_points)
작성한 Python 메인 스크립트를 컨테이너화하여 K8s CronJob으로 주기적(예: 10분 간격) 실행하거나, N8N/Argo Workflows로 오케스트레이션합니다.
apiVersion: batch/v1
kind: CronJob
metadata:
name: confluence-rag-sync-pipeline
namespace: aiops
spec:
schedule: "*/10 * * * *" # 매 10분마다 실행
concurrencyPolicy: Forbid # 이전 작업 미완료 시 중복 실행 방지
jobTemplate:
spec:
template:
spec:
containers:
- name: sync-worker
image: harbor.internal/aiops/confluence-sync:v1.0.0
env:
- name: CONFLUENCE_URL
value: "http://confluence.internal"
- name: OPENSEARCH_URL
value: "http://opensearch.internal:9200"
- name: QDRANT_URL
value: "http://qdrant.internal:6333"
volumeMounts:
- name: git-workspace
mountPath: /workspace/ops-knowledge-repo
volumes:
- name: git-workspace
persistentVolumeClaim:
claimName: ops-knowledge-git-pvc
restartPolicy: OnFailure
git rm 커밋 후 OpenSearch/Qdrant 데이터베이스에서도 doc_id 기준으로 인덱스를 완전히 제거하는 Clean-up 로직이 주기적 스캔에 포함되어야 합니다.RecursiveCharacterTextSplitter로 2차 분할(예: Max 1,000 tokens)하는 예외 처리 구문을 추가하면 RAG 검색 정확도가 더욱 보장됩니다.==
위에서 제시된 Air-Gapped AIOps Architecture에서 Local LLM은 크게 4가지 핵심 역할을 수행하게 됩니다.
각 역할의 특성에 따라 요구되는 계산 리소스(부하)와 Bottleneck 요소가 명확히 달라지므로, LLM의 역할별 요구 조건과 실제 서비스 시 예상되는 Infra 부하 규모 및 Capacity Planning을 정리해 드립니다.
[ User / Alertmanager / Pipeline ]
│
▼
┌─────────────────────────────────────────────────┐
│ Role 1. Intent Routing & Query Reformulation │
└────────────────────────┬────────────────────────┘
│ (Structured Search Query)
▼
┌─────────────────────────────────────────────────┐
│ Hybrid Search Engine (OpenSearch + Qdrant) │
└────────────────────────┬────────────────────────┘
│ (Top-K Retrived Chunks)
▼
┌─────────────────────────────────────────┴─────────────────────────────────────────┐
│ │
▼ ▼
┌─────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────┐
│ Role 2. Incident Context Synthesis & Diagnosis │ │ Role 3. Work Plan & Code/Manifest Generation │
└─────────────────────────────────────────────────┘ └─────────────────────────────────────────────────┘
│ │
└─────────────────────────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Role 4. Output Guardrail & Structure Validator │
└─────────────────────────────────────────────────┘
rm -rf, kubectl delete ns 등)이 없는지 2차로 빠르게 검증(Self-Correction)합니다.Air-Gapped 환경에서 LLM 부하는 1) Prompt Processing Phase (Prefill - Input)와 2) Token Generation Phase (Decode - Output)로 나누어 파악해야 합니다.
| 구분 | Use Case 1: 장애 분석 (Incident Copilot) | Use Case 2: 작업계획서 생성 (Work Plan Assistant) |
|---|---|---|
| Input Context (Prefill) | 매우 높음 (16K ~ 32K Tokens) |
(로그, 메트릭, 과거 SOP 복수 조합) | 중/상 (4K ~ 12K Tokens)
(Confluence 양식 + Solution Docs) |
| Output Length (Decode) | 보통 (512 ~ 1,024 Tokens)
(원인 요약, 1차 조치 가이드) | 매우 높음 (2K ~ 4K Tokens)
(장문의 문서 + YAML/CLI 스크립트) |
| 주요 Bottleneck Factor | GPU Memory Bandwidth & VRAM (KV Cache) | Compute Bound (TPOT: Time Per Output Token) |
| 동시 요청 특성 | Alert Spike 시 Burst Traffic (동시 다발적 발생) | 엔지니어 업무 시간대 Steady/Interactive Traffic |
AIOps 시스템에 적합한 로컬 모델로 Qwen2.5-Coder-32B (Instruct) 또는 Llama-3.1-70B (FP8/Q4 Quantized) 수준을 적용한다고 가정한 스펙 산정입니다.
LLM 운영 시 VRAM은 [모델 Weights] + [KV Cache (Context 처리용)]의 합으로 산출됩니다.
결론: 32B FP8 모델 기준으로, 최적의 서비스 제공을 위해서는 최소 80GB VRAM 1장(A100 80GB / H100) 또는 48GB VRAM 2장(L40S x 2 / A6000 Ada x 2) 사양이 필요합니다.
A100 80GB 1장 (또는 L40S 2장) 구성 시 vLLM(PagedAttention) 기반 기대 성능:
Air-Gapped 인프라 자원은 한정되어 있으므로, LLM의 부하를 줄이고 처리 속도를 대폭 높이는 아키텍처 테크닉이 필수적입니다.
LLM으로 무작정 모든 로그와 문서를 밀어 넣으면 VRAM 폭발과 latency 상승의 원인이 됩니다.
==
Air-Gapped Cloud-Native Platform 환경에서 운영 문맥(Confluence)과 실시간 관측 데이터(Observability)를 결합한 AIOps 기반 Incident Management & Work Plan Generation Pipeline 설계안입니다.
망분리 환경의 특성을 고려하여 데이터 수집/변환(Lifecycle) -> Hybrid Search RAG Engine -> Operational AIOps Core로 레이어를 명확히 분리해 설계했습니다.
+---------------------------------------------------------------------------------------------------+
| Air-Gapped Enterprise Network |
| |
| [ Data Sources ] [ Sync & Storage Layer ] [ AI Engine & Knowledge Base ] |
| +-------------------+ +-----------------------+ +----------------------------+ |
| | Confluence | Sync | Bitbucket / GitLab | Indexing | Hybrid Search Engine | |
| | (SOP, Incident) |------>| (Markdown Documents) |----------->| (Qdrant/Milvus + OpenSearch)| |
| +-------------------+ +-----------------------+ +--------------+-------------+ |
| | Vendor Docs | Repo | Git Lifecycle Repo | | | |
| | (K8s, MinIO, etc) |------>| (Scheduled / Webhook) | v | |
| +-------------------+ +-----------------------+ +----------------------------+ |
| | Local LLM Runtime | |
| | (vLLM / Ollama Engine) | |
| +--------------+-------------+ |
| | | |
| [ Observability ] [ Event Processing ] | | |
| +-------------------+ +-----------------------+ Incident Query | | |
| | Prometheus/Thanos |------>| Event Stream Engine |---------------------------+ | |
| | Alertmanager | Alert | (Kafka / N8N / Vector)| | |
| +-------------------+ +-----------+-----------+ | |
| | OpenSearch Logs | Query | Context Synthesis | |
| +-------------------+ v | |
| +----------------------+ | |
| | AIOps Controller |<-------------------------------------------+ |
| | (LangGraph / Router) | | |
| +----------+-----------+ | |
| | | |
| v | |
| [ Ops UI & Work Assistant ] | |
| - Incident Root-Cause Draft | |
| - Work Plan (SOP + Helm/CLI) Generator | |
+---------------------------------------------------------------------------------------------------+
문서의 버전 추적과 변경 이력 관리를 위해 Confluence 및 공식 Docs를 Git(Bitbucket GitLab) 기반의 Single Source of Truth로 일원화합니다.
docs/{solution_name}/{version}/ 파티션 구조로 Markdown 변환합니다.repo-root/
├── ops-knowledge/ # Confluence 수집 문서
│ ├── sop/ # 표준 작업 절차서
│ ├── incident-reports/ # 과거 장애 회고 및 대응 이력
│ ├── architecture/ # 시스템/네트워크 구성도
│ └── weekly-reports/ # 주간 보고서
└── solution-docs/ # 솔루션 기술 문서
├── kubernetes/v1.30/
├── minio-aistor/
├── cilium/
└── keycloak/
main 브랜치에 자동 커밋됩니다. Commit SHA를 Vector DB의 메타데이터 필드(commit_id)로 관리하여, 문서 수정 시 해당 벡터만 Delta Update 하도록 구성합니다.단순 Dense Vector Search만으로는 커스텀 CLI 명령어나 Log Pattern, Exact Keyword(예: CiliumBGPPeeringPolicy, OOMKilled) 검색 정확도가 떨어집니다. 따라서 Sparse + Dense Hybrid Search를 필수로 가져갑니다.
Alert 발생 시 단순 알림으로 끝나는 것이 아니라, 관측 데이터 + past Incident/SOP를 LLM이 즉시 합성하여 1차 판단 결과를 제시합니다.
[ Alertmanager / OpenSearch Alert ]
│ (Alert Payload: Pod, Node, Metric, Log Snippet)
▼
[ Incident Pipeline Router ]
│ 1. Enrich: OpenSearch Log (±5m) & Thanos Metric Pattern 조회
│ 2. Hybrid Search Query: 에러 패턴 + Component Name
▼
[ Hybrid Knowledge Retrieval ]
│ - Confluence 과거 장애 보고서 중 유사 사례 Top 3
│ - 해당 서비스 SOP의 장애 조치 절차
▼
[ Local LLM Engine ]
│ Context Synthesis & Root-Cause Hypotheses Formulation
▼
[ Slack/Mattermost Incident Card ]
- 현상 분석: "MinIO AIStor Storage Pool IOPS Spike & Timeout 발생"
- 원인 추정: "과거 2026-04 유사 장애(INC-1042) 사례와 89% 일치 (Network Throttling)"
- 추천 SOP: "SOP-MINIO-04 (Net-pol 검증 및 Disk Queue Flush 명령어)"
신규 서비스 배포나 엔터프라이즈 컴포넌트(Keycloak, Vault, Kyverno 등) 변경 작업 시, 사내 가이드라인과 최신 공식 문서의 CLI/YAML 사양을 조합한 안전한 작업계획서 draft를 자동 생성합니다.
작업계획서 표준 템플릿, 사내 변경 관리 가이드라인, 사내 K8s 네트워크 정책 규정 Retrieval.Cilium BGPPeeringPolicy CRD v1.15, Cilium CLI 명령어 모음 Retrieval.Qwen2.5-Coder / Llama-3.1 기반 14B~32B 모델 (vLLM 기반 온프레미스 인퍼런스). 코드 및 YAML 작성 능력이 우수한 Coder 특화 모델 추천.vLLM 인프라를 KEDA와 연동하여 Dynamic Scaling 구성.vault:secret/data/...)만 출력하도록 System Prompt 수준의 Guardrail 적용.Thumbs Up/Down 또는 수정본을 Git PR로 보낼 경우, 이를 RAG Evaluation (Ragas Framework) 지표로 축적하여 Search Index 및 Reranker Weight tuning에 활용.==
import weasyprint
print(weasyprint.__version__)
62.3
import html
from weasyprint import HTML
html_content = """<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<style>
@page {
size: A4;
margin: 16mm 14mm 16mm 14mm;
background-color: #f8fafc;
@bottom-right {
content: counter(page) " / " counter(pages);
font-family: 'Noto Sans KR', sans-serif;
font-size: 8pt;
color: #64748b;
}
}
* { box-sizing: border-box; }
body {
font-family: 'Noto Sans KR', sans-serif;
color: #1e293b;
margin: 0;
padding: 0;
font-size: 9.5pt;
line-height: 1.55;
}
.header-banner {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
color: #ffffff;
padding: 24px 20px;
margin: -16mm -14mm 20px -14mm;
border-bottom: 4px solid #3b82f6;
}
.header-banner h1 {
margin: 0 0 6px 0;
font-size: 18pt;
font-weight: 700;
letter-spacing: -0.5px;
color: #ffffff;
}
.header-banner .subtitle {
margin: 0;
font-size: 10pt;
color: #94a3b8;
}
h2 {
font-size: 13pt;
font-weight: 700;
color: #0f172a;
border-left: 4px solid #2563eb;
padding-left: 8px;
margin-top: 22px;
margin-bottom: 10px;
page-break-after: avoid;
}
h3 {
font-size: 10.5pt;
font-weight: 700;
color: #1e40af;
margin-top: 14px;
margin-bottom: 6px;
page-break-after: avoid;
}
p { margin: 0 0 8px 0; }
.card {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 12px 14px;
margin-bottom: 12px;
}
.grid-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 12px;
font-size: 9pt;
}
.grid-table th {
background-color: #f1f5f9;
color: #334155;
font-weight: 700;
text-align: left;
padding: 6px 8px;
border: 1px solid #cbd5e1;
}
.grid-table td {
padding: 6px 8px;
border: 1px solid #e2e8f0;
vertical-align: top;
}
.badge {
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
font-size: 8pt;
font-weight: 700;
}
.badge-blue { background-color: #dbeafe; color: #1e40af; }
.badge-green { background-color: #dcfce7; color: #166534; }
.badge-purple { background-color: #f3e8ff; color: #6b21a8; }
pre, code {
font-family: 'Menlo', 'Monaco', 'Consolas', monospace;
}
pre {
background-color: #0f172a;
color: #e2e8f0;
padding: 10px 12px;
border-radius: 6px;
font-size: 8.5pt;
line-height: 1.45;
overflow-x: auto;
margin: 8px 0;
white-space: pre-wrap;
word-wrap: break-word;
}
ul, ol {
margin: 0 0 10px 0;
padding-left: 18px;
}
li { margin-bottom: 4px; }
.flow-box {
background-color: #f8fafc;
border: 1px dashed #94a3b8;
border-radius: 6px;
padding: 10px;
font-family: monospace;
font-size: 8.5pt;
color: #334155;
margin-bottom: 12px;
white-space: pre;
}
</style>
</head>
<body>
<div class="header-banner">
<h1>Confluence Sync to Git & Hybrid Indexing Architecture</h1>
<div class="subtitle">Air-Gapped Cloud-Native AIOps Environment | Ingestion, Git Lifecycle, Embedding & Dual-Index Pipeline</div>
</div>
<h2>1. 파이프라인 전체 프로세스 요약</h2>
<p>Confluence의 증분 동기화부터 Markdown 변환, Git 저장, Chunking, 임베딩 및 하이브리드 검색 인덱싱(OpenSearch + Qdrant/Milvus)까지 전체 흐름은 다음과 같은 이벤트 드라이브 구조로 동작합니다.</p>
<div class="flow-box">
[1. Trigger] ------------> [2. Diff Check] ----------> [3. Conversion]
(Webhook / Cron) (CQL: lastModified) (HTML -> Clean MD)
│
[6. Hybrid Search] <------ [5. Dual Indexing] <------- [4. Git Commit]
(BM25 + Dense) (OpenSearch + Vector) (Bitbucket / GitLab)
</div>
<h2>2. Confluence 문서 변경 감지 및 추출 (Incremental Extraction)</h2>
<p>모든 문서를 매번 재수집하는 것은 Air-Gapped 환경에서 과도한 I/O 및 LLM 인덱싱 비용을 유발합니다. 지난 동기화 이후 <strong>변경된 문서만 식별하는 2가지 방식</strong>을 적용합니다.</p>
<div class="card">
<h3>방법 A: Confluence REST API (CQL - Confluence Query Language) 배치 조회 [권장]</h3>
<p>주기적(예: 매 10분 또는 1시간)으로 배치 작업 시 <code>CQL</code>을 이용해 수정 시점 기준 증분 조회합니다.</p>
<pre>GET /wiki/rest/api/content/search?cql=space="OPS"+AND+lastModified>="2026-07-26 00:00"+ORDER+BY+lastModified+ASC</pre>
<p><strong>수정 구별 메커니즘:</strong></p>
<ul>
<li><strong>State Storage (Redis/PostgreSQL):</strong> 파이프라인 데이터베이스에 <code>last_sync_timestamp</code>를 저장 및 관리.</li>
<li><strong>Payload Verification:</strong> 조회된 문서의 <code>version.number</code> 및 <code>history.lastUpdated.when</code> 값을 비교하여 신규 생성/수정을 판별합니다.</li>
</ul>
</div>
<div class="card">
<h3>방법 B: Confluence Webhook 실시간 수신</h3>
<p>Confluence Admin Webhook에 <code>page_created</code>, <code>page_updated</code>, <code>page_removed</code> 이벤트를 등록하여 이벤트 발생 즉시 메시지 큐(Kafka, RabbitMQ, N8N)로 수신합니다.</p>
</div>
<h2>3. HTML $\rightarrow$ Clean Markdown 변환 & Metadata Enrichment</h2>
<p>Confluence Storage Format(XHTML 기반)은 불필요한 레이아웃 태그 및 매크로가 많아 LLM 임베딩 품질을 떨어뜨립니다.</p>
<table class="grid-table">
<thead>
<tr>
<th style="width: 25%;">단계</th>
<th style="width: 35%;">주요 작업 내용</th>
<th style="width: 40%;">사용 기술 / 도구</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>1. HTML Cleaning</strong></td>
<td>Confluence 전용 매크로 태그, CSS 스타일, Navigation, Sidebar 제거</td>
<td><code>BeautifulSoup4</code>, <code>lxml</code></td>
</tr>
<tr>
<td><strong>2. AST Markdown 변환</strong></td>
<td>HTML DOM을 AST(Abstract Syntax Tree)로 파싱하여 깔끔한 Markdown 규격으로 변환</td>
<td><code>pandoc</code>, <code>html2text</code></td>
</tr>
<tr>
<td><strong>3. YAML Front-Matter 삽입</strong></td>
<td>문서 상단에 추적성 관리를 위한 메타데이터 주입</td>
<td>Custom Python Script</td>
</tr>
</tbody>
</table>
<h3>생성되는 Markdown 규격 예시:</h3>
<pre>---
id: "CONF-10429"
title: "Cilium BGP Control Plane 장애 조치 SOP"
space: "OPS"
version: 3
author: "sre-admin"
last_modified: "2026-07-26T14:30:00Z"
url: "https://confluence.internal/pages/10429"
tags: ["cilium", "bgp", "k8s", "sop"]
---
# Cilium BGP Control Plane 장애 조치 SOP
## 1. 개요
Cilium BGP Peering 중단 발생 시 현상 파악 및 복구 절차입니다.
## 2. 대응 명령어
```bash
cilium bgp peers
kubectl -n kube-system logs -l k8s-app=cilium --tail=100
```</pre>
<h2>4. Git (Bitbucket / GitLab) 버전 관리 및 Sync Lifecycle</h2>
<p>변환된 Markdown 파일을 Git 저장소에 정제하여 커밋함으로써 <strong>Single Source of Truth</strong>를 유지합니다.</p>
<div class="card">
<h3>Git Directory & Commit Pipeline</h3>
<ul>
<li><strong>파일 경로 규칙:</strong> <code>ops-knowledge/{space}/{page_id}_{slugified_title}.md</code></li>
<li><strong>Git Operations:</strong>
<ul>
<li><strong>수정/생성:</strong> 변환된 MD를 지정 경로에 쓰기 $\rightarrow$ <code>git add .</code> $\rightarrow$ <code>git commit -m "docs(sync): update CONF-10429 (v3)"</code> $\rightarrow$ <code>git push origin main</code></li>
<li><strong>삭제 처리:</strong> Confluence에서 삭제 이벤트 수신 시 해당 <code>.md</code> 파일 <code>git rm</code> 후 커밋.</li>
</ul>
</li>
<li><strong>Commit Hash 추출:</strong> <code>GIT_COMMIT_HASH=$(git rev-parse HEAD)</code>를 취득하여 후속 Vector/Sparse 인덱스의 Metadata 필드에 주입.</li>
</ul>
</div>
<h2>5. Chunking & Dual-Index Hybrid Indexing (OpenSearch + Qdrant)</h2>
<p>Git Commit 이후 Webhook 또는 CI Pipeline이 인덱싱 워크플로우를 트리거합니다.</p>
<h3>① Semantic Header Chunking Strategy</h3>
<p>단순 자르기(Fixed Length)는 SOP의 절차나 YAML/Code block을 잘라버려 RAG 품질을 저하시킵니다. Markdown의 Headings(<code>#</code>, <code>##</code>, <code>###</code>)를 기준으로 나누는 **MarkdownHeaderTextSplitter**를 사용합니다.</p>
<h3>② Dual Index Ingestion (Sparse + Dense)</h3>
<table class="grid-table">
<thead>
<tr>
<th style="width: 20%;">검색 엔진</th>
<th style="width: 35%;">역할 및 특징</th>
<th style="width: 45%;">저장 Document Structure</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>OpenSearch<br><span class="badge badge-blue">Sparse Index</span></strong></td>
<td>BM25 키워드 검색<br>(에러 코드, exact CLI, 고유 식별자, YAML 키값 검색)</td>
<td><code>doc_id</code>, <code>chunk_id</code>, <code>content</code>, <code>commit_hash</code>, <code>space</code>, <code>tags</code></td>
</tr>
<tr>
<td><strong>Qdrant / Milvus<br><span class="badge badge-purple">Dense Index</span></strong></td>
<td>Vector Similarity 검색<br>(로컬 Embedding 모델: BGE-M3 / E5-large-v2, 1024 dim)</td>
<td><code>id (uuid)</code>, <code>vector</code>, <code>payload: {content, doc_id, chunk_id, commit_hash}</code></td>
</tr>
</tbody>
</table>
<h3>③ 삭제 및 업데이트 처리 (Sync Consistency)</h3>
<ul>
<li><strong>문서 수정 발생 시:</strong> 기존 <code>doc_id: CONF-10429</code>에 해당하는 모든 Chunk를 OpenSearch 및 Vector DB에서 <strong>Term Delete Filter</strong>로 일괄 삭제 후, 새 Chunk 들을 Bulk Insert.</li>
<li><strong>문서 삭제 발생 시:</strong> 해당 <code>doc_id</code>의 모든 Chunk 인덱스 즉시 삭제.</li>
</ul>
<h2>6. 엔드투엔드 파이프라인 자동화 아키텍처 (Air-Gapped Flow)</h2>
<pre>
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ Confluence (Internal) │
│ └─> [Cron Trigger (10m) / Webhook] │
└───────────────────────────┬────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ Sync Engine (Python Worker / N8N Container) │
│ ├─ 1. Call CQL API (lastModified >= last_sync_time) │
│ ├─ 2. Parse HTML -> Clean Markdown + Inject YAML Front-Matter │
│ ├─ 3. Write to Git Repo (/workspace/ops-knowledge/...) │
│ ├─ 4. Git Commit & Push (Bitbucket / GitLab) │
│ └─ 5. Trigger Indexing Pipeline with Git Commit Hash │
└───────────────────────────┬────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ Indexing Worker (Celery / Argo Workflows) │
│ ├─ 1. Pull Git Repository & Extract MD Files │
│ ├─ 2. MarkdownHeaderTextSplitter (Header-based Chunking) │
│ ├─ 3. Generate Embeddings via Local vLLM/TEI (Text Embeddings Inference) │
│ ├─ 4. Bulk UPSERT to OpenSearch Index (BM25 Engine) │
│ └─ 5. Bulk UPSERT to Qdrant/Milvus Collection (Vector Engine) │
└────────────────────────────────────────────────────────────────────────────────────────┘
</pre>
</body>
</html>
"""
with open("confluence_git_indexing.html", "w", encoding="utf-8") as f:
f.write(html_content)
HTML("confluence_git_indexing.html").write_pdf("confluence_git_indexing_pipeline.pdf")
print("PDF 생성 완료: confluence_git_indexing_pipeline.pdf")
PDF 생성 완료: confluence_git_indexing_pipeline.pdf
Your PDF file is ready
Confluence 문서의 증분(Incremental) 수집부터 Markdown 변환, Git 저장소 연동, 그리고 OpenSearch(키워드) 및 Qdrant/Milvus(벡터) 하이브리드 인덱싱까지의 전 과정 파이프라인 설계 및 자동화 프로세스 상세 설명입니다.
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Confluence API │ │ Clean Markdown │ │ Git Repository │
│ (CQL Filtered) ├─────>│ + Front-Matter ├─────>│ (Bitbucket/Git) │
└─────────────────┘ └──────────────────┘ └────────┬─────────┘
│
▼
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Dual Indexing │<─────┤ Text Embeddings │<─────┤ Header-based │
│ (OpenSearch/ │ │ (Local TEI) │ │ Chunking │
│ Qdrant/Milvus) │ └──────────────────┘ └──────────────────┘
└─────────────────┘
전체 문서를 매번 재수집하는 것은 망분리 내 I/O 및 인덱싱 부하를 크게 유발하므로 "마지막 동기화 시점 이후 변경/생성된 문서"만 가져옵니다.
last_sync_timestamp (예: 2026-07-26T00:00:00Z)를 저장합니다.GET /wiki/rest/api/content/search?cql=space="OPS"+AND+lastModified>="2026-07-26 00:00"+ORDER+BY+lastModified+ASC
id, version.number, history.lastUpdated.when 정보를 수집하여 신규 작성(Created) 및 수정(Updated)을 식별합니다.page_created, page_updated, page_removed 이벤트를 이벤트 큐(N8N / Kafka / RabbitMQ)로 즉시 수신합니다.Confluence Storage Format(XHTML)의 레이아웃, 매크로 태그를 제거하고 LLM 및 인덱싱에 최적화된 Clean Markdown으로 변환합니다.
BeautifulSoup 또는 lxml을 사용해 Navigation, Table of Contents 매크로, 스타일 CSS 제거Pandoc 또는 html2text를 통해 Markdown AST로 변환---
id: "CONF-10429"
title: "Cilium BGP Control Plane 장애 조치 SOP"
space: "OPS"
version: 3
author: "sre-admin"
last_modified: "2026-07-26T14:30:00Z"
url: "https://confluence.internal/pages/10429"
tags: ["cilium", "bgp", "k8s", "sop"]
---
# Cilium BGP Control Plane 장애 조치 SOP
...
정제된 Markdown 파일을 Git에 자동 커밋하여 Single Source of Truth를 구축합니다.
ops-knowledge/{space_name}/{page_id}_{slugified_title}.md.md 파일 덮어쓰기 git add . git commit -m "docs(sync): update CONF-10429 (v3)" git push origin main.md 파일 git rm 및 커밋GIT_COMMIT_HASH=$(git rev-parse HEAD) 값을 저장하여 후속 Vector/Sparse 인덱스의 메타데이터로 부여.Git Push가 완료되면 인덱싱 파이프라인(Argo Workflows 또는 Celery Worker)이 트리거됩니다.
단순 글자 수 자르기(Fixed-size Chunking)는 SOP의 절차나 YAML/Code Block을 자르는 문제가 발생합니다. Markdown 헤더(#, ##, ###) 기준인 MarkdownHeaderTextSplitter를 활용해 섹션 단위 문맥을 유지합니다.
| 구분 | OpenSearch (Sparse Index) | Qdrant / Milvus (Dense Index) |
|---|---|---|
| 검색 엔진 | BM25 Keyword Search | Vector Search (BGE-M3 / E5-large-v2) |
| 주요 역할 | 에러 코드, exact CLI, 고유 식별자, YAML key값 | 자연어 의도 파악, 문맥적 유사도 검색 |
| 저장 필드 | doc_id, chunk_id, content, commit_hash, tags | id(uuid), vector(1024-dim), payload:{content, doc_id, commit_hash} |
doc_id: CONF-10429에 해당하는 모든 Chunk를 OpenSearch 및 Vector DB에서 Term Delete로 일괄 삭제한 후, 신규 파싱된 Chunk 들을 Bulk Upsert 합니다.doc_id 기준 인덱스를 즉시 보이지 않도록 완전 삭제합니다.상세 가이드라인, 코드 예시 및 다이어그램이 포함된 문서는 생성된 PDF 파일(confluence_git_indexing_pipeline.pdf)에서도 확인하실 수 있습니다.