전체 컨테이너화 + Nexus 등록 + K8s 배포 가이드를 작성했습니다.
1단계 — 로컬 PC에서 빌드
프로젝트 루트에 .env.build 파일에 Nexus 주소와 인증정보를 설정한 뒤 ./build/build-all.sh v1.2를 실행하면 5개 이미지(extractor, processor, indexer, git-tools, api-server)가 한 번에 빌드됩니다.
2단계 — Nexus에 Push
./build/push-all.sh v1.2를 실행하면 Nexus Docker Registry에 버전 태그와 latest 태그가 함께 등록됩니다. Push 후 Nexus REST API로 등록 결과를 자동 확인합니다.
3단계 — K8s 배포
순서가 중요한데, nexus-pull-secret → ServiceAccount/RBAC → Secrets → PVC → Argo CronWorkflow 순으로 적용합니다. ServiceAccount에 imagePullSecret을 연결해두면 이후 모든 Pod가 자동으로 Nexus 인증을 사용합니다.
중요 포인트 두 가지:
첫째, 사내 Nexus가 자체 서명 인증서를 쓴다면 Docker daemon의 insecure-registries에 등록해야 빌드 머신에서 push가 됩니다. K8s 워커 노드의 containerd 설정에도 동일하게 추가해야 pull이 됩니다.
둘째, processor 이미지는 GPU(nvidia/cuda 베이스)를 쓰기 때문에 이미지 크기가 다른 것보다 훨씬 큽니다. 사내 빌드 머신에 GPU가 없어도 빌드는 가능하고, 실행 시에만 GPU 노드에 스케줄됩니다(nodeSelector: accelerator: nvidia-gpu).
===
환경 가정: 사내 Nexus Repository Manager 3.x / Docker Registry 포함 / K8s 내부망(Airgapped)
먼저 아래와 같이 소스 파일들을 배치합니다.
aiops-pipeline/ ← 프로젝트 루트 (Git 저장소)
├── docker/
│ ├── extractor/
│ │ ├── Dockerfile
│ │ └── requirements.txt
│ ├── processor/
│ │ ├── Dockerfile
│ │ └── requirements.txt
│ ├── indexer/
│ │ ├── Dockerfile
│ │ └── requirements.txt
│ ├── git-tools/
│ │ └── Dockerfile
│ └── api-server/
│ ├── Dockerfile
│ └── requirements.txt
│
├── scripts/ ← 앞서 작성한 Python 소스
│ ├── incremental_export.py
│ ├── processor.py
│ ├── lifecycle_manager.py
│ ├── indexer.py
│ ├── aiops_agent.py
│ ├── api_server.py
│ └── sync_to_git.sh
│
├── k8s/
│ ├── secrets.yaml
│ ├── pvc.yaml
│ ├── argo-pipeline.yaml
│ ├── api-server-deployment.yaml
│ └── nexus-pull-secret.yaml
│
├── build/
│ ├── build-all.sh ← 전체 이미지 한번에 빌드
│ └── push-all.sh ← 전체 이미지 Nexus에 Push
│
└── .env.build ← 빌드용 환경변수 (Git에 올리지 않음)
# .env.build ← .gitignore에 추가 필수
NEXUS_HOST=nexus.internal.company.com
NEXUS_REPO=docker-hosted # Nexus Docker Hosted Repository 이름
NEXUS_PORT=8082 # Nexus Docker Registry 포트
NEXUS_USER=deploy-user
NEXUS_PASS=your-nexus-password
IMAGE_TAG=v1.2 # 배포 버전 태그
# docker/extractor/Dockerfile
FROM python:3.11-slim AS base
LABEL maintainer="platform-team@company.com"
LABEL version="1.2"
LABEL description="Confluence Incremental Exporter"
# 시스템 패키지 설치 (최소화)
RUN apt-get update && apt-get install -y --no-install-recommends \
wget curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# MinIO Client 설치
RUN wget -q https://dl.min.io/client/mc/release/linux-amd64/mc \
-O /usr/local/bin/mc && chmod +x /usr/local/bin/mc
WORKDIR /app
# Python 의존성 먼저 복사 (캐시 레이어 활용)
COPY docker/extractor/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 소스 복사
COPY scripts/incremental_export.py .
# 비루트 사용자로 실행 (보안)
RUN useradd -m -u 1000 appuser && chown -R appuser /app
USER appuser
ENTRYPOINT ["python", "incremental_export.py"]
# docker/extractor/requirements.txt
atlassian-python-api==3.41.11
html2text==2024.2.26
minio==7.2.7
requests==2.31.0
python-dotenv==1.0.0
# docker/processor/Dockerfile
# GPU 사용: CUDA 12.1 + Python 3.11 베이스 이미지
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04 AS base
LABEL description="LangChain LLM Document Processor"
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1
RUN apt-get update && apt-get install -y --no-install-recommends \
python3.11 python3.11-dev python3-pip \
wget curl git \
&& rm -rf /var/lib/apt/lists/*
# python3.11을 기본 python으로 설정
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.11 1 \
&& update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1
# MinIO Client
RUN wget -q https://dl.min.io/client/mc/release/linux-amd64/mc \
-O /usr/local/bin/mc && chmod +x /usr/local/bin/mc
WORKDIR /app
COPY docker/processor/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 소스 복사
COPY scripts/processor.py .
COPY scripts/lifecycle_manager.py .
RUN useradd -m -u 1000 appuser && chown -R appuser /app
USER appuser
ENTRYPOINT ["python", "processor.py"]
# docker/processor/requirements.txt
langchain==0.2.16
langchain-openai==0.1.23
langchain-community==0.2.16
langchain-core==0.2.38
langchain-huggingface==0.0.3
minio==7.2.7
pydantic==1.10.21
pyyaml==6.0.2
requests==2.31.0
# 폐쇄망: transformers/sentence-transformers는 모델과 함께 /models에 미리 반입
# docker/indexer/Dockerfile
FROM python:3.11-slim AS base
LABEL description="Milvus Vector DB Indexer"
RUN apt-get update && apt-get install -y --no-install-recommends \
git curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY docker/indexer/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY scripts/indexer.py .
RUN useradd -m -u 1000 appuser && chown -R appuser /app
USER appuser
ENTRYPOINT ["python", "indexer.py"]
# docker/indexer/requirements.txt
langchain==0.2.16
langchain-community==0.2.16
langchain-huggingface==0.0.3
langchain-milvus==0.1.4
pymilvus==2.4.4
unstructured==0.14.10
pyyaml==6.0.2
sqlalchemy==2.0.34
# docker/git-tools/Dockerfile
FROM alpine:3.19 AS base
LABEL description="Git Sync Tool for Wiki Pipeline"
# git, bash, python3, mc 설치
RUN apk add --no-cache \
git bash curl wget python3 py3-pip \
openssh-client
# MinIO Client
RUN wget -q https://dl.min.io/client/mc/release/linux-amd64/mc \
-O /usr/local/bin/mc && chmod +x /usr/local/bin/mc
# Python 패키지 (lifecycle_manager.py 의존성)
RUN pip3 install --no-cache-dir pyyaml requests
WORKDIR /app
COPY scripts/sync_to_git.sh .
COPY scripts/lifecycle_manager.py .
RUN chmod +x sync_to_git.sh
# git 설정 (컨테이너 내 기본값)
RUN git config --global http.sslVerify false && \
git config --global core.compression 0
ENTRYPOINT ["/bin/bash", "sync_to_git.sh"]
# docker/api-server/Dockerfile
FROM python:3.11-slim AS base
LABEL description="AIOps LangGraph Agent API Server"
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY docker/api-server/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# K8s Python Client (클러스터 내부에서 실행)
RUN pip install --no-cache-dir kubernetes==30.1.0
COPY scripts/aiops_agent.py .
COPY scripts/api_server.py .
RUN useradd -m -u 1000 appuser && chown -R appuser /app
USER appuser
EXPOSE 8080
# 헬스체크
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s \
CMD curl -f http://localhost:8080/health || exit 1
ENTRYPOINT ["uvicorn", "api_server:app", "--host", "0.0.0.0", "--port", "8080", "--workers", "2"]
# docker/api-server/requirements.txt
fastapi==0.111.1
uvicorn==0.30.3
langchain==0.2.16
langchain-openai==0.1.23
langchain-community==0.2.16
langchain-huggingface==0.0.3
langchain-milvus==0.1.4
langgraph==0.1.19
pymilvus==2.4.4
httpx==0.27.0
pydantic==2.8.2
pyyaml==6.0.2
Nexus 관리 UI에서 확인합니다.
Nexus UI: http://nexus.internal.company.com:8081
메뉴: Repository → Repositories → Create repository
Type: docker (hosted)
이름: docker-hosted
HTTP Port: 8082 ← Docker push/pull에 사용할 포트
Allow anonymous: false ← 인증 필수
사내 Nexus가 자체 서명 인증서를 쓰는 경우(내부망 흔한 상황):
// /etc/docker/daemon.json (Linux) 또는
// Docker Desktop → Settings → Docker Engine (Windows/Mac)
{
"insecure-registries": [
"nexus.internal.company.com:8082"
],
"registry-mirrors": [],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
# daemon.json 수정 후 Docker 재시작
sudo systemctl restart docker
# 확인
docker info | grep -A 5 "Insecure Registries"
build/build-all.sh)#!/bin/bash
# build/build-all.sh
# 사용법: ./build/build-all.sh [optional-tag]
# 예시: ./build/build-all.sh v1.3
set -euo pipefail
# .env.build 로드
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
source "$PROJECT_ROOT/.env.build"
# 태그 오버라이드 (인자가 있으면 사용)
TAG="${1:-$IMAGE_TAG}"
REGISTRY="${NEXUS_HOST}:${NEXUS_PORT}"
echo "=================================================="
echo " AIOps Pipeline 이미지 빌드 시작"
echo " Registry : ${REGISTRY}/${NEXUS_REPO}"
echo " Tag : ${TAG}"
echo " Build Dir: ${PROJECT_ROOT}"
echo "=================================================="
# ── 이미지 목록 정의 ─────────────────────────────────────────
declare -A IMAGES=(
["extractor"]="docker/extractor"
["processor"]="docker/processor"
["indexer"]="docker/indexer"
["git-tools"]="docker/git-tools"
["api-server"]="docker/api-server"
)
# ── Nexus 로그인 ──────────────────────────────────────────────
echo ""
echo "▶ Nexus Docker Registry 로그인..."
echo "$NEXUS_PASS" | docker login "${REGISTRY}" \
--username "$NEXUS_USER" \
--password-stdin
echo "✅ 로그인 성공"
# ── 각 이미지 빌드 ────────────────────────────────────────────
for IMAGE_NAME in "${!IMAGES[@]}"; do
DOCKERFILE_DIR="${PROJECT_ROOT}/${IMAGES[$IMAGE_NAME]}"
FULL_TAG="${REGISTRY}/${NEXUS_REPO}/wiki-pipeline/${IMAGE_NAME}:${TAG}"
LATEST_TAG="${REGISTRY}/${NEXUS_REPO}/wiki-pipeline/${IMAGE_NAME}:latest"
echo ""
echo "▶ Building: ${IMAGE_NAME} (${FULL_TAG})"
echo "--------------------------------------------------"
docker build \
--no-cache \
--build-arg BUILDTIME="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
--build-arg VERSION="${TAG}" \
--label "build.version=${TAG}" \
--label "build.date=$(date -u +"%Y-%m-%d")" \
--file "${DOCKERFILE_DIR}/Dockerfile" \
--tag "${FULL_TAG}" \
--tag "${LATEST_TAG}" \
"${PROJECT_ROOT}" # 빌드 컨텍스트는 프로젝트 루트
echo "✅ Built: ${IMAGE_NAME}:${TAG}"
done
echo ""
echo "=================================================="
echo " 빌드 완료! Push하려면: ./build/push-all.sh ${TAG}"
echo "=================================================="
build/push-all.sh)#!/bin/bash
# build/push-all.sh
# 사용법: ./build/push-all.sh [optional-tag]
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
source "$PROJECT_ROOT/.env.build"
TAG="${1:-$IMAGE_TAG}"
REGISTRY="${NEXUS_HOST}:${NEXUS_PORT}"
declare -a IMAGE_NAMES=("extractor" "processor" "indexer" "git-tools" "api-server")
echo ""
echo "▶ Nexus에 이미지 Push 시작 (Tag: ${TAG})"
for IMAGE_NAME in "${IMAGE_NAMES[@]}"; do
FULL_TAG="${REGISTRY}/${NEXUS_REPO}/wiki-pipeline/${IMAGE_NAME}:${TAG}"
LATEST_TAG="${REGISTRY}/${NEXUS_REPO}/wiki-pipeline/${IMAGE_NAME}:latest"
echo ""
echo "▶ Pushing: ${IMAGE_NAME}..."
docker push "${FULL_TAG}"
docker push "${LATEST_TAG}"
echo "✅ Pushed: ${FULL_TAG}"
done
echo ""
echo "=================================================="
echo " Push 완료!"
echo ""
echo " 등록된 이미지 확인:"
for NAME in "${IMAGE_NAMES[@]}"; do
echo " ${REGISTRY}/${NEXUS_REPO}/wiki-pipeline/${NAME}:${TAG}"
done
echo "=================================================="
# Nexus에서 이미지 목록 확인 (REST API)
echo ""
echo "▶ Nexus에서 등록된 이미지 목록 조회..."
curl -s -u "${NEXUS_USER}:${NEXUS_PASS}" \
"http://${NEXUS_HOST}:8081/service/rest/v1/components?repository=${NEXUS_REPO}" \
| python3 -c "
import json,sys
data = json.load(sys.stdin)
for item in data.get('items',[]):
print(f\" → {item.get('name')}:{item.get('version')}\")
" 2>/dev/null || echo " (Nexus REST API 조회 실패 - UI에서 직접 확인하세요)"
# 1. 스크립트 실행 권한 부여
chmod +x build/build-all.sh build/push-all.sh
# 2. 전체 이미지 빌드
./build/build-all.sh v1.2
# 3. Nexus에 Push
./build/push-all.sh v1.2
# ── 특정 이미지만 빌드/Push 하고 싶을 때 ──────────────────
REGISTRY="nexus.internal.company.com:8082"
REPO="docker-hosted"
# 단일 이미지 빌드
docker build \
-f docker/processor/Dockerfile \
-t ${REGISTRY}/${REPO}/wiki-pipeline/processor:v1.2 \
.
# 단일 이미지 Push
docker push ${REGISTRY}/${REPO}/wiki-pipeline/processor:v1.2
K8s 클러스터에서 Nexus의 프라이빗 이미지를 Pull하려면 imagePullSecret이 필요합니다.
# k8s 네임스페이스에 Nexus 인증 정보 등록
kubectl create secret docker-registry nexus-pull-secret \
--namespace platform-ops \
--docker-server=nexus.internal.company.com:8082 \
--docker-username=deploy-user \
--docker-password=your-nexus-password \
--docker-email=platform@company.com
# 확인
kubectl get secret nexus-pull-secret -n platform-ops -o yaml
# k8s/nexus-pull-secret.yaml (선언형 방식)
apiVersion: v1
kind: Secret
metadata:
name: nexus-pull-secret
namespace: platform-ops
type: kubernetes.io/dockerconfigjson
data:
# base64 인코딩된 docker config
# 생성: cat ~/.docker/config.json | base64 -w 0
.dockerconfigjson: <base64-encoded-docker-config>
매번 Pod에 imagePullSecrets를 붙이는 대신, ServiceAccount에 연결하면 네임스페이스 내 모든 Pod에 자동 적용됩니다.
# 기존 default ServiceAccount에 연결
kubectl patch serviceaccount argo-wiki-sa \
-n platform-ops \
-p '{"imagePullSecrets": [{"name": "nexus-pull-secret"}]}'
# 또는 새 ServiceAccount 생성 시 포함
# k8s/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: argo-wiki-sa
namespace: platform-ops
imagePullSecrets:
- name: nexus-pull-secret
---
# Argo Workflows가 파이프라인 Pod를 생성할 수 있도록 권한 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: argo-wiki-role
namespace: platform-ops
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "configmaps", "persistentvolumeclaims"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["events", "nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "daemonsets"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: argo-wiki-rolebinding
namespace: platform-ops
subjects:
- kind: ServiceAccount
name: argo-wiki-sa
namespace: platform-ops
roleRef:
kind: Role
name: argo-wiki-role
apiGroup: rbac.authorization.k8s.io
아래 순서대로 적용하면 됩니다.
# ── Step 0: 네임스페이스 및 기반 리소스 ──────────────────────
kubectl create namespace platform-ops
# Nexus Pull Secret
kubectl apply -f k8s/nexus-pull-secret.yaml
# ServiceAccount & RBAC
kubectl apply -f k8s/serviceaccount.yaml
# ── Step 1: Secret (인증 정보) 등록 ──────────────────────────
# 실제 값으로 수정 후 적용
kubectl apply -f k8s/secrets.yaml -n platform-ops
# ── Step 2: 공유 볼륨 (PVC) 생성 ─────────────────────────────
kubectl apply -f k8s/pvc.yaml -n platform-ops
# ── Step 3: Argo Workflows 설치 (미설치 시) ──────────────────
kubectl create namespace argo
kubectl apply -n argo -f https://github.com/argoproj/argo-workflows/releases/latest/download/install.yaml
# 또는 폐쇄망에서는 Helm chart를 내려받아 설치
# ── Step 4: AIOps API 서버 배포 ──────────────────────────────
kubectl apply -f k8s/api-server-deployment.yaml -n platform-ops
# ── Step 5: Argo Workflows CronWorkflow 등록 ─────────────────
kubectl apply -f k8s/argo-pipeline.yaml -n platform-ops
# ── Step 6: 첫 실행 (수동 트리거) ────────────────────────────
# Argo CLI 또는 UI에서 즉시 실행
argo submit --from=cronworkflow/llm-wiki-pipeline -n platform-ops
# 실행 상태 확인
argo list -n platform-ops
argo get <workflow-name> -n platform-ops
argo logs <workflow-name> -n platform-ops
k8s/pvc.yaml)# k8s/pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wiki-pipeline-pvc
namespace: platform-ops
spec:
accessModes:
- ReadWriteMany # 여러 Pod가 동시 접근 가능 (NFS 또는 Ceph RBD 권장)
storageClassName: ceph-rbd # 사내 스토리지 클래스명으로 변경
resources:
requests:
storage: 50Gi
k8s/api-server-deployment.yaml)# k8s/api-server-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: aiops-api-server
namespace: platform-ops
labels:
app: aiops-api
spec:
replicas: 2
selector:
matchLabels:
app: aiops-api
template:
metadata:
labels:
app: aiops-api
spec:
serviceAccountName: argo-wiki-sa
imagePullSecrets:
- name: nexus-pull-secret
containers:
- name: api-server
image: nexus.internal.company.com:8082/docker-hosted/wiki-pipeline/api-server:v1.2
ports:
- containerPort: 8080
envFrom:
- secretRef:
name: wiki-pipeline-secrets
env:
- name: EMBEDDING_MODEL_PATH
value: "/models/bge-m3"
- name: MILVUS_HOST
value: "milvus.storage.svc.cluster.local"
volumeMounts:
- name: models
mountPath: /models
readOnly: true
resources:
requests:
cpu: "500m"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: models
hostPath:
path: /data/models # 워커 노드에 미리 모델 파일 배치
---
apiVersion: v1
kind: Service
metadata:
name: aiops-api-svc
namespace: platform-ops
spec:
selector:
app: aiops-api
ports:
- port: 8080
targetPort: 8080
type: ClusterIP
---
# 외부(Lens 로컬 PC)에서 접근할 수 있도록 NodePort 또는 Ingress 추가
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aiops-api-ingress
namespace: platform-ops
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: aiops.internal.company.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: aiops-api-svc
port:
number: 8080
# k8s/argo-pipeline.yaml (Nexus 이미지 주소 적용)
apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
name: llm-wiki-pipeline
namespace: platform-ops
spec:
schedule: "0 2 * * *"
timezone: "Asia/Seoul"
concurrencyPolicy: Forbid
workflowSpec:
serviceAccountName: argo-wiki-sa
entrypoint: main-pipeline
# 전역 imagePullSecrets 설정
imagePullSecrets:
- name: nexus-pull-secret
volumes:
- name: workspace
persistentVolumeClaim:
claimName: wiki-pipeline-pvc
- name: models
hostPath:
path: /data/models
templates:
- name: main-pipeline
dag:
tasks:
- name: step1-extract
template: confluence-extractor
- name: step2-process
dependencies: [step1-extract]
template: langchain-processor
- name: step3-lifecycle
dependencies: [step2-process]
template: lifecycle-manager
- name: step4-git-sync
dependencies: [step3-lifecycle]
template: git-syncer
- name: step5-indexing
dependencies: [step4-git-sync]
template: vector-indexer
- name: step6-notify
dependencies: [step5-indexing]
template: slack-notifier
# ── Nexus 이미지 주소 적용 ────────────────────────────────
- name: confluence-extractor
container:
image: nexus.internal.company.com:8082/docker-hosted/wiki-pipeline/extractor:v1.2
command: ["python", "incremental_export.py"]
resources:
requests: {cpu: "500m", memory: "1Gi"}
limits: {cpu: "2", memory: "2Gi"}
envFrom:
- secretRef:
name: wiki-pipeline-secrets
volumeMounts:
- name: workspace
mountPath: /workspace
- name: langchain-processor
container:
image: nexus.internal.company.com:8082/docker-hosted/wiki-pipeline/processor:v1.2
command: ["python", "processor.py"]
resources:
requests:
cpu: "2"
memory: "8Gi"
nvidia.com/gpu: "1"
limits:
cpu: "8"
memory: "16Gi"
nvidia.com/gpu: "1"
nodeSelector:
accelerator: nvidia-gpu
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
envFrom:
- secretRef:
name: wiki-pipeline-secrets
env:
- name: LLM_ENDPOINT
value: "http://vllm-service.ai-namespace.svc.cluster.local:8000/v1"
volumeMounts:
- name: workspace
mountPath: /workspace
- name: models
mountPath: /models
readOnly: true
- name: lifecycle-manager
container:
image: nexus.internal.company.com:8082/docker-hosted/wiki-pipeline/processor:v1.2
command: ["python", "lifecycle_manager.py", "--action", "archive",
"--clone-dir", "/workspace/llm-wiki"]
resources:
requests: {cpu: "200m", memory: "512Mi"}
limits: {cpu: "1", memory: "1Gi"}
envFrom:
- secretRef:
name: wiki-pipeline-secrets
volumeMounts:
- name: workspace
mountPath: /workspace
- name: git-syncer
container:
image: nexus.internal.company.com:8082/docker-hosted/wiki-pipeline/git-tools:v1.2
command: ["/bin/bash", "sync_to_git.sh"]
resources:
requests: {cpu: "200m", memory: "256Mi"}
limits: {cpu: "1", memory: "512Mi"}
envFrom:
- secretRef:
name: wiki-pipeline-secrets
volumeMounts:
- name: workspace
mountPath: /workspace
- name: vector-indexer
container:
image: nexus.internal.company.com:8082/docker-hosted/wiki-pipeline/indexer:v1.2
command: ["python", "indexer.py"]
resources:
requests: {cpu: "1", memory: "4Gi"}
limits: {cpu: "4", memory: "8Gi"}
env:
- name: WIKI_DIR
value: "/workspace/llm-wiki/wiki/active"
- name: EMBEDDING_MODEL_PATH
value: "/models/bge-m3"
envFrom:
- secretRef:
name: wiki-pipeline-secrets
volumeMounts:
- name: workspace
mountPath: /workspace
- name: models
mountPath: /models
readOnly: true
- name: slack-notifier
container:
image: curlimages/curl:latest # 이건 Nexus에 미러링 후 주소 변경 권장
command: [sh, -c]
args:
- |
DATE=$(date '+%Y-%m-%d %H:%M KST')
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-type: application/json' \
-d "{\"text\":\"✅ LLM Wiki 파이프라인 완료 (${DATE})\"}"
envFrom:
- secretRef:
name: wiki-pipeline-secrets
[개발자 로컬 PC]
1. 소스 수정 (scripts/*.py, docker/*/Dockerfile)
2. ./build/build-all.sh v1.3 ← 이미지 빌드
3. ./build/push-all.sh v1.3 ← Nexus에 Push
[Nexus Repository]
4. 이미지 저장
nexus.internal.company.com:8082/docker-hosted/wiki-pipeline/extractor:v1.3
nexus.internal.company.com:8082/docker-hosted/wiki-pipeline/processor:v1.3
...
[K8s 클러스터]
5. argo-pipeline.yaml의 image 주소를 v1.3으로 수정 후 apply
kubectl apply -f k8s/argo-pipeline.yaml -n platform-ops
6. Argo가 매일 새벽 2시에 자동 실행 (또는 수동 트리거)
argo submit --from=cronworkflow/llm-wiki-pipeline -n platform-ops
7. 각 Step Pod가 Nexus에서 이미지 Pull → 실행
step1: Confluence 추출 → MinIO
step2: LLM 분류 → MinIO wiki/
step3: 생애주기 관리
step4: Git Push
step5: Milvus 인덱싱
step6: Slack 알림
[AIOps API 서버] (24시간 상시 실행)
- Alertmanager Webhook 수신
- Lens에서 HTTP 호출: http://aiops.internal.company.com/analyze
- 정기 리스크 스캔: GET /risk-scan
# 이미지 Pull 실패 시
kubectl describe pod <pod-name> -n platform-ops | grep -A 10 "Events"
# → ImagePullBackOff: nexus-pull-secret 확인
kubectl get secret nexus-pull-secret -n platform-ops
# Nexus 연결 확인
curl -u deploy-user:password \
http://nexus.internal.company.com:8082/v2/_catalog
# 이미지 목록 확인
curl -u deploy-user:password \
http://nexus.internal.company.com:8082/v2/docker-hosted/wiki-pipeline/extractor/tags/list
# Argo 파이프라인 로그 확인
argo logs llm-wiki-pipeline-<hash> -n platform-ops --follow
# 특정 Step 로그만 확인
argo logs llm-wiki-pipeline-<hash> -n platform-ops -c langchain-processor
# Pod에 직접 접속하여 디버깅
kubectl run debug-pod --rm -it \
--image=nexus.internal.company.com:8082/docker-hosted/wiki-pipeline/processor:v1.2 \
--overrides='{"spec":{"imagePullSecrets":[{"name":"nexus-pull-secret"}]}}' \
-n platform-ops -- /bin/bash
빌드 가이드 버전: v1.0 | 작성일: 2026-04-29