Remove outdated documents related to the approval system and WACE system analysis

- Deleted the following files as they are no longer relevant to the current project structure:
  - 결재 시스템 구현 현황
  - 결재 시스템 v2 사용 가이드
  - WACE 시스템 문제점 분석 및 개선 계획
  - Agent Pipeline 한계점 분석
  - AI 기반 화면 자동 생성 시스템 설계서
  - WACE ERP Backend - 분석 문서 인덱스

These deletions help streamline the documentation and remove obsolete information, ensuring that only current and relevant resources are maintained.
This commit is contained in:
kjs
2026-04-01 12:35:40 +09:00
parent ccb0c8df4c
commit 8be4159f17
37 changed files with 2 additions and 1 deletions
@@ -0,0 +1,451 @@
# AI 기반 화면 자동 생성 시스템 설계서
## 1. 개요
### 1.1 목표
사용자가 자연어로 "자재입고페이지 만들어줘"라고 요청하면, AI가 자동으로 완성된 화면과 워크플로우를 생성
### 1.2 핵심 가치
- **제로 코딩**: 대화만으로 화면 생성
- **워크플로우 포함**: 저장 시 재고 업데이트 등 자동 설정
- **기존 시스템 활용**: 새 인프라 최소화, 기존 API 재사용
### 1.3 예상 시나리오
```
사용자: "자재입고페이지 만들어줘"
AI: "입고관리 화면 41개를 분석했습니다.
✅ 자동 적용 (90%+ 사용)
- 레이아웃: 분할화면 (좌: 목록, 우: 상세)
- 필수 필드: 입고번호, 입고일자, 품목코드, 수량
💡 추천 기능 (60% 사용)
- 저장 시 재고 자동 증가
[추가하기]
선택 필드
[ ] 창고 (68%) [ ] 공급업체 (61%)
[화면 생성하기]"
사용자: "재고 연동 추가하고, 창고 필드도 넣어줘"
AI: "화면을 생성했습니다.
- 화면명: 자재 입고관리
- 레이아웃: 분할화면
- 필드: 입고번호, 입고일자, 품목, 수량, 창고
- 워크플로우: 저장 시 inventory_stock +qty
[미리보기] [메뉴에 추가]"
```
---
## 2. 시스템 아키텍처
```
┌───────────────────────────────────────────────────────────────┬───────────────────────────────┐
│ 신규 개발 (AI 담당) │ 기존 드래그앤드랍 시스템 │
│ ┌─────────────┐ ┌──────────────────────────────┐ │ ┌─────────────────────┐ │
│ │ Chat UI │────▶│ AI 서비스 │ │ │ D&D UI Builder │ │
│ └─────────────┘ │ • LLM 호출 (Claude/GPT) │ │ └─────────┬──────────┘ │
│ │ • 패턴 분석 + RAG 검색 │ │ │ │
│ │ • JSON 생성 │ │ ▼ │
│ └──────────────┬──────────────┘ │ ┌─────────────────────┐ │
└─────────────────────────────────────┼─────────────────────---──┴─┼─────────────────────┬─────┘
│ 기존 API 호출 │ 기존 API 호출 │
▼ ┼─────────────────────┘
┌-------------------------------------------------------------------------------┐
│ 기존 시스템 (vexplor) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Screen API │ │ Flow API │ │ Dataflow API│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ PostgreSQL │ │
│ └─────────────┘ │
└-------------------------------------------------------------------------------┘
```
### 설계 원칙
| 원칙 | 설명 |
| --------------------- | --------------------------------- |
| 기존 코드 변경 최소화 | 기존 백엔드/프론트 수정 없음 |
| 기존 API 재사용 | AI가 기존 API를 "사용자처럼" 호출 |
| RAG 기반 지식 주입 | 필요한 패턴만 동적으로 LLM에 주입 |
| **Assistive AI** | AI는 결정하지 않고, 사용자의 결정을 돕는다 |
---
## 3. AI가 화면을 "알아서" 만드는 방법
### 3.1 지식 소스
| 데이터 | 테이블 | 활용 |
| --------------- | ---------------------------------- | ------------------- |
| 기존 화면 | screen_definitions, screen_layouts | 패턴 학습 |
| 테이블 라벨 | table_labels | 테이블 검색 |
| 컬럼 라벨 | column_labels | 필드→컴포넌트 매핑 |
| 워크플로우 패턴 | workflow_patterns (신규) | 비즈니스 로직 |
### 3.2 통계 기반 패턴 분석
> **핵심 아이디어**: 사용자들이 이미 만든 화면들을 분석해서 "입고 화면은 보통 이렇게 생겼더라"를 알아내는 것
#### 예시: "입고페이지 만들어줘" 요청 시
**Step 1. 테이블 찾기**
```sql
-- "입고"라는 단어로 table_labels 검색
SELECT table_name, table_label FROM table_labels
WHERE table_label LIKE '%입고%';
-- 결과: inbound_mng (입고관리)
```
**Step 2. 이 테이블을 쓰는 기존 화면 찾기**
```sql
-- inbound_mng 테이블을 사용하는 화면들 조회
SELECT * FROM screen_definitions
WHERE table_name = 'inbound_mng';
-- 결과: 41개 화면 발견!
```
**Step 3. 41개 화면의 "공통점" 분석**
레이아웃 통계:
| 레이아웃 | 개수 | 비율 |
| -------------------------------- | ----- | -------- |
| split-panel (좌: 목록, 우: 상세) | 32개 | **78%** |
| table-list (목록만) | 6개 | 15% |
| form (폼만) | 3개 | 7% |
필드 사용 통계:
| 필드 | 사용 화면 수 | 비율 |
| ---------------------- | ------------ | -------- |
| inbound_no (입고번호) | 41개 | **100%** |
| inbound_date (입고일자)| 41개 | **100%** |
| item_code (품목코드) | 40개 | **98%** |
| qty (수량) | 39개 | **95%** |
| warehouse_code (창고) | 28개 | 68% |
| supplier_code (공급업체)| 25개 | 61% |
**Step 4. 확신도(Confidence)에 따라 다르게 처리**
AI는 통계 결과의 확신도에 따라 행동을 다르게 합니다:
| 확신도 | 기준 | AI 행동 | 예시 |
| ------ | ---- | ------- | ---- |
| **높음** | 90%+ | 자동 적용 | 필수 필드(입고번호, 일자) 자동 추가 |
| **중간** | 60~90% | 추천하며 확인 | "분할화면으로 만들까요? (78% 사용)" |
| **낮음** | 60% 미만 | 옵션 나열 | "창고 필드 추가할까요? (68%)" |
```
AI 판단 예시:
- 레이아웃: split-panel (78%) → "분할화면으로 생성합니다"
- 필수 필드: 입고번호, 입고일자 (100%) → 자동 추가
- 워크플로우: 입고→재고 (60%) → "💡 재고 자동 연동 추가할까요?"
- 선택 필드: 창고 (68%) → "추가 필드: [ ] 창고 [ ] 공급업체"
```
**핵심**: 확실한 것은 빠르게 처리하고, 애매한 것은 사용자에게 물어본다.
#### 비유: "맛집 추천 AI"와 같은 원리
| 맛집 추천 AI | 화면 생성 AI |
| ------------ | ------------ |
| "강남에서 점심 뭐 먹지?" | "입고페이지 만들어줘" |
| 강남 식당 1000개 리뷰 분석 | 기존 입고 화면 41개 분석 |
| "70%가 파스타집, 평균 1.5만원" | "78%가 분할화면, 100%가 입고번호 사용" |
| "파스타집 추천드릴까요?" | "분할화면으로 만들까요?" |
### 3.3 RAG 기반 동적 지식 주입 (핵심)
> **문제**: 모든 도메인 지식을 프롬프트에 넣으면 Context Window 초과
> **해결**: 필요한 지식만 검색해서 동적 주입
```
사용자: "입고페이지 만들어줘"
1. 키워드 추출: "입고"
2. workflow_patterns 검색 → "입고→재고 증가" 패턴 발견
3. LLM 프롬프트에 해당 패턴만 주입
4. 재고 증가 로직 포함된 화면 생성
```
#### 장점
| 장점 | 설명 |
| ------------- | ---------------------------------- |
| 토큰 절약 | 관련 패턴 1-2개만 주입 |
| 확장성 | 패턴 1000개여도 프롬프트 길이 동일 |
| 회사별 커스텀 | company_code로 회사별 패턴 적용 |
### 3.4 멀티테넌트 & Fallback 전략
회사마다 테이블명이 다르거나, 신규 회사라 기존 화면이 없을 수 있습니다.
**데이터 검색 우선순위:**
```
1순위: 해당 회사의 기존 화면 (가장 정확)
↓ 없거나 부족하면
2순위: 전체 회사의 익명화된 통계 (company_code 제외, 패턴만)
↓ 그래도 부족하면
3순위: vexplor 표준 템플릿 (기본 레이아웃 + 필수 필드)
```
**여러 테이블이 검색될 때:**
```
사용자: "입고페이지 만들어줘"
AI: "입고 관련 테이블이 3개 있습니다:
1. 자재입고관리 (material_inbound)
2. 제품입고관리 (product_inbound)
3. 반품입고관리 (return_inbound)
어떤 테이블로 만들까요?"
```
### 3.5 기존 시스템 분석 결과
**발견된 학습 가능 데이터:**
- `transferData` 액션: 14개 (발주→입고, 수주→출고 등)
- 제어관리 프레임워크: `dataflowControlService.ts` 존재
- 입고→재고 규칙: 아직 정의되지 않음 → AI가 생성하면 됨
---
## 4. 개발 범위
### 4.1 AI 담당 (신규)
| 작업 | 파일 | 우선순위 |
| ------------------------------------ | ----------------------------- | ------------ |
| AI 채팅 API | `aiRoutes.ts` | P0 |
| 화면 패턴 분석 | `screenAnalyzer.ts` | P0 |
| LLM 호출 | `llmService.ts` | P0 |
| **워크플로우 패턴 검색 (RAG)** | `workflowPatternService.ts` | **P0** |
| 채팅 UI | `AIChatPanel.tsx` | P0 |
| **workflow_patterns 테이블** | DB | **P0** |
```typescript
// 패턴 검색 핵심 로직
export async function searchWorkflowPatterns(userIntent: string, companyCode: string) {
const keywords = extractKeywords(userIntent);
return await query(`
SELECT * FROM workflow_patterns
WHERE intent_keywords && $1::text[]
AND (company_code = $2 OR company_code = '*')
ORDER BY priority DESC
LIMIT 3
`, [keywords, companyCode]);
}
```
### 4.2 vexplor 담당 (기존 보완)
| 작업 | 현재 상태 | 필요 작업 |
| -------------- | --------- | ------------------- |
| 화면 생성 API | ✅ 존재 | 문서화 |
| 워크플로우 API | ✅ 존재 | 문서화 |
| 제어관리 API | ✅ 존재 | AI 활용 가능 |
| table_labels | 부분 존재 | 주요 테이블 한글 라벨 |
| column_labels | 부분 존재 | web_type 보완 |
#### 4.2.1 table_labels가 필요한 이유
AI가 "입고"라는 단어로 테이블을 찾으려면 한글 라벨이 있어야 합니다.
**현재 상태:**
| table_name | table_label | AI 검색 |
| ---------- | ----------- | ------- |
| inbound_mng | **입고관리** | ✅ "입고" 검색 가능 |
| outbound_mng | **출고관리** | ✅ "출고" 검색 가능 |
| production_record | production_record | ❌ "생산" 검색 불가 |
| purchase_order_master | purchase_order_master | ❌ "발주" 검색 불가 |
**필요 작업**: 주요 업무 테이블에 한글 라벨 추가
```sql
UPDATE table_labels SET table_label = '생산실적' WHERE table_name = 'production_record';
UPDATE table_labels SET table_label = '발주관리' WHERE table_name = 'purchase_order_master';
```
#### 4.2.2 column_labels의 web_type이 필요한 이유
AI가 컬럼을 보고 **어떤 컴포넌트를 생성할지** 결정해야 합니다.
**현재 상태** (inbound_mng):
| column_name | column_label | web_type |
| ----------- | ------------ | -------- |
| inbound_date | 입고일 | **null** |
| inbound_qty | 입고수량 | **null** |
| item_code | 품목코드 | **null** |
**web_type이 null이면?** → AI가 모든 필드를 text-input으로 만들어버림
**web_type이 있으면:**
| column_name | web_type | AI가 생성할 컴포넌트 |
| ----------- | -------- | ------------------- |
| inbound_date | **date** | 📅 날짜 선택기 |
| inbound_qty | **number** | 🔢 숫자 입력 |
| item_code | **entity** | 🔍 품목 검색 팝업 |
| memo | **textarea** | 📝 여러 줄 텍스트 |
**필요 작업**: 주요 테이블 컬럼에 web_type 추가
```sql
UPDATE column_labels SET web_type = 'date' WHERE column_name LIKE '%_date';
UPDATE column_labels SET web_type = 'number' WHERE column_name LIKE '%_qty';
UPDATE column_labels SET web_type = 'entity' WHERE column_name LIKE '%_code' AND column_name != 'company_code';
UPDATE column_labels SET web_type = 'textarea' WHERE column_name = 'memo';
```
---
## 5. 데이터베이스 스키마 (AI 전용)
```sql
-- 핵심: 워크플로우 패턴 (RAG 지식 베이스)
CREATE TABLE workflow_patterns (
pattern_id SERIAL PRIMARY KEY,
category VARCHAR(50) NOT NULL, -- 'inventory', 'sales'
pattern_name VARCHAR(200) NOT NULL, -- '입고→재고 증가'
intent_keywords TEXT[] NOT NULL, -- ['입고', '자재입고', 'inbound']
description TEXT,
source_table_hint VARCHAR(100), -- 'inbound_mng'
target_table_hint VARCHAR(100), -- 'inventory_stock'
logic_template JSONB NOT NULL, -- 제어관리 생성 템플릿
company_code VARCHAR(20) DEFAULT '*',
priority INTEGER DEFAULT 100,
is_active BOOLEAN DEFAULT true
);
CREATE INDEX idx_workflow_patterns_keywords ON workflow_patterns USING GIN(intent_keywords);
-- 초기 데이터
INSERT INTO workflow_patterns (category, pattern_name, intent_keywords, description, source_table_hint, target_table_hint, logic_template) VALUES
('inventory', '입고→재고 증가',
ARRAY['입고', '구매입고', '자재입고', 'inbound'],
'입고 저장 시 재고 수량 증가',
'inbound_mng', 'inventory_stock',
'{"actionType": "upsert", "operation": "increment", "fieldMappings": [{"source": "item_code", "target": "item_code", "type": "key"}, {"source": "inbound_qty", "target": "qty", "type": "increment"}]}'::jsonb
),
('inventory', '출고→재고 감소',
ARRAY['출고', '판매출고', 'outbound'],
'출고 저장 시 재고 수량 감소',
'outbound_mng', 'inventory_stock',
'{"actionType": "update", "operation": "decrement", "fieldMappings": [{"source": "item_code", "target": "item_code", "type": "key"}, {"source": "outbound_qty", "target": "qty", "type": "decrement"}]}'::jsonb
);
-- 동의어 매핑 (P1)
CREATE TABLE keyword_mapping (
id SERIAL PRIMARY KEY,
keyword VARCHAR(100) NOT NULL,
table_name VARCHAR(100) NOT NULL,
company_code VARCHAR(20) DEFAULT '*'
);
-- AI 대화 이력 (P2)
CREATE TABLE ai_conversations (
id SERIAL PRIMARY KEY,
session_id VARCHAR(100) NOT NULL,
company_code VARCHAR(20) NOT NULL,
user_id VARCHAR(50) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE ai_messages (
id SERIAL PRIMARY KEY,
conversation_id INTEGER REFERENCES ai_conversations(id),
role VARCHAR(20) NOT NULL,
content TEXT NOT NULL,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
---
## 6. 개발 로드맵
### Phase 1: MVP
**AI:**
- 채팅 UI + LLM 연동
- 화면 패턴 분석
- workflow_patterns 테이블 + RAG 검색
**vexplor:**
- API 스펙 문서화 (screen, flow)
- 주요 테이블 한글 라벨 20개
### Phase 2: 워크플로우
**AI:**
- 자연어 → 워크플로우 변환
- dataflow_diagrams 자동 생성
**vexplor:**
- 워크플로우 API 문서화
### Phase 3: 고도화
- 대화형 수정 ("왼쪽 패널 넓혀줘")
- 멀티턴 컨텍스트
- 사용자 피드백 학습
---
## 7. vexplor 체크리스트
### 즉시 (P0)
- [ ] POST /api/screen/create 스펙
- [ ] POST /api/flow/definitions 스펙
- [ ] 화면 레이아웃 JSON 예시
### 1주 내 (P1)
- [ ] 주요 테이블 20개 한글 라벨
- inbound_mng, outbound_mng, inventory_stock
- item_info, customer_mng, supplier_mng
- sales_order_mng, purchase_order_mng
- [ ] column_labels web_type 보완
---
## 8. 성공 지표
| 지표 | 목표 |
| -------------------- | --------- |
| 화면 생성 성공률 | 90%+ |
| 평균 생성 시간 | 10초 이내 |
| 수정 없이 사용 | 70%+ |
| 워크플로우 자동 연결 | 80%+ |
@@ -0,0 +1,375 @@
# vexplor 쿠버네티스 자동 배포 가이드
## 개요
이 문서는 vexplor 프로젝트를 Gitea Actions를 통해 쿠버네티스 클러스터에 자동 배포하는 방법을 설명합니다.
**작성일**: 2024년 12월 22일
---
## 아키텍처
```
┌─────────────────────────────────────────────────────────────────┐
│ Gitea Repository │
│ g.wace.me/chpark/vexplor │
└─────────────────────┬───────────────────────────────────────────┘
│ push to main
┌─────────────────────────────────────────────────────────────────┐
│ Gitea Actions Runner │
│ 1. Checkout code │
│ 2. Build Docker images (frontend, backend) │
│ 3. Push to Harbor Registry │
│ 4. Deploy to Kubernetes │
└─────────────────────┬───────────────────────────────────────────┘
┌──────────┴──────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Harbor Registry │ │ Kubernetes (K8s) │
│ harbor.wace.me │ │ 112.168.212.142 │
└──────────────────┘ └──────────────────┘
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Frontend │ │ Backend │ │ Ingress │
│ :3000 │ │ :3001 │ │ Nginx │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└────────────────┴────────────────┘
┌─────────────────────┐
│ External Access │
│ v1.vexplor.com │
│ api.vexplor.com │
└─────────────────────┘
```
---
## 사전 요구사항
### 1. 쿠버네티스 클러스터
```bash
# 서버 정보
IP: 112.168.212.142
SSH: ssh -p 22 wace@112.168.212.142
K8s 버전: v1.28.15
```
### 2. Harbor 레지스트리 접근 권한
Harbor에 `vexplor` 프로젝트가 생성되어 있어야 합니다.
### 3. Gitea Repository Secrets
Gitea 저장소에 다음 Secrets를 설정해야 합니다:
| Secret 이름 | 설명 |
|------------|------|
| `HARBOR_USERNAME` | Harbor 사용자명 |
| `HARBOR_PASSWORD` | Harbor 비밀번호 |
| `KUBECONFIG` | base64 인코딩된 Kubernetes config |
---
## 초기 설정
### 1단계: 쿠버네티스 클러스터 접속
```bash
ssh -p 22 wace@112.168.212.142
```
### 2단계: Nginx Ingress Controller 설치
```bash
# Nginx Ingress Controller 설치 (baremetal용)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.5/deploy/static/provider/baremetal/deploy.yaml
# 설치 확인
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
```
### 3단계: Local Path Provisioner 설치 (PVC용)
```bash
# Local Path Provisioner 설치
kubectl apply -f k8s/local-path-provisioner.yaml
# 설치 확인
kubectl get pods -n local-path-storage
kubectl get storageclass
```
### 4단계: Cert-Manager 설치 (SSL 인증서용)
```bash
# Cert-Manager 설치
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
# 설치 확인
kubectl get pods -n cert-manager
# ClusterIssuer 생성 (Let's Encrypt)
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@vexplor.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
EOF
```
### 5단계: vexplor Secret 생성
```bash
# Secret 템플릿을 복사하여 실제 값으로 수정
cp k8s/vexplor-secret.yaml.template k8s/vexplor-secret.yaml
# 값 수정 후 적용
kubectl apply -f k8s/vexplor-secret.yaml
```
### 6단계: Gitea Secrets 설정
1. Gitea 저장소로 이동: https://g.wace.me/chpark/vexplor
2. Settings > Secrets > Actions 메뉴로 이동
3. 다음 Secrets 추가:
#### HARBOR_USERNAME
Harbor 로그인 사용자명
#### HARBOR_PASSWORD
Harbor 로그인 비밀번호
#### KUBECONFIG
```bash
# 쿠버네티스 서버에서 실행
cat ~/.kube/config | base64 -w 0
```
출력된 값을 KUBECONFIG secret으로 등록
---
## 배포 트리거
### 자동 배포 (Push)
다음 경로의 파일이 변경되어 `main` 브랜치에 push되면 자동으로 배포됩니다:
- `backend-node/**`
- `frontend/**`
- `docker/**`
- `k8s/**`
- `.gitea/workflows/deploy.yml`
### 수동 배포
1. Gitea 저장소 > Actions 탭으로 이동
2. "Deploy vexplor" 워크플로우 선택
3. "Run workflow" 버튼 클릭
---
## 파일 구조
```
vexplor/
├── .gitea/
│ └── workflows/
│ └── deploy.yml # Gitea Actions 워크플로우
├── docker/
│ └── deploy/
│ ├── backend.Dockerfile # 백엔드 배포용 Dockerfile
│ └── frontend.Dockerfile # 프론트엔드 배포용 Dockerfile
├── k8s/
│ ├── namespace.yaml # 네임스페이스 정의
│ ├── vexplor-config.yaml # ConfigMap
│ ├── vexplor-secret.yaml.template # Secret 템플릿
│ ├── vexplor-backend-deployment.yaml # 백엔드 Deployment/Service/PVC
│ ├── vexplor-frontend-deployment.yaml # 프론트엔드 Deployment/Service
│ ├── vexplor-ingress.yaml # Ingress 설정
│ ├── local-path-provisioner.yaml # 스토리지 프로비저너
│ └── ingress-nginx.yaml # Ingress 컨트롤러 패치
└── docs/
└── KUBERNETES_DEPLOYMENT_GUIDE.md # 이 문서
```
---
## 운영 명령어
### 상태 확인
```bash
# 전체 리소스 확인
kubectl get all -n vexplor
# Pod 상태 확인
kubectl get pods -n vexplor -o wide
# 로그 확인
kubectl logs -f deployment/vexplor-backend -n vexplor
kubectl logs -f deployment/vexplor-frontend -n vexplor
# Pod 상세 정보
kubectl describe pod <pod-name> -n vexplor
```
### 수동 배포/롤백
```bash
# 이미지 업데이트
kubectl set image deployment/vexplor-backend \
vexplor-backend=harbor.wace.me/vexplor/vexplor-backend:v20241222-120000-abc1234 \
-n vexplor
# 롤아웃 상태 확인
kubectl rollout status deployment/vexplor-backend -n vexplor
# 롤백
kubectl rollout undo deployment/vexplor-backend -n vexplor
kubectl rollout undo deployment/vexplor-frontend -n vexplor
# 히스토리 확인
kubectl rollout history deployment/vexplor-backend -n vexplor
```
### 스케일링
```bash
# 레플리카 수 조정
kubectl scale deployment/vexplor-backend --replicas=3 -n vexplor
kubectl scale deployment/vexplor-frontend --replicas=3 -n vexplor
```
### Pod 재시작
```bash
# Deployment 재시작 (롤링 업데이트)
kubectl rollout restart deployment/vexplor-backend -n vexplor
kubectl rollout restart deployment/vexplor-frontend -n vexplor
```
---
## 문제 해결
### Pod이 Pending 상태일 때
```bash
# Pod 이벤트 확인
kubectl describe pod <pod-name> -n vexplor
# 노드 리소스 확인
kubectl describe node
kubectl top nodes
```
### ImagePullBackOff 오류
```bash
# Harbor Secret 확인
kubectl get secret harbor-registry -n vexplor -o yaml
# Secret 재생성
kubectl delete secret harbor-registry -n vexplor
kubectl create secret docker-registry harbor-registry \
--docker-server=192.168.1.100:5001 \
--docker-username=<username> \
--docker-password=<password> \
-n vexplor
```
### Ingress가 작동하지 않을 때
```bash
# Ingress 상태 확인
kubectl get ingress -n vexplor
kubectl describe ingress vexplor-ingress -n vexplor
# Ingress Controller 로그
kubectl logs -f deployment/ingress-nginx-controller -n ingress-nginx
```
### SSL 인증서 문제
```bash
# Certificate 상태 확인
kubectl get certificate -n vexplor
kubectl describe certificate vexplor-tls -n vexplor
# Cert-Manager 로그
kubectl logs -f deployment/cert-manager -n cert-manager
```
---
## 네트워크 설정
### 방화벽 포트 개방
쿠버네티스 서버에서 다음 포트가 개방되어야 합니다:
| 포트 | 용도 |
|-----|------|
| 30080 | HTTP (Ingress NodePort) |
| 30443 | HTTPS (Ingress NodePort) |
| 6443 | Kubernetes API |
### DNS 설정
다음 도메인이 쿠버네티스 서버 IP를 가리키도록 설정:
- `v1.vexplor.com` → 112.168.212.142
- `api.vexplor.com` → 112.168.212.142
---
## 환경 변수
### Backend 환경 변수
| 변수 | 설명 | 소스 |
|-----|------|-----|
| `NODE_ENV` | 환경 (production) | ConfigMap |
| `PORT` | 서버 포트 (3001) | ConfigMap |
| `DATABASE_URL` | PostgreSQL 연결 문자열 | Secret |
| `JWT_SECRET` | JWT 서명 키 | Secret |
| `JWT_EXPIRES_IN` | JWT 만료 시간 | ConfigMap |
| `CORS_ORIGIN` | CORS 허용 도메인 | ConfigMap |
### Frontend 환경 변수
| 변수 | 설명 | 소스 |
|-----|------|-----|
| `NODE_ENV` | 환경 (production) | ConfigMap |
| `NEXT_PUBLIC_API_URL` | 클라이언트 API URL | ConfigMap |
| `SERVER_API_URL` | SSR용 내부 API URL | Deployment |
---
## 참고 자료
- [Kubernetes 공식 문서](https://kubernetes.io/docs/)
- [Gitea Actions 문서](https://docs.gitea.com/usage/actions/overview)
- [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/)
- [Cert-Manager](https://cert-manager.io/docs/)
- [Harbor Registry](https://goharbor.io/docs/)