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:
@@ -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/)
|
||||
|
||||
Reference in New Issue
Block a user