From b606ed18aed21a5ff91d13353d60d28fc5550f40 Mon Sep 17 00:00:00 2001 From: Cloud User Date: Wed, 25 Feb 2026 15:32:09 +0900 Subject: [PATCH] Add CI/CD workflow examples and serving manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci.yaml: lint (ruff) + test (pytest) on push/PR - train.yaml: dvc pull → train → dvc push → docker build → deploy - dvc-data-update.yaml: data validation on .dvc file changes - manifests/deployment.yaml: example serving Deployment + Service Co-Authored-By: Claude Opus 4.6 --- .gitea/workflows/ci.yaml | 30 +++++++++++ .gitea/workflows/dvc-data-update.yaml | 40 ++++++++++++++ .gitea/workflows/train.yaml | 75 +++++++++++++++++++++++++++ README.md | 52 ++++++++++++++++--- manifests/deployment.yaml | 52 +++++++++++++++++++ 5 files changed, 242 insertions(+), 7 deletions(-) create mode 100644 .gitea/workflows/ci.yaml create mode 100644 .gitea/workflows/dvc-data-update.yaml create mode 100644 .gitea/workflows/train.yaml create mode 100644 manifests/deployment.yaml diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..83444a4 --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,30 @@ +name: CI - Lint & Test + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +jobs: + lint-and-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install ruff pytest + + - name: Lint + run: ruff check . + + - name: Test + run: pytest tests/ -v diff --git a/.gitea/workflows/dvc-data-update.yaml b/.gitea/workflows/dvc-data-update.yaml new file mode 100644 index 0000000..e077472 --- /dev/null +++ b/.gitea/workflows/dvc-data-update.yaml @@ -0,0 +1,40 @@ +name: DVC Data Validation + +on: + push: + paths: + - "**.dvc" + - "dvc.yaml" + - "dvc.lock" + +jobs: + validate-data: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + pip install dvc pandas + + - name: Pull updated data + run: dvc pull + + - name: Validate data + run: | + python -c " + import pandas as pd + import sys + + # TODO: 실제 데이터 경로와 검증 로직으로 수정 + # df = pd.read_csv('data/dataset.csv') + # assert len(df) > 0, 'Dataset is empty' + # assert df.isnull().sum().sum() == 0, 'Dataset has null values' + print('Data validation passed') + " diff --git a/.gitea/workflows/train.yaml b/.gitea/workflows/train.yaml new file mode 100644 index 0000000..67bbb13 --- /dev/null +++ b/.gitea/workflows/train.yaml @@ -0,0 +1,75 @@ +name: Train & Deploy + +on: + push: + branches: [main] + paths: + - "src/**" + - "configs/**" + - "dvc.yaml" + +jobs: + train: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install dvc + + - name: Pull data from DVC + run: dvc pull + + - name: Train model + run: python src/train.py + + - name: Track model with DVC + run: | + dvc add models/ + dvc push + + - name: Commit DVC metadata + run: | + git config user.name "gitea-runner" + git config user.email "runner@mlops" + git add models/*.dvc models/.gitignore + git diff --cached --quiet || git commit -m "Update model - $(date +%Y%m%d-%H%M%S)" + git push + + build-and-deploy: + needs: train + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: main + + - name: Build Docker image + run: | + IMAGE_TAG="${{ github.sha }}" + docker build -t $REGISTRY_URL/mlops-serving:${IMAGE_TAG} . + docker push $REGISTRY_URL/mlops-serving:${IMAGE_TAG} + env: + REGISTRY_URL: ${{ vars.REGISTRY_URL }} + + - name: Update manifest + run: | + IMAGE_TAG="${{ github.sha }}" + sed -i "s|image:.*mlops-serving.*|image: $REGISTRY_URL/mlops-serving:${IMAGE_TAG}|" manifests/deployment.yaml + git config user.name "gitea-runner" + git config user.email "runner@mlops" + git add manifests/ + git commit -m "Deploy model ${IMAGE_TAG:0:7}" + git push + env: + REGISTRY_URL: ${{ vars.REGISTRY_URL }} + # ↑ manifests/ 변경 → ArgoCD가 자동 감지 → soo ns에 배포 diff --git a/README.md b/README.md index 12b44c6..997ea88 100644 --- a/README.md +++ b/README.md @@ -163,20 +163,58 @@ dvc version --- +## 5. CI/CD 워크플로우 + +`.gitea/workflows/` 에 정의. Gitea Runner가 실행합니다. + +### ci.yaml — 코드 push 시 린트 + 테스트 + +``` +trigger: push to main/develop, PR to main +동작: ruff lint → pytest +``` + +### train.yaml — 모델 학습 + 배포 + +``` +trigger: src/, configs/, dvc.yaml 변경 시 +동작: + 1. dvc pull (NAS에서 데이터 가져오기) + 2. python src/train.py (학습) + 3. dvc add + dvc push (모델 → NAS) + 4. Docker build + push (서빙 이미지) + 5. manifests/ 이미지 태그 업데이트 → git push + 6. → ArgoCD 자동 감지 → soo ns에 배포 +``` + +### dvc-data-update.yaml — 데이터 변경 시 검증 + +``` +trigger: .dvc 파일, dvc.yaml, dvc.lock 변경 시 +동작: dvc pull → 데이터 검증 스크립트 실행 +``` + +--- + ## 파일 구조 ``` mlops_architecture/ -├── .env # 접속 정보, 시크릿 (git 제외) +├── .env # 접속 정보, 시크릿 (git 제외) ├── .gitignore -├── config.yaml # 인프라 구성 정보 -├── README.md # 설치 가이드 (이 파일) -├── .dvc/ # DVC 설정 (remote 등) +├── .dvc/ # DVC 설정 (remote 등) +├── .gitea/workflows/ # CI/CD 파이프라인 +│ ├── ci.yaml # 린트 + 테스트 +│ ├── train.yaml # 학습 → 빌드 → 배포 +│ └── dvc-data-update.yaml # 데이터 검증 ├── argocd/ -│ └── application.yaml # ArgoCD Application 정의 +│ └── application.yaml # ArgoCD Application 정의 +├── config.yaml # 인프라 구성 정보 ├── gitea-runner/ -│ └── deployment.yaml # Gitea Runner K8s 배포 (Secret+ConfigMap+Deployment) -└── manifests/ # K8s 배포 manifest (ArgoCD가 감시) +│ └── deployment.yaml # Gitea Runner K8s 배포 +├── manifests/ # K8s 배포 manifest (ArgoCD 감시) +│ └── deployment.yaml # 서빙 Deployment + Service +└── README.md # 설치 가이드 (이 파일) ``` ## 설정 파일 사용법 diff --git a/manifests/deployment.yaml b/manifests/deployment.yaml new file mode 100644 index 0000000..7bbd442 --- /dev/null +++ b/manifests/deployment.yaml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mlops-serving + namespace: soo + labels: + app: mlops-serving +spec: + replicas: 1 + selector: + matchLabels: + app: mlops-serving + template: + metadata: + labels: + app: mlops-serving + spec: + containers: + - name: serving + image: ${REGISTRY_URL}/mlops-serving:latest # CI에서 자동 업데이트 + ports: + - containerPort: 8080 + resources: + requests: + cpu: 500m + memory: 512Mi + limits: + cpu: "2" + memory: 2Gi + # env: + # - name: MODEL_PATH + # value: /models/model.pt + # volumeMounts: + # - name: model-storage + # mountPath: /models + # volumes: + # - name: model-storage + # persistentVolumeClaim: + # claimName: mlops-model-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: mlops-serving + namespace: soo +spec: + selector: + app: mlops-serving + ports: + - port: 80 + targetPort: 8080 + type: ClusterIP