26A29c

Young-Kyoo Kim·2026년 4월 28일

전체 컨테이너화 + 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-secretServiceAccount/RBACSecretsPVCArgo CronWorkflow 순으로 적용합니다. ServiceAccount에 imagePullSecret을 연결해두면 이후 모든 Pod가 자동으로 Nexus 인증을 사용합니다.

중요 포인트 두 가지:

첫째, 사내 Nexus가 자체 서명 인증서를 쓴다면 Docker daemon의 insecure-registries에 등록해야 빌드 머신에서 push가 됩니다. K8s 워커 노드의 containerd 설정에도 동일하게 추가해야 pull이 됩니다.

둘째, processor 이미지는 GPU(nvidia/cuda 베이스)를 쓰기 때문에 이미지 크기가 다른 것보다 훨씬 큽니다. 사내 빌드 머신에 GPU가 없어도 빌드는 가능하고, 실행 시에만 GPU 노드에 스케줄됩니다(nodeSelector: accelerator: nvidia-gpu).

===

AIOps Pipeline 컨테이너화 & Nexus 등록 전체 가이드

환경 가정: 사내 Nexus Repository Manager 3.x / Docker Registry 포함 / K8s 내부망(Airgapped)


1. 전체 디렉토리 구조

먼저 아래와 같이 소스 파일들을 배치합니다.

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에 올리지 않음)

2. .env.build (빌드 환경 변수 설정)

# .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                    # 배포 버전 태그

3. Dockerfile 작성

3-1. extractor (Confluence 추출기)

# 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

3-2. processor (LLM 분류기 - GPU 포함)

# 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에 미리 반입

3-3. indexer (Vector DB 인덱서)

# 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

3-4. git-tools (Git 동기화 도구)

# 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"]

3-5. api-server (AIOps FastAPI 서버)

# 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

4. Nexus Docker Registry 설정

4-1. Nexus에서 Docker Hosted Repository 확인/생성

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  ← 인증 필수

4-2. 로컬 Docker daemon에 Nexus insecure registry 등록

사내 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"

5. 빌드 & Push 스크립트

5-1. 전체 빌드 스크립트 (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 "=================================================="

5-2. 전체 Push 스크립트 (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에서 직접 확인하세요)"

5-3. 빌드 실행 방법

# 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

6. K8s에서 Nexus 이미지 Pull 설정

K8s 클러스터에서 Nexus의 프라이빗 이미지를 Pull하려면 imagePullSecret이 필요합니다.

6-1. 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>

6-2. ServiceAccount에 imagePullSecret 연결 (전역 설정)

매번 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

7. K8s 배포 순서 (처음 설치)

아래 순서대로 적용하면 됩니다.

# ── 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

8. K8s 리소스 YAML 전체

8-1. PVC 설정 (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

8-2. AIOps API 서버 Deployment (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

8-3. Argo CronWorkflow (이미지 주소 업데이트 포함)

# 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

9. 전체 실행 흐름 요약

[개발자 로컬 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

10. 트러블슈팅 체크리스트

# 이미지 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

0개의 댓글