diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index ec48126e..e5c8d001 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,19 +1,16 @@ -# Gitea Actions Workflow - vexplor 자동 배포 +# Gitea Actions Workflow - vexplor 이미지 빌드 & Harbor Push # -# 환경 변수: -# - GITEA_DOMAIN: g.wace.me -# - HARBOR_REGISTRY: harbor.wace.me -# - K8S_NAMESPACE: vexplor +# 동작 방식: +# 1. main 브랜치에 push 시 자동 실행 +# 2. Docker 이미지 빌드 (Backend, Frontend) +# 3. Harbor 레지스트리에 Push +# 4. 공장 서버의 Watchtower가 새 이미지 감지 후 자동 업데이트 # # 필수 Secrets (Repository Settings > Secrets): # - HARBOR_USERNAME: Harbor 사용자명 # - HARBOR_PASSWORD: Harbor 비밀번호 -# - K8S_SSH_KEY: base64로 인코딩된 SSH 비밀키 (쿠버네티스 서버 접속용) -# -# Application Secrets: -# - k8s/vexplor-secret.yaml 파일에서 관리 -name: Deploy vexplor +name: Build and Push Images on: push: @@ -24,44 +21,35 @@ on: - "backend-node/**" - "frontend/**" - "docker/**" - - "k8s/**" - ".gitea/workflows/deploy.yml" - workflow_dispatch: + paths-ignore: + - "**.md" + - "deploy/**" + - "k8s/**" + workflow_dispatch: # 수동 실행도 가능 env: GITEA_DOMAIN: g.wace.me HARBOR_REGISTRY: localhost:5001 - HARBOR_REGISTRY_K8S: harbor.wace.me HARBOR_REGISTRY_EXTERNAL: harbor.wace.me HARBOR_PROJECT: speefox_vexplor - K8S_NAMESPACE: vexplor - - # 쿠버네티스 서버 SSH 접속 정보 - K8S_SSH_HOST: 112.168.212.142 - K8S_SSH_PORT: 22 - K8S_SSH_USER: wace # Frontend 빌드 환경 변수 NEXT_PUBLIC_API_URL: "https://api.vexplor.com/api" NEXT_PUBLIC_ENV: "production" - INTERNAL_API_URL: "http://vexplor-backend-service:3001" # Frontend 설정 FRONTEND_IMAGE_NAME: vexplor-frontend - FRONTEND_DEPLOYMENT_NAME: vexplor-frontend - FRONTEND_CONTAINER_NAME: vexplor-frontend FRONTEND_BUILD_CONTEXT: frontend FRONTEND_DOCKERFILE_PATH: docker/deploy/frontend.Dockerfile # Backend 설정 BACKEND_IMAGE_NAME: vexplor-backend - BACKEND_DEPLOYMENT_NAME: vexplor-backend - BACKEND_CONTAINER_NAME: vexplor-backend BACKEND_BUILD_CONTEXT: backend-node BACKEND_DOCKERFILE_PATH: docker/deploy/backend.Dockerfile jobs: - build-and-deploy: + build-and-push: runs-on: ubuntu-24.04 steps: @@ -79,7 +67,7 @@ jobs: run: | echo "필수 도구 설치 중..." apt-get update -qq - apt-get install -y git curl ca-certificates gnupg openssh-client + apt-get install -y git curl ca-certificates gnupg # Docker 클라이언트 설치 install -m 0755 -d /etc/apt/keyrings @@ -94,7 +82,6 @@ jobs: echo "설치 완료:" git --version - ssh -V docker --version export DOCKER_HOST=unix:///var/run/docker.sock @@ -120,13 +107,13 @@ jobs: # Frontend 이미지 echo "FRONTEND_FULL_IMAGE=${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${FRONTEND_IMAGE_NAME}" >> $GITHUB_ENV - echo "FRONTEND_FULL_IMAGE_K8S=${HARBOR_REGISTRY_K8S}/${HARBOR_PROJECT}/${FRONTEND_IMAGE_NAME}" >> $GITHUB_ENV # Backend 이미지 echo "BACKEND_FULL_IMAGE=${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${BACKEND_IMAGE_NAME}" >> $GITHUB_ENV - echo "BACKEND_FULL_IMAGE_K8S=${HARBOR_REGISTRY_K8S}/${HARBOR_PROJECT}/${BACKEND_IMAGE_NAME}" >> $GITHUB_ENV + echo "==========================================" echo "빌드 태그: ${IMAGE_TAG}" + echo "==========================================" # Harbor 로그인 - name: Login to Harbor @@ -144,7 +131,9 @@ jobs: # Backend 빌드 및 푸시 - name: Build and Push Backend image run: | - echo "Backend 이미지 빌드 및 푸시..." + echo "==========================================" + echo "Backend 이미지 빌드 시작..." + echo "==========================================" export DOCKER_HOST=unix:///var/run/docker.sock cd /workspace/source @@ -154,14 +143,22 @@ jobs: -f ${BACKEND_DOCKERFILE_PATH} \ ${BACKEND_BUILD_CONTEXT} + echo "Backend 이미지 푸시..." docker push ${BACKEND_FULL_IMAGE}:${IMAGE_TAG} docker push ${BACKEND_FULL_IMAGE}:latest - echo "Backend 푸시 완료" + + echo "==========================================" + echo "Backend 푸시 완료!" + echo " - ${BACKEND_FULL_IMAGE}:${IMAGE_TAG}" + echo " - ${BACKEND_FULL_IMAGE}:latest" + echo "==========================================" # Frontend 빌드 및 푸시 - name: Build and Push Frontend image run: | - echo "Frontend 이미지 빌드 및 푸시..." + echo "==========================================" + echo "Frontend 이미지 빌드 시작..." + echo "==========================================" export DOCKER_HOST=unix:///var/run/docker.sock cd /workspace/source @@ -176,175 +173,40 @@ jobs: --build-arg NEXT_PUBLIC_API_URL="${NEXT_PUBLIC_API_URL}" \ ${FRONTEND_BUILD_CONTEXT} + echo "Frontend 이미지 푸시..." docker push ${FRONTEND_FULL_IMAGE}:${IMAGE_TAG} docker push ${FRONTEND_FULL_IMAGE}:latest - echo "Frontend 푸시 완료" + + echo "==========================================" + echo "Frontend 푸시 완료!" + echo " - ${FRONTEND_FULL_IMAGE}:${IMAGE_TAG}" + echo " - ${FRONTEND_FULL_IMAGE}:latest" + echo "==========================================" - # SSH 키 설정 (쿠버네티스 서버 접속용) - - name: Setup SSH Key - env: - SSH_KEY_CONTENT: ${{ secrets.K8S_SSH_KEY }} - run: | - echo "SSH 키 설정..." - - if [ -z "${SSH_KEY_CONTENT}" ]; then - echo "K8S_SSH_KEY secret이 설정되지 않았습니다!" - exit 1 - fi - - mkdir -p ~/.ssh - echo "${SSH_KEY_CONTENT}" | base64 -d > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - - # known_hosts에 쿠버네티스 서버 추가 - ssh-keyscan -p ${K8S_SSH_PORT} ${K8S_SSH_HOST} >> ~/.ssh/known_hosts 2>/dev/null - - # SSH 연결 테스트 - echo "SSH 연결 테스트..." - ssh -o StrictHostKeyChecking=no -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} "echo 'SSH 연결 성공'" - echo "SSH 키 설정 완료" - - # k8s 매니페스트 파일을 쿠버네티스 서버로 전송 - - name: Transfer k8s manifests - run: | - echo "k8s 매니페스트 파일 전송..." - cd /workspace/source - - # 쿠버네티스 서버에 디렉토리 생성 - ssh -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} "mkdir -p ~/vexplor-deploy/k8s" - - # k8s 파일 전송 - scp -P ${K8S_SSH_PORT} -r k8s/* ${K8S_SSH_USER}@${K8S_SSH_HOST}:~/vexplor-deploy/k8s/ - - echo "매니페스트 파일 전송 완료" - - # Kubernetes 배포 (SSH를 통해 원격 실행) - - name: Deploy to Kubernetes - env: - HARBOR_USER: ${{ secrets.HARBOR_USERNAME }} - HARBOR_PASS: ${{ secrets.HARBOR_PASSWORD }} - run: | - echo "Kubernetes 배포 시작 (SSH 원격 실행)..." - - # SSH를 통해 쿠버네티스 서버에서 kubectl 명령 실행 - ssh -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} << 'DEPLOY_SCRIPT' - set -e - cd ~/vexplor-deploy - - echo "네임스페이스 확인..." - kubectl apply -f k8s/namespace.yaml - - echo "ConfigMap 적용..." - kubectl apply -f k8s/vexplor-config.yaml -n vexplor - - # Secret 적용 (존재하는 경우에만) - if [ -f k8s/vexplor-secret.yaml ]; then - echo "Secret 적용..." - kubectl apply -f k8s/vexplor-secret.yaml -n vexplor - fi - - echo "네임스페이스 및 ConfigMap 적용 완료" - DEPLOY_SCRIPT - - # Harbor Registry Secret 생성 (별도로 처리 - 환경변수 사용) - echo "Harbor Registry Secret 확인..." - ssh -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} "kubectl get secret harbor-registry -n vexplor" > /dev/null 2>&1 || \ - ssh -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} "kubectl create secret docker-registry harbor-registry \ - --docker-server=${HARBOR_REGISTRY_K8S} \ - --docker-username=${HARBOR_USER} \ - --docker-password='${HARBOR_PASS}' \ - -n vexplor" - echo "Harbor Registry Secret 확인 완료" - - # Backend 배포 - echo "Backend 배포..." - ssh -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} << BACKEND_DEPLOY - set -e - cd ~/vexplor-deploy - kubectl apply -f k8s/vexplor-backend-deployment.yaml -n vexplor - - echo "Backend 이미지 업데이트..." - kubectl set image deployment/${BACKEND_DEPLOYMENT_NAME} \ - ${BACKEND_CONTAINER_NAME}=${HARBOR_REGISTRY_K8S}/${HARBOR_PROJECT}/${BACKEND_IMAGE_NAME}:latest \ - -n vexplor || true - kubectl rollout restart deployment/${BACKEND_DEPLOYMENT_NAME} -n vexplor - - echo "Backend Rolling Update 진행 중..." - kubectl rollout status deployment/${BACKEND_DEPLOYMENT_NAME} -n vexplor --timeout=5m - echo "Backend 배포 완료" - BACKEND_DEPLOY - - # Frontend 배포 - echo "Frontend 배포..." - ssh -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} << FRONTEND_DEPLOY - set -e - cd ~/vexplor-deploy - kubectl apply -f k8s/vexplor-frontend-deployment.yaml -n vexplor - - echo "Frontend 이미지 업데이트..." - kubectl set image deployment/${FRONTEND_DEPLOYMENT_NAME} \ - ${FRONTEND_CONTAINER_NAME}=${HARBOR_REGISTRY_K8S}/${HARBOR_PROJECT}/${FRONTEND_IMAGE_NAME}:latest \ - -n vexplor || true - kubectl rollout restart deployment/${FRONTEND_DEPLOYMENT_NAME} -n vexplor - - echo "Frontend Rolling Update 진행 중..." - kubectl rollout status deployment/${FRONTEND_DEPLOYMENT_NAME} -n vexplor --timeout=5m - echo "Frontend 배포 완료" - FRONTEND_DEPLOY - - # Ingress 배포 - echo "Ingress 배포..." - ssh -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} "cd ~/vexplor-deploy && kubectl apply -f k8s/vexplor-ingress.yaml -n vexplor" - - echo "전체 배포 완료!" - - # 배포 검증 - - name: Verify deployment - run: | - echo "배포 검증..." - ssh -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} << 'VERIFY_SCRIPT' - echo "" - echo "Backend 상태:" - kubectl get deployment vexplor-backend -n vexplor - kubectl get pods -l app=vexplor-backend -n vexplor - echo "" - echo "Frontend 상태:" - kubectl get deployment vexplor-frontend -n vexplor - kubectl get pods -l app=vexplor-frontend -n vexplor - echo "" - echo "Services:" - kubectl get svc -n vexplor - echo "" - echo "Ingress:" - kubectl get ingress -n vexplor - echo "" - echo "검증 완료" - VERIFY_SCRIPT - - # 배포 요약 - - name: Deployment summary + # 빌드 완료 요약 + - name: Build summary if: success() run: | + echo "" echo "==========================================" - echo "배포가 성공적으로 완료되었습니다!" + echo " 이미지 빌드 & Push 완료!" echo "==========================================" + echo "" echo "빌드 버전: ${IMAGE_TAG}" - echo "Frontend: https://v1.vexplor.com" - echo "Backend API: https://api.vexplor.com" + echo "" + echo "푸시된 이미지:" + echo " - Backend: ${HARBOR_REGISTRY_EXTERNAL}/${HARBOR_PROJECT}/${BACKEND_IMAGE_NAME}:latest" + echo " - Frontend: ${HARBOR_REGISTRY_EXTERNAL}/${HARBOR_PROJECT}/${FRONTEND_IMAGE_NAME}:latest" + echo "" + echo "다음 단계:" + echo " - 공장 서버의 Watchtower가 자동으로 새 이미지를 감지합니다" + echo " - 또는 수동 업데이트: docker compose pull && docker compose up -d" + echo "" echo "==========================================" - # 실패 시 롤백 - - name: Rollback on failure - if: failure() - run: | - echo "배포 실패! 이전 버전으로 롤백..." - ssh -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} "kubectl rollout undo deployment/vexplor-backend -n vexplor" || true - ssh -p ${K8S_SSH_PORT} ${K8S_SSH_USER}@${K8S_SSH_HOST} "kubectl rollout undo deployment/vexplor-frontend -n vexplor" || true - # Harbor 로그아웃 - name: Logout from Harbor if: always() run: | export DOCKER_HOST=unix:///var/run/docker.sock docker logout ${HARBOR_REGISTRY} || true - diff --git a/.gitignore b/.gitignore index 5b2b1f56..24814953 100644 --- a/.gitignore +++ b/.gitignore @@ -291,4 +291,6 @@ uploads/ *.hwp *.hwpx -claude.md \ No newline at end of file +claude.md + +.cursor/mcp.json \ No newline at end of file diff --git a/deploy/customers/README.md b/deploy/customers/README.md new file mode 100644 index 00000000..8b55214b --- /dev/null +++ b/deploy/customers/README.md @@ -0,0 +1,115 @@ +# 고객사별 환경 변수 관리 + +## 개요 + +이 폴더는 각 고객사(업체)별 환경 변수 설정을 **참고용**으로 관리합니다. + +**중요:** 실제 비밀번호는 이 파일에 저장하지 마세요. 템플릿으로만 사용합니다. + +--- + +## 고객사 목록 + +| 고객사 | 파일 | 배포 형태 | 상태 | +| :--- | :--- | :--- | :--- | +| 스피폭스 | `spifox.env` | 온프레미스 (공장 서버) | 진행 중 | +| 엔키드 | `enkid.env` | 온프레미스 (공장 서버) | 예정 | + +--- + +## 신규 고객사 추가 절차 + +### 1단계: 환경 변수 파일 생성 + +```bash +# 기존 파일 복사 +cp spifox.env newcustomer.env + +# 수정 +nano newcustomer.env +``` + +필수 수정 항목: +- `COMPANY_CODE`: 고유한 회사 코드 (예: NEWCO) +- `SERVER_IP`: 고객사 서버 IP +- `DB_PASSWORD`: 고유한 비밀번호 +- `JWT_SECRET`: 고유한 시크릿 키 + +### 2단계: 데이터베이스에 회사 등록 + +```sql +-- company_info 테이블에 추가 +INSERT INTO company_info (company_code, company_name, status) +VALUES ('NEWCO', '신규고객사', 'ACTIVE'); +``` + +### 3단계: 관리자 계정 생성 + +```sql +-- user_info 테이블에 관리자 추가 +INSERT INTO user_info (user_id, user_name, company_code, role) +VALUES ('newco_admin', '신규고객사 관리자', 'NEWCO', 'COMPANY_ADMIN'); +``` + +### 4단계: 고객사 서버에 배포 + +```bash +# 고객사 서버에 SSH 접속 +ssh user@customer-server + +# 설치 폴더 생성 +sudo mkdir -p /opt/vexplor +cd /opt/vexplor + +# docker-compose.yml 복사 (deploy/onpremise/에서) +# .env 파일 복사 및 수정 + +# 서비스 시작 +docker compose up -d +``` + +--- + +## 환경 변수 설명 + +| 변수 | 설명 | 예시 | +| :--- | :--- | :--- | +| `COMPANY_CODE` | 회사 고유 코드 (멀티테넌시) | `SPIFOX`, `ENKID` | +| `SERVER_IP` | 서버의 실제 IP | `192.168.0.100` | +| `DB_PASSWORD` | DB 비밀번호 | (고객사별 고유) | +| `JWT_SECRET` | JWT 토큰 시크릿 | (고객사별 고유) | +| `IMAGE_TAG` | Docker 이미지 버전 | `latest`, `v1.0.0` | + +--- + +## 보안 주의사항 + +1. **비밀번호**: 이 폴더의 파일에는 실제 비밀번호를 저장하지 마세요 +2. **Git**: `.env` 파일이 커밋되지 않도록 `.gitignore` 확인 +3. **고객사별 격리**: 각 고객사는 별도 서버, 별도 DB로 완전 격리 +4. **키 관리**: JWT_SECRET은 고객사별로 반드시 다르게 설정 + +--- + +## 구조 다이어그램 + +``` +[Harbor (이미지 저장소)] + │ + │ docker pull + ↓ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ 스피폭스 공장 │ │ 엔키드 공장 │ │ 신규 고객사 │ +│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ +│ │ Vexplor │ │ │ │ Vexplor │ │ │ │ Vexplor │ │ +│ │ SPIFOX │ │ │ │ ENKID │ │ │ │ NEWCO │ │ +│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ +│ │ │ │ │ │ +│ [독립 DB] │ │ [독립 DB] │ │ [독립 DB] │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + +* 각 공장은 완전히 독립적으로 운영 +* 같은 Docker 이미지 사용, .env만 다름 +* 데이터는 절대 섞이지 않음 (물리적 격리) +``` + diff --git a/deploy/customers/enkid.env b/deploy/customers/enkid.env new file mode 100644 index 00000000..3a9e84df --- /dev/null +++ b/deploy/customers/enkid.env @@ -0,0 +1,36 @@ +# ============================================ +# 엔키드(ENKID) 공장 서버 환경 변수 +# ============================================ +# 이 파일을 엔키드 공장 서버의 /opt/vexplor/.env로 복사 + +# 회사 정보 +COMPANY_CODE=ENKID + +# 서버 정보 (실제 서버 IP로 변경 필요) +SERVER_IP=10.0.0.50 + +# 데이터베이스 +DB_USER=vexplor +DB_PASSWORD=enkid_secure_password_here +DB_NAME=vexplor +DB_PORT=5432 + +# 백엔드 +BACKEND_PORT=3001 +JWT_SECRET=enkid_jwt_secret_minimum_32_characters +JWT_EXPIRES_IN=24h +LOG_LEVEL=info + +# 프론트엔드 +FRONTEND_PORT=80 + +# Harbor 레지스트리 +HARBOR_USER=enkid_harbor_user +HARBOR_PASSWORD=enkid_harbor_password + +# 이미지 태그 +IMAGE_TAG=latest + +# Watchtower (1시간마다 업데이트 확인) +UPDATE_INTERVAL=3600 + diff --git a/deploy/customers/spifox.env b/deploy/customers/spifox.env new file mode 100644 index 00000000..ab7d6004 --- /dev/null +++ b/deploy/customers/spifox.env @@ -0,0 +1,36 @@ +# ============================================ +# 스피폭스(SPIFOX) 공장 서버 환경 변수 +# ============================================ +# 이 파일을 스피폭스 공장 서버의 /opt/vexplor/.env로 복사 + +# 회사 정보 +COMPANY_CODE=SPIFOX + +# 서버 정보 (실제 서버 IP로 변경 필요) +SERVER_IP=192.168.0.100 + +# 데이터베이스 +DB_USER=vexplor +DB_PASSWORD=spifox_secure_password_here +DB_NAME=vexplor +DB_PORT=5432 + +# 백엔드 +BACKEND_PORT=3001 +JWT_SECRET=spifox_jwt_secret_minimum_32_characters +JWT_EXPIRES_IN=24h +LOG_LEVEL=info + +# 프론트엔드 +FRONTEND_PORT=80 + +# Harbor 레지스트리 +HARBOR_USER=spifox_harbor_user +HARBOR_PASSWORD=spifox_harbor_password + +# 이미지 태그 +IMAGE_TAG=latest + +# Watchtower (1시간마다 업데이트 확인) +UPDATE_INTERVAL=3600 + diff --git a/deploy/onpremise/README.md b/deploy/onpremise/README.md new file mode 100644 index 00000000..76cad490 --- /dev/null +++ b/deploy/onpremise/README.md @@ -0,0 +1,321 @@ +# Vexplor 온프레미스(공장) 배포 가이드 + +## 개요 + +이 가이드는 Vexplor를 **공장 내부 서버(온프레미스)**에 배포하는 방법을 설명합니다. + +**Watchtower**를 사용하여 Harbor에 새 이미지가 푸시되면 **자동으로 업데이트**됩니다. + +--- + +## 사전 요구사항 + +### 서버 요구사항 + +| 항목 | 최소 사양 | 권장 사양 | +| :--- | :--- | :--- | +| OS | Ubuntu 20.04+ | Ubuntu 22.04 LTS | +| CPU | 4 Core | 8 Core | +| RAM | 8 GB | 16 GB | +| Disk | 50 GB | 100 GB SSD | +| Network | Harbor 접근 가능 | - | + +### 필수 소프트웨어 + +```bash +# Docker 설치 확인 +docker --version # 20.10 이상 + +# Docker Compose 설치 확인 +docker compose version # v2.0 이상 +``` + +--- + +## 1단계: 초기 설정 + +### 1.1 배포 폴더 생성 + +```bash +# 배포 폴더 생성 +sudo mkdir -p /opt/vexplor +cd /opt/vexplor + +# 파일 복사 (또는 git clone) +# deploy/onpremise/ 폴더의 내용을 복사 +``` + +### 1.2 환경 변수 설정 + +```bash +# 예제 파일 복사 +cp env.example .env + +# 편집 +nano .env +``` + +**필수 수정 항목:** + +```bash +# 서버 IP (이 서버의 실제 IP) +SERVER_IP=192.168.0.100 + +# 회사 코드 +COMPANY_CODE=SPIFOX + +# DB 비밀번호 (강력한 비밀번호 설정) +DB_PASSWORD=MySecurePassword123! + +# JWT 시크릿 (32자 이상) +JWT_SECRET=your-super-secret-jwt-key-minimum-32-chars + +# Harbor 인증 정보 +HARBOR_USER=your_username +HARBOR_PASSWORD=your_password +``` + +### 1.3 Harbor 레지스트리 로그인 + +Watchtower가 이미지를 당겨올 수 있도록 Docker 로그인이 필요합니다. + +```bash +# Harbor 로그인 +docker login harbor.wace.me + +# Username: (입력) +# Password: (입력) + +# 로그인 성공 확인 +cat ~/.docker/config.json +``` + +--- + +## 2단계: 서비스 실행 + +### 2.1 서비스 시작 + +```bash +cd /opt/vexplor + +# 이미지 다운로드 & 실행 +docker compose up -d + +# 상태 확인 +docker compose ps +``` + +### 2.2 정상 동작 확인 + +```bash +# 모든 컨테이너 Running 상태 확인 +docker compose ps + +# 로그 확인 +docker compose logs -f + +# 개별 서비스 로그 +docker compose logs -f backend +docker compose logs -f frontend +docker compose logs -f watchtower +``` + +### 2.3 웹 접속 테스트 + +``` +프론트엔드: http://SERVER_IP:80 +백엔드 API: http://SERVER_IP:3001/health +``` + +--- + +## 3단계: 자동 업데이트 확인 + +### Watchtower 동작 확인 + +```bash +# Watchtower 로그 확인 +docker compose logs -f watchtower +``` + +**정상 로그 예시:** + +``` +watchtower | time="2024-12-28T10:00:00+09:00" level=info msg="Checking for updates..." +watchtower | time="2024-12-28T10:00:05+09:00" level=info msg="Found new image harbor.wace.me/vexplor/vexplor-backend:latest" +watchtower | time="2024-12-28T10:00:10+09:00" level=info msg="Stopping container vexplor-backend" +watchtower | time="2024-12-28T10:00:15+09:00" level=info msg="Starting container vexplor-backend" +``` + +### 업데이트 주기 변경 + +```bash +# .env 파일에서 변경 +UPDATE_INTERVAL=3600 # 1시간마다 확인 + +# 변경 후 watchtower 재시작 +docker compose restart watchtower +``` + +--- + +## 운영 가이드 + +### 서비스 관리 명령어 + +```bash +# 모든 서비스 상태 확인 +docker compose ps + +# 모든 서비스 중지 +docker compose stop + +# 모든 서비스 시작 +docker compose start + +# 모든 서비스 재시작 +docker compose restart + +# 모든 서비스 삭제 (데이터 유지) +docker compose down + +# 모든 서비스 삭제 + 볼륨 삭제 (주의: 데이터 삭제됨!) +docker compose down -v +``` + +### 로그 확인 + +```bash +# 전체 로그 (실시간) +docker compose logs -f + +# 특정 서비스 로그 +docker compose logs -f backend +docker compose logs -f frontend +docker compose logs -f database + +# 최근 100줄만 +docker compose logs --tail=100 backend +``` + +### 수동 업데이트 (긴급 시) + +자동 업데이트를 기다리지 않고 즉시 업데이트하려면: + +```bash +# 최신 이미지 다운로드 +docker compose pull + +# 재시작 +docker compose up -d +``` + +### 특정 버전으로 롤백 + +```bash +# .env 파일에서 버전 지정 +IMAGE_TAG=v1.0.0 + +# 재시작 +docker compose up -d +``` + +--- + +## 백업 가이드 + +### DB 백업 + +```bash +# 백업 디렉토리 생성 +mkdir -p /opt/vexplor/backups + +# PostgreSQL 백업 +docker compose exec database pg_dump -U vexplor vexplor > /opt/vexplor/backups/backup_$(date +%Y%m%d_%H%M%S).sql +``` + +### 업로드 파일 백업 + +```bash +# 볼륨 위치 확인 +docker volume inspect vexplor_backend_uploads + +# 또는 직접 복사 +docker cp vexplor-backend:/app/uploads /opt/vexplor/backups/uploads_$(date +%Y%m%d) +``` + +### 자동 백업 스크립트 (Cron) + +```bash +# crontab 편집 +crontab -e + +# 매일 새벽 3시 DB 백업 +0 3 * * * docker compose -f /opt/vexplor/docker-compose.yml exec -T database pg_dump -U vexplor vexplor > /opt/vexplor/backups/backup_$(date +\%Y\%m\%d).sql +``` + +--- + +## 문제 해결 + +### 컨테이너가 시작되지 않음 + +```bash +# 로그 확인 +docker compose logs backend + +# 일반적인 원인: +# 1. 환경 변수 누락 → .env 파일 확인 +# 2. 포트 충돌 → netstat -tlnp | grep 3001 +# 3. 메모리 부족 → free -h +``` + +### DB 연결 실패 + +```bash +# DB 컨테이너 상태 확인 +docker compose logs database + +# DB 직접 접속 테스트 +docker compose exec database psql -U vexplor -d vexplor -c "SELECT 1" +``` + +### Watchtower가 업데이트하지 않음 + +```bash +# Watchtower 로그 확인 +docker compose logs watchtower + +# Harbor 인증 확인 +docker pull harbor.wace.me/vexplor/vexplor-backend:latest + +# 라벨 확인 (라벨이 있는 컨테이너만 업데이트) +docker inspect vexplor-backend | grep watchtower +``` + +### 디스크 공간 부족 + +```bash +# 사용하지 않는 이미지/컨테이너 정리 +docker system prune -a + +# 오래된 로그 정리 +docker compose logs --tail=0 backend # 로그 초기화 +``` + +--- + +## 보안 권장사항 + +1. **방화벽 설정**: 필요한 포트(80, 3001)만 개방 +2. **SSL/TLS**: Nginx 리버스 프록시 + Let's Encrypt 적용 권장 +3. **정기 백업**: 최소 주 1회 DB 백업 +4. **로그 모니터링**: 비정상 접근 감시 + +--- + +## 연락처 + +배포 관련 문의: [담당자 이메일] + diff --git a/deploy/onpremise/docker-compose.yml b/deploy/onpremise/docker-compose.yml new file mode 100644 index 00000000..a779cad7 --- /dev/null +++ b/deploy/onpremise/docker-compose.yml @@ -0,0 +1,155 @@ +# Vexplor 온프레미스(공장) 배포용 Docker Compose +# 사용법: docker compose up -d + +services: + # ============================================ + # 1. 데이터베이스 (PostgreSQL) + # ============================================ + database: + image: postgres:15-alpine + container_name: vexplor-db + environment: + POSTGRES_USER: ${DB_USER:-vexplor} + POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required} + POSTGRES_DB: ${DB_NAME:-vexplor} + TZ: Asia/Seoul + volumes: + - postgres_data:/var/lib/postgresql/data + - ./init-db:/docker-entrypoint-initdb.d # 초기화 스크립트 (선택) + ports: + - "${DB_PORT:-5432}:5432" + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-vexplor}"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - vexplor-network + + # ============================================ + # 2. 백엔드 API (Node.js) + # ============================================ + backend: + image: harbor.wace.me/speefox_vexplor/vexplor-backend:${IMAGE_TAG:-latest} + container_name: vexplor-backend + environment: + NODE_ENV: production + PORT: 3001 + HOST: 0.0.0.0 + TZ: Asia/Seoul + # DB 연결 + DB_HOST: database + DB_PORT: 5432 + DB_USER: ${DB_USER:-vexplor} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME:-vexplor} + # JWT + JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required} + JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-24h} + # 암호화 키 (메일 등 민감정보 암호화용) + ENCRYPTION_KEY: ${ENCRYPTION_KEY:-vexplor-encryption-key-32characters-secure} + # 회사 코드 (온프레미스는 단일 회사) + DEFAULT_COMPANY_CODE: ${COMPANY_CODE:-SPIFOX} + # 로깅 + LOG_LEVEL: ${LOG_LEVEL:-info} + volumes: + - backend_uploads:/app/uploads + - backend_data:/app/data + - backend_logs:/app/logs + ports: + - "${BACKEND_PORT:-3001}:3001" + depends_on: + database: + condition: service_healthy + restart: always + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3001/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - vexplor-network + labels: + - "com.centurylinklabs.watchtower.enable=true" + + # ============================================ + # 3. 프론트엔드 (Next.js) + # ============================================ + frontend: + image: harbor.wace.me/speefox_vexplor/vexplor-frontend:${IMAGE_TAG:-latest} + container_name: vexplor-frontend + environment: + NODE_ENV: production + PORT: 3000 + HOSTNAME: 0.0.0.0 + TZ: Asia/Seoul + # 백엔드 API URL (내부 통신) + NEXT_PUBLIC_API_URL: http://${SERVER_IP:-localhost}:${BACKEND_PORT:-3001}/api + ports: + - "${FRONTEND_PORT:-80}:3000" + depends_on: + - backend + restart: always + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + networks: + - vexplor-network + labels: + - "com.centurylinklabs.watchtower.enable=true" + + # ============================================ + # 4. Watchtower (자동 업데이트) + # ============================================ + watchtower: + image: containrrr/watchtower:latest + container_name: vexplor-watchtower + environment: + TZ: Asia/Seoul + DOCKER_API_VERSION: "1.44" + # Harbor 레지스트리 인증 + REPO_USER: ${HARBOR_USER} + REPO_PASS: ${HARBOR_PASSWORD} + # 업데이트 설정 + # WATCHTOWER_POLL_INTERVAL: ${UPDATE_INTERVAL:-300} # 간격 기반 (비활성화) + WATCHTOWER_SCHEDULE: "0 0 * * * *" # 매시 정각에 실행 (cron 형식) + WATCHTOWER_CLEANUP: "true" # 이전 이미지 자동 삭제 + WATCHTOWER_INCLUDE_STOPPED: "true" # 중지된 컨테이너도 업데이트 + WATCHTOWER_ROLLING_RESTART: "true" # 순차 재시작 (다운타임 최소화) + WATCHTOWER_LABEL_ENABLE: "true" # 라벨이 있는 컨테이너만 업데이트 + # 알림 설정 (선택) + # WATCHTOWER_NOTIFICATIONS: slack + # WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL: ${SLACK_WEBHOOK_URL} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + # Harbor 인증 정보 (docker login 후 생성됨) + - ~/.docker/config.json:/config.json:ro + restart: always + networks: + - vexplor-network + +# ============================================ +# 볼륨 정의 +# ============================================ +volumes: + postgres_data: + driver: local + backend_uploads: + driver: local + backend_data: + driver: local + backend_logs: + driver: local + +# ============================================ +# 네트워크 정의 +# ============================================ +networks: + vexplor-network: + driver: bridge + diff --git a/deploy/onpremise/env.example b/deploy/onpremise/env.example new file mode 100644 index 00000000..7ffc0d5b --- /dev/null +++ b/deploy/onpremise/env.example @@ -0,0 +1,65 @@ +# ============================================ +# Vexplor 온프레미스(공장) 환경 변수 +# ============================================ +# 사용법: 이 파일을 .env로 복사 후 값 수정 +# cp env.example .env + +# ============================================ +# 서버 정보 +# ============================================ +# 이 서버의 IP 주소 (프론트엔드가 백엔드 API 호출할 때 사용) +SERVER_IP=192.168.0.100 + +# ============================================ +# 회사 정보 +# ============================================ +# 이 공장의 회사 코드 (멀티테넌시용) +COMPANY_CODE=SPIFOX + +# ============================================ +# 데이터베이스 설정 +# ============================================ +DB_USER=vexplor +DB_PASSWORD=your_secure_password_here +DB_NAME=vexplor +DB_PORT=5432 + +# ============================================ +# 백엔드 설정 +# ============================================ +BACKEND_PORT=3001 +JWT_SECRET=your_jwt_secret_key_minimum_32_characters +JWT_EXPIRES_IN=24h +LOG_LEVEL=info + +# ============================================ +# 프론트엔드 설정 +# ============================================ +FRONTEND_PORT=80 + +# ============================================ +# Harbor 레지스트리 인증 +# ============================================ +# Watchtower가 이미지를 당겨올 때 사용 +HARBOR_USER=your_harbor_username +HARBOR_PASSWORD=your_harbor_password + +# ============================================ +# 이미지 태그 +# ============================================ +# latest 또는 특정 버전 (v1.0.0 등) +IMAGE_TAG=latest + +# ============================================ +# Watchtower 설정 +# ============================================ +# 업데이트 확인 주기 (초 단위) +# 300 = 5분, 3600 = 1시간, 86400 = 24시간 +UPDATE_INTERVAL=3600 + +# ============================================ +# 알림 설정 (선택) +# ============================================ +# Slack 웹훅 URL (업데이트 알림 받기) +# SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxx + diff --git a/deploy/onpremise/scripts/backup.sh b/deploy/onpremise/scripts/backup.sh new file mode 100644 index 00000000..1e3a65fd --- /dev/null +++ b/deploy/onpremise/scripts/backup.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# ============================================ +# Vexplor 백업 스크립트 +# Cron에 등록하여 정기 백업 가능 +# ============================================ + +set -e + +INSTALL_DIR="/opt/vexplor" +BACKUP_DIR="/opt/vexplor/backups" +DATE=$(date +%Y%m%d_%H%M%S) + +# 백업 디렉토리 생성 +mkdir -p $BACKUP_DIR + +echo "==========================================" +echo " Vexplor 백업 시작 - $DATE" +echo "==========================================" + +cd $INSTALL_DIR + +# 1. PostgreSQL 데이터베이스 백업 +echo "[1/3] 데이터베이스 백업..." +docker compose exec -T database pg_dump -U vexplor vexplor > "$BACKUP_DIR/db_$DATE.sql" +gzip "$BACKUP_DIR/db_$DATE.sql" +echo " → $BACKUP_DIR/db_$DATE.sql.gz" + +# 2. 업로드 파일 백업 +echo "[2/3] 업로드 파일 백업..." +docker cp vexplor-backend:/app/uploads "$BACKUP_DIR/uploads_$DATE" 2>/dev/null || echo " → 업로드 폴더 없음 (스킵)" +if [ -d "$BACKUP_DIR/uploads_$DATE" ]; then + tar -czf "$BACKUP_DIR/uploads_$DATE.tar.gz" -C "$BACKUP_DIR" "uploads_$DATE" + rm -rf "$BACKUP_DIR/uploads_$DATE" + echo " → $BACKUP_DIR/uploads_$DATE.tar.gz" +fi + +# 3. 환경 설정 백업 +echo "[3/3] 환경 설정 백업..." +cp "$INSTALL_DIR/.env" "$BACKUP_DIR/env_$DATE" +cp "$INSTALL_DIR/docker-compose.yml" "$BACKUP_DIR/docker-compose_$DATE.yml" +echo " → $BACKUP_DIR/env_$DATE" +echo " → $BACKUP_DIR/docker-compose_$DATE.yml" + +# 4. 오래된 백업 정리 (30일 이상) +echo "" +echo "[정리] 30일 이상 된 백업 삭제..." +find $BACKUP_DIR -type f -mtime +30 -delete 2>/dev/null || true + +# 완료 +echo "" +echo "==========================================" +echo " 백업 완료!" +echo "==========================================" +echo "" +echo "백업 위치: $BACKUP_DIR" +ls -lh $BACKUP_DIR | tail -10 + diff --git a/deploy/onpremise/scripts/install.sh b/deploy/onpremise/scripts/install.sh new file mode 100644 index 00000000..880dcbcc --- /dev/null +++ b/deploy/onpremise/scripts/install.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# ============================================ +# Vexplor 온프레미스 초기 설치 스크립트 +# ============================================ + +set -e + +echo "==========================================" +echo " Vexplor 온프레미스 설치 스크립트" +echo "==========================================" + +# 색상 정의 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 설치 경로 +INSTALL_DIR="/opt/vexplor" + +# 1. Docker 설치 확인 +echo -e "\n${YELLOW}[1/5] Docker 설치 확인...${NC}" +if ! command -v docker &> /dev/null; then + echo -e "${RED}Docker가 설치되어 있지 않습니다.${NC}" + echo "다음 명령어로 설치하세요:" + echo " curl -fsSL https://get.docker.com | sh" + echo " sudo usermod -aG docker \$USER" + exit 1 +fi +echo -e "${GREEN}Docker $(docker --version | cut -d' ' -f3)${NC}" + +# 2. Docker Compose 확인 +echo -e "\n${YELLOW}[2/5] Docker Compose 확인...${NC}" +if ! docker compose version &> /dev/null; then + echo -e "${RED}Docker Compose v2가 설치되어 있지 않습니다.${NC}" + exit 1 +fi +echo -e "${GREEN}$(docker compose version)${NC}" + +# 3. 설치 디렉토리 생성 +echo -e "\n${YELLOW}[3/5] 설치 디렉토리 생성...${NC}" +sudo mkdir -p $INSTALL_DIR +sudo chown $USER:$USER $INSTALL_DIR +echo -e "${GREEN}$INSTALL_DIR 생성 완료${NC}" + +# 4. 파일 복사 +echo -e "\n${YELLOW}[4/5] 설정 파일 복사...${NC}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +cp "$SCRIPT_DIR/docker-compose.yml" "$INSTALL_DIR/" +cp "$SCRIPT_DIR/env.example" "$INSTALL_DIR/" + +if [ ! -f "$INSTALL_DIR/.env" ]; then + cp "$SCRIPT_DIR/env.example" "$INSTALL_DIR/.env" + echo -e "${YELLOW}[주의] .env 파일을 생성했습니다. 반드시 수정하세요!${NC}" +fi + +echo -e "${GREEN}파일 복사 완료${NC}" + +# 5. Harbor 로그인 안내 +echo -e "\n${YELLOW}[5/5] Harbor 레지스트리 로그인...${NC}" +if [ ! -f ~/.docker/config.json ] || ! grep -q "harbor.wace.me" ~/.docker/config.json 2>/dev/null; then + echo -e "${YELLOW}Harbor 로그인이 필요합니다:${NC}" + echo " docker login harbor.wace.me" +else + echo -e "${GREEN}Harbor 로그인 확인됨${NC}" +fi + +# 완료 메시지 +echo -e "\n==========================================" +echo -e "${GREEN} 설치 준비 완료!${NC}" +echo "==========================================" +echo "" +echo "다음 단계:" +echo " 1. 환경 변수 설정: nano $INSTALL_DIR/.env" +echo " 2. Harbor 로그인: docker login harbor.wace.me" +echo " 3. 서비스 시작: cd $INSTALL_DIR && docker compose up -d" +echo "" + diff --git a/deploy/onpremise/scripts/server-setup.sh b/deploy/onpremise/scripts/server-setup.sh new file mode 100644 index 00000000..fa20a85f --- /dev/null +++ b/deploy/onpremise/scripts/server-setup.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# ============================================ +# Vexplor 온프레미스 서버 초기 설정 스크립트 +# 스피폭스 공장 서버용 +# ============================================ +# 사용법: sudo bash server-setup.sh + +set -e + +# 색상 정의 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo "" +echo "==========================================" +echo " Vexplor 서버 초기 설정" +echo "==========================================" +echo "" + +# root 권한 확인 +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}이 스크립트는 root 권한이 필요합니다.${NC}" + echo "다음 명령어로 실행하세요: sudo bash server-setup.sh" + exit 1 +fi + +# ============================================ +# 1. Docker 설치 +# ============================================ +echo -e "${YELLOW}[1/5] Docker 설치 중...${NC}" + +# 기존 Docker 제거 +apt-get remove -y docker docker-engine docker.io containerd runc 2>/dev/null || true + +# 필수 패키지 설치 +apt-get update +apt-get install -y ca-certificates curl gnupg + +# Docker GPG 키 추가 +install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg +chmod a+r /etc/apt/keyrings/docker.gpg + +# Docker 저장소 추가 +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + tee /etc/apt/sources.list.d/docker.list > /dev/null + +# Docker 설치 +apt-get update +apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +echo -e "${GREEN}Docker 설치 완료!${NC}" +docker --version +docker compose version + +# ============================================ +# 2. 사용자를 docker 그룹에 추가 +# ============================================ +echo "" +echo -e "${YELLOW}[2/5] 사용자 권한 설정...${NC}" + +# wace 사용자를 docker 그룹에 추가 +usermod -aG docker wace + +echo -e "${GREEN}wace 사용자를 docker 그룹에 추가했습니다.${NC}" + +# ============================================ +# 3. Vexplor 디렉토리 생성 +# ============================================ +echo "" +echo -e "${YELLOW}[3/5] Vexplor 디렉토리 생성...${NC}" + +mkdir -p /opt/vexplor +chown wace:wace /opt/vexplor + +echo -e "${GREEN}/opt/vexplor 디렉토리 생성 완료!${NC}" + +# ============================================ +# 4. Docker 서비스 시작 및 자동 시작 설정 +# ============================================ +echo "" +echo -e "${YELLOW}[4/5] Docker 서비스 설정...${NC}" + +systemctl start docker +systemctl enable docker + +echo -e "${GREEN}Docker 서비스 활성화 완료!${NC}" + +# ============================================ +# 5. 방화벽 설정 (필요시) +# ============================================ +echo "" +echo -e "${YELLOW}[5/5] 방화벽 설정 확인...${NC}" + +if command -v ufw &> /dev/null; then + ufw status + echo "" + echo "필요시 다음 포트를 개방하세요:" + echo " sudo ufw allow 80/tcp # 웹 서비스" + echo " sudo ufw allow 3001/tcp # 백엔드 API" +else + echo "ufw가 설치되어 있지 않습니다. (방화벽 설정 스킵)" +fi + +# ============================================ +# 완료 +# ============================================ +echo "" +echo "==========================================" +echo -e "${GREEN} 서버 초기 설정 완료!${NC}" +echo "==========================================" +echo "" +echo "다음 단계:" +echo " 1. 로그아웃 후 다시 로그인 (docker 그룹 적용)" +echo " exit" +echo " ssh -p 22 wace@112.168.212.142" +echo "" +echo " 2. Docker 동작 확인" +echo " docker ps" +echo "" +echo " 3. Vexplor 배포 진행" +echo " cd /opt/vexplor" +echo " # docker-compose.yml 및 .env 파일 복사 후" +echo " docker compose up -d" +echo "" + diff --git a/deploy/onpremise/scripts/update.sh b/deploy/onpremise/scripts/update.sh new file mode 100644 index 00000000..77e7678b --- /dev/null +++ b/deploy/onpremise/scripts/update.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# ============================================ +# Vexplor 수동 업데이트 스크립트 +# Watchtower를 기다리지 않고 즉시 업데이트할 때 사용 +# ============================================ + +set -e + +INSTALL_DIR="/opt/vexplor" +cd $INSTALL_DIR + +echo "==========================================" +echo " Vexplor 수동 업데이트" +echo "==========================================" + +# 1. 현재 상태 백업 +echo "[1/4] 현재 설정 백업..." +docker compose config > "backup-config-$(date +%Y%m%d-%H%M%S).yml" + +# 2. 최신 이미지 다운로드 +echo "[2/4] 최신 이미지 다운로드..." +docker compose pull backend frontend + +# 3. 서비스 재시작 (롤링 업데이트) +echo "[3/4] 서비스 재시작..." +docker compose up -d --no-deps backend +sleep 10 # 백엔드가 완전히 뜰 때까지 대기 +docker compose up -d --no-deps frontend + +# 4. 상태 확인 +echo "[4/4] 상태 확인..." +sleep 5 +docker compose ps + +echo "" +echo "==========================================" +echo " 업데이트 완료!" +echo "==========================================" +echo "" +echo "로그 확인: docker compose logs -f" + diff --git a/digitalTwin/architecture-v4.md b/digitalTwin/architecture-v4.md new file mode 100644 index 00000000..96e32ef1 --- /dev/null +++ b/digitalTwin/architecture-v4.md @@ -0,0 +1,209 @@ +# 디지털트윈 아키텍처 v4 + +## 변경사항 (v3 → v4) + +| 구분 | v3 | v4 | +| :--- | :--- | :--- | +| OTA 업데이트 | 개념만 존재 | Fleet Manager + MQTT 구현 | +| 디바이스 관리 | 없음 | Device Registry 추가 | +| 상태 모니터링 | 없음 | Heartbeat + Metrics 추가 | +| 원격 제어 | 없음 | MQTT 기반 명령 추가 | +| Agent | 없음 | Fleet Agent 추가 | + +--- + +## Mermaid 다이어그램 + +```mermaid +--- +config: + layout: dagre +--- +flowchart BT + subgraph Global_Platform["☁️ Vexplor 글로벌 플랫폼"] + direction TB + AAS_Dashboard["💻 AAS 통합 대시보드
(React/Next.js)
• 중앙 모니터링
• Fleet 관리 UI"] + Global_API["🌐 글로벌 API 게이트웨이
• 사용자 인증 (Auth)
• 고객사 라우팅
• Fleet API"] + + subgraph Fleet_System["🎛️ Fleet Management"] + Fleet_Manager["📊 Fleet Manager
• Device Registry
• 배포 오케스트레이션
• 상태 모니터링"] + MQTT_Broker["📡 MQTT Broker
(Mosquitto/EMQX)
• 실시간 통신
• 10,000+ 연결"] + Monitoring["📈 Monitoring
(Prometheus/Grafana)
• 메트릭 수집
• 알림"] + end + + Update_Server["🚀 배포/업데이트 매니저
• Docker 이미지 레지스트리 (Harbor)
• 버전 관리
• Canary 배포"] + end + + subgraph Local_Server["스피폭스 사내 서버 (Local Server)"] + Fleet_Agent_A["🤖 Fleet Agent
• MQTT 연결
• Heartbeat (30초)
• 원격 명령 실행
• Docker 관리"] + VEX_Engine["VEX Flow 엔진
데이터 수집/처리"] + Customer_DB[("사내 통합 DB
(모든 데이터 보유)")] + Watchtower_A["🐋 Watchtower
이미지 자동 업데이트"] + end + + subgraph Edge_Internals["🖥️ 엣지 디바이스 (Store & Forward)"] + Edge_Collector["수집/가공
(Python)"] + Edge_Buffer[("💾 로컬 버퍼
(TimescaleDB)
단절 시 임시 저장")] + Edge_Sender["📤 전송 매니저
(Priority Queue)"] + Edge_Retry_Queue[("🕒 재전송 큐
(SQLite/File)")] + end + + subgraph Factory_A["🏭 스피폭스 공장 현장 (Factory Floor)"] + Edge_Internals + PLC_A["PLC / 센서"] + end + + subgraph Customer_A["🏢 고객사 A: 스피폭스 (사내망)"] + Local_Server + Factory_A + end + + subgraph Local_Server_B["고객사 B 사내 서버"] + Fleet_Agent_B["🤖 Fleet Agent"] + Watchtower_B["🐋 Watchtower"] + end + + subgraph Customer_B["🏭 고객사 B (확장 예정)"] + Local_Server_B + end + + subgraph Local_Server_N["고객사 N 사내 서버"] + Fleet_Agent_N["🤖 Fleet Agent"] + end + + subgraph Customer_N["🏭 고객사 N (10,000개)"] + Local_Server_N + end + + %% 대시보드 연결 + AAS_Dashboard <--> Global_API + AAS_Dashboard <--> Fleet_Manager + + %% Fleet System 내부 연결 + Fleet_Manager <--> MQTT_Broker + Fleet_Manager <--> Monitoring + Fleet_Manager <--> Update_Server + + %% 공장 내부 연결 + PLC_A <--> Edge_Collector + Edge_Collector --> Edge_Buffer + Edge_Buffer --> Edge_Sender + Edge_Sender -- ① 정상 전송 --> VEX_Engine + Edge_Sender -- ② 전송 실패 시 --> Edge_Retry_Queue + Edge_Retry_Queue -. ③ 네트워크 복구 시 재전송 .-> Edge_Sender + VEX_Engine <--> Customer_DB + + %% Fleet Agent 연결 (MQTT - Outbound Only) + Fleet_Agent_A == 📡 MQTT (Heartbeat/명령) ==> MQTT_Broker + Fleet_Agent_B == 📡 MQTT ==> MQTT_Broker + Fleet_Agent_N == 📡 MQTT ==> MQTT_Broker + + %% Agent ↔ 로컬 컴포넌트 + Fleet_Agent_A <--> VEX_Engine + Fleet_Agent_A <--> Watchtower_A + Fleet_Agent_A <--> Customer_DB + + %% OTA 업데이트 (Pull 방식) + Update_Server -. 이미지 배포 .-> Watchtower_A + Update_Server -. 이미지 배포 .-> Watchtower_B + Watchtower_A -. 컨테이너 업데이트 .-> VEX_Engine + + %% 엣지 업데이트 + VEX_Engine -. 엣지 업데이트 .-> Edge_Collector + + %% 스타일 + AAS_Dashboard:::user + Global_API:::global + Update_Server:::global + Fleet_Manager:::fleet + MQTT_Broker:::fleet + Monitoring:::fleet + VEX_Engine:::localServer + Customer_DB:::localServer + Fleet_Agent_A:::agent + Fleet_Agent_B:::agent + Fleet_Agent_N:::agent + Watchtower_A:::agent + Watchtower_B:::agent + Edge_Collector:::edge + Edge_Buffer:::edgedb + Edge_Sender:::edge + Edge_Retry_Queue:::fail + PLC_A:::factory + + classDef factory fill:#e1f5fe,stroke:#01579b,stroke-width:2px + classDef edge fill:#fff9c4,stroke:#fbc02d,stroke-width:2px + classDef edgedb fill:#fff9c4,stroke:#fbc02d,stroke-width:2px,stroke-dasharray: 5 5 + classDef localServer fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px + classDef global fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px + classDef user fill:#ffebee,stroke:#c62828,stroke-width:2px + classDef fleet fill:#e3f2fd,stroke:#1565c0,stroke-width:2px + classDef agent fill:#fff3e0,stroke:#ef6c00,stroke-width:2px + classDef fail fill:#ffebee,stroke:#c62828,stroke-width:2px,stroke-dasharray: 5 5 + + linkStyle 8 stroke:#2e7d32,stroke-width:2px,fill:none + linkStyle 9 stroke:#c62828,stroke-width:2px,fill:none + linkStyle 10 stroke:#fbc02d,stroke-width:2px,stroke-dasharray: 5 5,fill:none +``` + +--- + +## 추가된 컴포넌트 설명 + +### 1. Fleet Management (신규) + +| 컴포넌트 | 역할 | +| :--- | :--- | +| **Fleet Manager** | 10,000개 디바이스 등록/관리, 배포 오케스트레이션 | +| **MQTT Broker** | 실시간 양방향 통신 (Outbound Only 유지) | +| **Monitoring** | Prometheus + Grafana, 메트릭 수집 & 알림 | + +### 2. Fleet Agent (각 공장 서버에 설치) + +| 기능 | 설명 | +| :--- | :--- | +| **MQTT 연결** | 글로벌 플랫폼과 상시 연결 (Outbound) | +| **Heartbeat** | 30초마다 상태 보고 | +| **원격 명령** | 업데이트, 재시작, 설정 변경 수신 | +| **Docker 관리** | 컨테이너 상태 모니터링 & 제어 | + +### 3. Watchtower (기존 유지) + +- Harbor에서 새 이미지 자동 Pull +- Fleet Agent의 명령으로 즉시 업데이트 가능 + +--- + +## 통신 흐름 비교 + +### v3 (기존) +``` +보안 커넥터 ←→ 글로벌 API (양방향 터널) +``` + +### v4 (신규) +``` +Fleet Agent ──→ MQTT Broker (Outbound Only) + ←── 명령 수신 (Subscribe) + ──→ 상태 보고 (Publish) + +Watchtower ──→ Harbor (Pull Only) +``` + +**장점:** +- 방화벽 Inbound 규칙 불필요 +- 10,000개 동시 연결 가능 +- 실시간 명령 전달 + +--- + +## 데이터 흐름 + +``` +[공장 → 글로벌] +PLC → 엣지 → VEX Flow → Fleet Agent → MQTT → Fleet Manager → Dashboard + +[글로벌 → 공장] +Dashboard → Fleet Manager → MQTT → Fleet Agent → Docker/VEX Flow +``` + diff --git a/digitalTwin/fleet-management-plan.md b/digitalTwin/fleet-management-plan.md new file mode 100644 index 00000000..e80aaab9 --- /dev/null +++ b/digitalTwin/fleet-management-plan.md @@ -0,0 +1,725 @@ +# Fleet Management 시스템 구축 계획서 + +## 개요 + +**목표:** 10,000개 이상의 온프레미스 공장 서버를 중앙에서 효율적으로 관리 + +**현재 상태:** 1개 업체 (스피폭스), Watchtower 기반 자동 업데이트 + +**목표 상태:** 10,000개 업체, 실시간 모니터링 & 원격 제어 가능 + +--- + +## 목차 + +1. [아키텍처 설계](#1-아키텍처-설계) +2. [Phase별 구현 계획](#2-phase별-구현-계획) +3. [핵심 컴포넌트 상세](#3-핵심-컴포넌트-상세) +4. [데이터베이스 스키마](#4-데이터베이스-스키마) +5. [API 설계](#5-api-설계) +6. [기술 스택](#6-기술-스택) +7. [일정 및 마일스톤](#7-일정-및-마일스톤) +8. [리스크 및 대응](#8-리스크-및-대응) + +--- + +## 1. 아키텍처 설계 + +### 1.1 전체 아키텍처 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Vexplor 글로벌 플랫폼 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Web UI │ │ Fleet API │ │ Config │ │ Monitoring │ │ +│ │ (Dashboard) │ │ Gateway │ │ Server │ │ & Alerts │ │ +│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ +│ │ │ │ │ │ +│ └────────────────┼────────────────┼────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────┐ ┌─────────────┐ │ +│ │ Message │ │ Device │ │ +│ │ Broker │ │ Registry │ │ +│ │ (MQTT) │ │ (Redis) │ │ +│ └──────┬──────┘ └─────────────┘ │ +│ │ │ +└──────────────────────────┼────────────────────────────────────────────┘ + │ + │ MQTT (TLS) + │ + ┌──────────────────┼──────────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌─────────┐ ┌─────────┐ ┌─────────┐ + │ Agent │ │ Agent │ │ Agent │ + │ 스피폭스 │ │ 엔키드 │ │ 고객 N │ + └─────────┘ └─────────┘ └─────────┘ + │ │ │ + ┌─────────┐ ┌─────────┐ ┌─────────┐ + │ Vexplor │ │ Vexplor │ │ Vexplor │ + │ Backend │ │ Backend │ │ Backend │ + │Frontend │ │Frontend │ │Frontend │ + │ DB │ │ DB │ │ DB │ + └─────────┘ └─────────┘ └─────────┘ +``` + +### 1.2 통신 흐름 + +``` +[공장 서버 → 글로벌] +1. Agent 시작 시 MQTT 연결 (Outbound Only) +2. 주기적 Heartbeat 전송 (30초) +3. 상태/메트릭 보고 (5분) +4. 로그 전송 (선택적) + +[글로벌 → 공장 서버] +1. 업데이트 명령 +2. 설정 변경 +3. 재시작 명령 +4. 데이터 요청 +``` + +--- + +## 2. Phase별 구현 계획 + +### Phase 1: 기반 구축 (1~10개 업체) +**기간:** 2주 + +| 구현 항목 | 설명 | 우선순위 | +| :--- | :--- | :--- | +| Device Registry API | 디바이스 등록/조회 | P0 | +| Heartbeat API | 상태 보고 수신 | P0 | +| 기본 대시보드 | 디바이스 목록/상태 표시 | P1 | +| Agent 기본 버전 | Heartbeat 전송 기능 | P0 | + +**산출물:** +- `POST /api/fleet/devices/register` +- `POST /api/fleet/devices/heartbeat` +- `GET /api/fleet/devices` +- Agent Docker 이미지 + +--- + +### Phase 2: 실시간 통신 (10~100개 업체) +**기간:** 4주 + +| 구현 항목 | 설명 | 우선순위 | +| :--- | :--- | :--- | +| MQTT 브로커 설치 | Eclipse Mosquitto | P0 | +| Agent MQTT 연결 | 상시 연결 유지 | P0 | +| 원격 명령 기능 | 업데이트/재시작 명령 | P1 | +| 실시간 상태 업데이트 | WebSocket → 대시보드 | P1 | + +**산출물:** +- MQTT 브로커 (Docker) +- Agent v2 (MQTT 지원) +- 원격 명령 UI + +--- + +### Phase 3: 배포 관리 (100~500개 업체) +**기간:** 6주 + +| 구현 항목 | 설명 | 우선순위 | +| :--- | :--- | :--- | +| 버전 관리 시스템 | 릴리즈 버전 관리 | P0 | +| 단계적 롤아웃 | Canary 배포 | P0 | +| 롤백 기능 | 이전 버전 복구 | P0 | +| 그룹 관리 | 지역/업종별 그룹핑 | P1 | +| 배포 스케줄링 | 시간대별 배포 | P2 | + +**산출물:** +- Release Management UI +- Deployment Pipeline +- Rollback 자동화 + +--- + +### Phase 4: 모니터링 강화 (500~2,000개 업체) +**기간:** 6주 + +| 구현 항목 | 설명 | 우선순위 | +| :--- | :--- | :--- | +| 메트릭 수집 | CPU/Memory/Disk | P0 | +| 알림 시스템 | Slack/Email/SMS | P0 | +| 로그 중앙화 | 원격 로그 수집 | P1 | +| 이상 탐지 | 자동 장애 감지 | P1 | +| SLA 대시보드 | 가용성 리포트 | P2 | + +**산출물:** +- Prometheus + Grafana +- Alert Manager +- Log Aggregator (Loki) + +--- + +### Phase 5: 대규모 확장 (2,000~10,000개 업체) +**기간:** 8주 + +| 구현 항목 | 설명 | 우선순위 | +| :--- | :--- | :--- | +| MQTT 클러스터링 | 고가용성 브로커 | P0 | +| 샤딩 | 지역별 분산 | P0 | +| 자동 프로비저닝 | 신규 업체 자동 설정 | P1 | +| API Rate Limiting | 과부하 방지 | P1 | +| 멀티 리전 | 글로벌 분산 | P2 | + +**산출물:** +- MQTT Cluster (EMQX) +- Regional Gateway +- Auto-provisioning System + +--- + +## 3. 핵심 컴포넌트 상세 + +### 3.1 Fleet Agent (공장 서버에 설치) + +``` +┌─────────────────────────────────────────┐ +│ Fleet Agent │ +├─────────────────────────────────────────┤ +│ ┌─────────────┐ ┌─────────────┐ │ +│ │ MQTT │ │ Command │ │ +│ │ Client │ │ Executor │ │ +│ └──────┬──────┘ └──────┬──────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────────────────┐ │ +│ │ Core Controller │ │ +│ └─────────────────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────┐ ┌─────────────┐ │ +│ │ Metrics │ │ Docker │ │ +│ │ Collector │ │ Manager │ │ +│ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────┘ +``` + +**주요 기능:** +- MQTT 연결 유지 (자동 재연결) +- Heartbeat 전송 (30초) +- 시스템 메트릭 수집 +- Docker 컨테이너 관리 +- 원격 명령 실행 + +### 3.2 Fleet Manager (글로벌 서버) + +**주요 기능:** +- 디바이스 등록/인증 +- 상태 모니터링 +- 배포 오케스트레이션 +- 설정 관리 +- 알림 발송 + +### 3.3 Message Broker (MQTT) + +**선택지:** +| 옵션 | 장점 | 단점 | 추천 규모 | +| :--- | :--- | :--- | :--- | +| Mosquitto | 가볍고 간단 | 클러스터링 어려움 | ~1,000 | +| EMQX | 클러스터링, 고성능 | 복잡함 | 1,000~100,000 | +| HiveMQ | 엔터프라이즈급 | 비용 | 100,000+ | + +**권장:** Phase 1~3은 Mosquitto, Phase 4~5는 EMQX + +--- + +## 4. 데이터베이스 스키마 + +### 4.1 디바이스 테이블 + +```sql +-- 디바이스 (공장 서버) 정보 +CREATE TABLE fleet_devices ( + id SERIAL PRIMARY KEY, + device_id VARCHAR(50) UNIQUE NOT NULL, -- 고유 식별자 + company_code VARCHAR(20) NOT NULL, -- 회사 코드 + device_name VARCHAR(100), -- 표시 이름 + + -- 연결 정보 + ip_address VARCHAR(45), + last_seen_at TIMESTAMPTZ, + is_online BOOLEAN DEFAULT false, + + -- 버전 정보 + agent_version VARCHAR(20), + app_version VARCHAR(20), + + -- 시스템 정보 + os_info JSONB, + hardware_info JSONB, + + -- 그룹/태그 + device_group VARCHAR(50), + tags JSONB DEFAULT '[]', + + -- 메타 + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + + FOREIGN KEY (company_code) REFERENCES company_info(company_code) +); + +CREATE INDEX idx_fleet_devices_company ON fleet_devices(company_code); +CREATE INDEX idx_fleet_devices_online ON fleet_devices(is_online); +CREATE INDEX idx_fleet_devices_group ON fleet_devices(device_group); +``` + +### 4.2 Heartbeat 로그 테이블 + +```sql +-- Heartbeat 기록 (TimescaleDB 권장) +CREATE TABLE fleet_heartbeats ( + id BIGSERIAL, + device_id VARCHAR(50) NOT NULL, + received_at TIMESTAMPTZ DEFAULT NOW(), + + -- 상태 + status VARCHAR(20), -- OK, WARNING, ERROR + uptime_seconds BIGINT, + + -- 메트릭 + cpu_percent DECIMAL(5,2), + memory_percent DECIMAL(5,2), + disk_percent DECIMAL(5,2), + + -- 컨테이너 상태 + containers JSONB, + + PRIMARY KEY (device_id, received_at) +); + +-- TimescaleDB 하이퍼테이블 변환 (선택) +-- SELECT create_hypertable('fleet_heartbeats', 'received_at'); +``` + +### 4.3 배포 테이블 + +```sql +-- 릴리즈 버전 관리 +CREATE TABLE fleet_releases ( + id SERIAL PRIMARY KEY, + version VARCHAR(20) NOT NULL, + release_type VARCHAR(20), -- stable, beta, hotfix + + -- 이미지 정보 + backend_image VARCHAR(200), + frontend_image VARCHAR(200), + agent_image VARCHAR(200), + + -- 변경사항 + changelog TEXT, + + -- 상태 + status VARCHAR(20) DEFAULT 'draft', -- draft, testing, released, deprecated + released_at TIMESTAMPTZ, + + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- 배포 작업 +CREATE TABLE fleet_deployments ( + id SERIAL PRIMARY KEY, + release_id INTEGER REFERENCES fleet_releases(id), + + -- 배포 대상 + target_type VARCHAR(20), -- all, group, specific + target_value VARCHAR(100), -- 그룹명 또는 device_id + + -- 롤아웃 설정 + rollout_strategy VARCHAR(20), -- immediate, canary, scheduled + rollout_percentage INTEGER, + scheduled_at TIMESTAMPTZ, + + -- 상태 + status VARCHAR(20) DEFAULT 'pending', + started_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ, + + -- 결과 + total_devices INTEGER, + success_count INTEGER DEFAULT 0, + failed_count INTEGER DEFAULT 0, + + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- 개별 디바이스 배포 상태 +CREATE TABLE fleet_deployment_status ( + id SERIAL PRIMARY KEY, + deployment_id INTEGER REFERENCES fleet_deployments(id), + device_id VARCHAR(50), + + status VARCHAR(20) DEFAULT 'pending', -- pending, downloading, installing, completed, failed + started_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ, + error_message TEXT, + + UNIQUE(deployment_id, device_id) +); +``` + +### 4.4 알림 규칙 테이블 + +```sql +-- 알림 규칙 +CREATE TABLE fleet_alert_rules ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + + -- 조건 + condition_type VARCHAR(50), -- offline, version_mismatch, high_cpu, etc. + condition_value JSONB, + threshold_minutes INTEGER, -- 조건 지속 시간 + + -- 알림 채널 + notify_channels JSONB, -- ["slack", "email"] + notify_targets JSONB, -- 수신자 목록 + + -- 상태 + is_enabled BOOLEAN DEFAULT true, + + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- 알림 기록 +CREATE TABLE fleet_alerts ( + id SERIAL PRIMARY KEY, + rule_id INTEGER REFERENCES fleet_alert_rules(id), + device_id VARCHAR(50), + + alert_type VARCHAR(50), + message TEXT, + severity VARCHAR(20), -- info, warning, critical + + -- 해결 상태 + status VARCHAR(20) DEFAULT 'open', -- open, acknowledged, resolved + resolved_at TIMESTAMPTZ, + + created_at TIMESTAMPTZ DEFAULT NOW() +); +``` + +--- + +## 5. API 설계 + +### 5.1 Device Management API + +```yaml +# 디바이스 등록 +POST /api/fleet/devices/register +Request: + device_id: string (required) + company_code: string (required) + device_name: string + agent_version: string + os_info: object +Response: + success: boolean + data: + device_id: string + mqtt_credentials: + broker_url: string + username: string + password: string + +# 디바이스 목록 조회 +GET /api/fleet/devices +Query: + company_code: string + is_online: boolean + device_group: string + page: number + limit: number +Response: + success: boolean + data: Device[] + pagination: { total, page, limit } + +# 디바이스 상세 조회 +GET /api/fleet/devices/:deviceId +Response: + success: boolean + data: + device: Device + recent_heartbeats: Heartbeat[] + recent_alerts: Alert[] +``` + +### 5.2 Heartbeat API + +```yaml +# Heartbeat 전송 +POST /api/fleet/devices/:deviceId/heartbeat +Request: + status: string + uptime_seconds: number + metrics: + cpu_percent: number + memory_percent: number + disk_percent: number + containers: + - name: string + status: string + version: string +Response: + success: boolean + data: + commands: Command[] # 대기 중인 명령 반환 +``` + +### 5.3 Deployment API + +```yaml +# 배포 생성 +POST /api/fleet/deployments +Request: + release_id: number + target_type: "all" | "group" | "specific" + target_value: string + rollout_strategy: "immediate" | "canary" | "scheduled" + rollout_percentage: number + scheduled_at: datetime +Response: + success: boolean + data: + deployment_id: number + estimated_devices: number + +# 배포 상태 조회 +GET /api/fleet/deployments/:deploymentId +Response: + success: boolean + data: + deployment: Deployment + status_summary: + pending: number + in_progress: number + completed: number + failed: number + device_statuses: DeploymentStatus[] + +# 배포 롤백 +POST /api/fleet/deployments/:deploymentId/rollback +Response: + success: boolean + data: + rollback_deployment_id: number +``` + +### 5.4 Command API + +```yaml +# 원격 명령 전송 +POST /api/fleet/devices/:deviceId/commands +Request: + command_type: "update" | "restart" | "config" | "logs" + payload: object +Response: + success: boolean + data: + command_id: string + status: "queued" + +# 명령 결과 조회 +GET /api/fleet/commands/:commandId +Response: + success: boolean + data: + command_id: string + status: "queued" | "sent" | "executing" | "completed" | "failed" + result: object +``` + +--- + +## 6. 기술 스택 + +### 6.1 글로벌 플랫폼 + +| 컴포넌트 | 기술 | 비고 | +| :--- | :--- | :--- | +| Fleet API | Node.js (기존 backend-node 확장) | 기존 코드 재사용 | +| Message Broker | Mosquitto → EMQX | 단계적 전환 | +| Device Registry | Redis | 빠른 조회 | +| Database | PostgreSQL | 기존 DB 확장 | +| Time-series DB | TimescaleDB | Heartbeat 저장 | +| Monitoring | Prometheus + Grafana | 메트릭 시각화 | +| Log | Loki | 로그 중앙화 | +| Alert | AlertManager | 알림 관리 | + +### 6.2 Fleet Agent + +| 컴포넌트 | 기술 | 비고 | +| :--- | :--- | :--- | +| Runtime | Go 또는 Node.js | 가볍고 안정적 | +| MQTT Client | Paho MQTT | 표준 라이브러리 | +| Docker SDK | Docker API | 컨테이너 관리 | +| Metrics | gopsutil | 시스템 메트릭 | + +### 6.3 대시보드 + +| 컴포넌트 | 기술 | 비고 | +| :--- | :--- | :--- | +| UI Framework | Next.js (기존) | 기존 코드 확장 | +| Real-time | Socket.io | 실시간 상태 | +| Charts | Recharts | 메트릭 시각화 | +| Map | Leaflet | 지역별 표시 | + +--- + +## 7. 일정 및 마일스톤 + +### 7.1 전체 일정 + +``` +2025 Q1 2025 Q2 2025 Q3 +│ │ │ +├── Phase 1 (2주) ─────────┤ │ +│ Device Registry │ │ +│ Heartbeat API │ │ +│ 기본 대시보드 │ │ +│ │ │ +│ ├── Phase 2 (4주) ──────────┤ │ +│ │ MQTT 브로커 │ │ +│ │ Agent v2 │ │ +│ │ 원격 명령 │ │ +│ │ │ │ +│ │ ├── Phase 3 (6주) ──────┤ +│ │ │ 버전 관리 │ +│ │ │ Canary 배포 │ +│ │ │ 롤백 │ +│ │ │ │ +``` + +### 7.2 상세 마일스톤 + +| 마일스톤 | 목표 | 완료 기준 | 예상 일정 | +| :--- | :--- | :--- | :--- | +| M1 | Device Registry | 디바이스 등록/조회 API 완료 | 1주차 | +| M2 | Heartbeat | 상태 보고 & 저장 완료 | 2주차 | +| M3 | Basic Dashboard | 디바이스 목록 UI 완료 | 2주차 | +| M4 | MQTT Setup | 브로커 설치 & 연결 테스트 | 4주차 | +| M5 | Agent v2 | MQTT 기반 Agent 완료 | 6주차 | +| M6 | Remote Command | 업데이트/재시작 명령 완료 | 8주차 | +| M7 | Release Mgmt | 버전 관리 UI 완료 | 10주차 | +| M8 | Canary Deploy | 단계적 배포 완료 | 14주차 | + +--- + +## 8. 리스크 및 대응 + +### 8.1 기술적 리스크 + +| 리스크 | 영향 | 확률 | 대응 | +| :--- | :--- | :--- | :--- | +| MQTT 연결 불안정 | 높음 | 중간 | 자동 재연결, 오프라인 큐 | +| 대량 동시 접속 | 높음 | 높음 | 클러스터링, 로드밸런싱 | +| 보안 취약점 | 높음 | 낮음 | TLS 필수, 인증 강화 | +| 네트워크 단절 | 중간 | 높음 | 로컬 캐시, 재전송 로직 | + +### 8.2 운영 리스크 + +| 리스크 | 영향 | 확률 | 대응 | +| :--- | :--- | :--- | :--- | +| 잘못된 배포 | 높음 | 중간 | Canary 배포, 자동 롤백 | +| 모니터링 누락 | 중간 | 중간 | 다중 알림 채널 | +| 버전 파편화 | 중간 | 높음 | 강제 업데이트 정책 | + +--- + +## 9. 다음 단계 + +### 즉시 시작할 작업 (Phase 1) + +1. **Device Registry 테이블 생성** + - `fleet_devices` 테이블 마이그레이션 + +2. **Fleet API 엔드포인트 개발** + - `POST /api/fleet/devices/register` + - `POST /api/fleet/devices/:deviceId/heartbeat` + - `GET /api/fleet/devices` + +3. **Agent 기본 버전 개발** + - Docker 이미지로 배포 + - 주기적 Heartbeat 전송 + +4. **대시보드 기본 화면** + - 디바이스 목록 + - 온라인/오프라인 상태 표시 + +--- + +## 부록 + +### A. MQTT 토픽 설계 + +``` +vexplor/ +├── devices/ +│ ├── {device_id}/ +│ │ ├── status # 상태 보고 (Agent → Server) +│ │ ├── metrics # 메트릭 보고 (Agent → Server) +│ │ ├── commands # 명령 수신 (Server → Agent) +│ │ └── responses # 명령 응답 (Agent → Server) +│ │ +├── broadcasts/ +│ ├── all # 전체 공지 +│ └── groups/{group} # 그룹별 공지 +│ +└── system/ + ├── announcements # 시스템 공지 + └── maintenance # 점검 알림 +``` + +### B. Agent 설정 파일 + +```yaml +# /opt/vexplor/agent/config.yaml +device: + id: "SPIFOX-001" + company_code: "SPIFOX" + name: "스피폭스 메인 서버" + +mqtt: + broker: "mqtts://mqtt.vexplor.com:8883" + username: "${MQTT_USERNAME}" + password: "${MQTT_PASSWORD}" + keepalive: 60 + reconnect_interval: 5 + +heartbeat: + interval: 30 # seconds + +metrics: + enabled: true + interval: 300 # 5 minutes + collect: + - cpu + - memory + - disk + - network + +docker: + socket: "/var/run/docker.sock" + managed_containers: + - vexplor-backend + - vexplor-frontend + - vexplor-db +``` + +### C. 참고 자료 + +- [EMQX Documentation](https://docs.emqx.com/) +- [Eclipse Mosquitto](https://mosquitto.org/) +- [AWS IoT Device Management](https://aws.amazon.com/iot-device-management/) +- [Google Cloud IoT Core](https://cloud.google.com/iot-core) +- [HashiCorp Nomad](https://www.nomadproject.io/) + diff --git a/digitalTwin/디지털트윈 아키텍쳐_v3.png b/digitalTwin/디지털트윈 아키텍쳐_v3.png new file mode 100644 index 00000000..b72e7549 Binary files /dev/null and b/digitalTwin/디지털트윈 아키텍쳐_v3.png differ diff --git a/digitalTwin/디지털트윈 아키텍쳐_v4.png b/digitalTwin/디지털트윈 아키텍쳐_v4.png new file mode 100644 index 00000000..62d72b47 Binary files /dev/null and b/digitalTwin/디지털트윈 아키텍쳐_v4.png differ diff --git a/k8s/ingress-nginx.yaml b/k8s/ingress-nginx.yaml deleted file mode 100644 index dfb551cd..00000000 --- a/k8s/ingress-nginx.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Nginx Ingress Controller 설치 -# 단일 노드 클러스터용 설정 (NodePort 사용) -# -# 설치 명령어: -# kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.5/deploy/static/provider/baremetal/deploy.yaml -# -# 또는 이 파일로 커스텀 설치: -# kubectl apply -f k8s/ingress-nginx.yaml - -# NodePort를 80, 443으로 고정하는 패치용 설정 -apiVersion: v1 -kind: Service -metadata: - name: ingress-nginx-controller - namespace: ingress-nginx - labels: - app.kubernetes.io/name: ingress-nginx - app.kubernetes.io/instance: ingress-nginx - app.kubernetes.io/component: controller -spec: - type: NodePort - externalTrafficPolicy: Local - ipFamilyPolicy: SingleStack - ipFamilies: - - IPv4 - ports: - - name: http - port: 80 - protocol: TCP - targetPort: http - nodePort: 30080 - - name: https - port: 443 - protocol: TCP - targetPort: https - nodePort: 30443 - selector: - app.kubernetes.io/name: ingress-nginx - app.kubernetes.io/instance: ingress-nginx - app.kubernetes.io/component: controller - diff --git a/kubernetes-setup-guide.md b/kubernetes-setup-guide.md index 3d27b04c..8f785b46 100644 --- a/kubernetes-setup-guide.md +++ b/kubernetes-setup-guide.md @@ -12,29 +12,29 @@ ### 기존 서버 (참조용) -| 항목 | 값 | -|------|-----| -| IP | 211.115.91.170 | -| SSH 포트 | 12991 | -| 사용자 | geonhee | -| OS | Ubuntu 24.04.3 LTS | -| K8s 버전 | v1.28.0 | -| 컨테이너 런타임 | containerd 1.7.28 | +| 항목 | 값 | +| --------------- | ------------------ | +| IP | 211.115.91.170 | +| SSH 포트 | 12991 | +| 사용자 | geonhee | +| OS | Ubuntu 24.04.3 LTS | +| K8s 버전 | v1.28.0 | +| 컨테이너 런타임 | containerd 1.7.28 | ### 새 서버 (구축 완료) -| 항목 | 값 | -|------|-----| -| IP | 112.168.212.142 | -| SSH 포트 | 22 | -| 사용자 | wace | -| 호스트명 | waceserver | -| OS | Ubuntu 24.04.3 LTS | -| K8s 버전 | v1.28.15 | -| 컨테이너 런타임 | containerd 1.7.28 | -| 내부 IP | 10.10.0.74 | -| CPU | 20코어 | -| 메모리 | 31GB | +| 항목 | 값 | +| --------------- | ------------------ | +| IP | 112.168.212.142 | +| SSH 포트 | 22 | +| 사용자 | wace | +| 호스트명 | waceserver | +| OS | Ubuntu 24.04.3 LTS | +| K8s 버전 | v1.28.15 | +| 컨테이너 런타임 | containerd 1.7.28 | +| 내부 IP | 10.10.0.74 | +| CPU | 20코어 | +| 메모리 | 31GB | --- @@ -112,6 +112,7 @@ sudo kubeadm init --pod-network-cidr=10.244.0.0/16 ``` **출력 결과 (중요 정보)**: + - 클러스터 초기화 성공 - API 서버: https://10.10.0.74:6443 - 워커 노드 조인 토큰 생성됨 @@ -155,9 +156,9 @@ kubectl taint nodes --all node-role.kubernetes.io/control-plane- kubectl get nodes -o wide ``` -| NAME | STATUS | ROLES | VERSION | INTERNAL-IP | OS-IMAGE | CONTAINER-RUNTIME | -|------|--------|-------|---------|-------------|----------|-------------------| -| waceserver | Ready | control-plane | v1.28.15 | 10.10.0.74 | Ubuntu 24.04.3 LTS | containerd://1.7.28 | +| NAME | STATUS | ROLES | VERSION | INTERNAL-IP | OS-IMAGE | CONTAINER-RUNTIME | +| ---------- | ------ | ------------- | -------- | ----------- | ------------------ | ------------------- | +| waceserver | Ready | control-plane | v1.28.15 | 10.10.0.74 | Ubuntu 24.04.3 LTS | containerd://1.7.28 | ### 시스템 Pod 상태 @@ -166,15 +167,15 @@ kubectl get pods -n kube-system kubectl get pods -n kube-flannel ``` -| 컴포넌트 | 상태 | -|---------|------| -| etcd | ✅ Running | -| kube-apiserver | ✅ Running | +| 컴포넌트 | 상태 | +| ----------------------- | ---------- | +| etcd | ✅ Running | +| kube-apiserver | ✅ Running | | kube-controller-manager | ✅ Running | -| kube-scheduler | ✅ Running | -| kube-proxy | ✅ Running | -| coredns (x2) | ✅ Running | -| kube-flannel | ✅ Running | +| kube-scheduler | ✅ Running | +| kube-proxy | ✅ Running | +| coredns (x2) | ✅ Running | +| kube-flannel | ✅ Running | --- @@ -188,6 +189,7 @@ kubeadm join 10.10.0.74:6443 --token 4lfga6.luad9f367uxh0rlq \ ``` **토큰 만료 시 새 토큰 생성**: + ```bash kubeadm token create --print-join-command ``` @@ -271,11 +273,11 @@ k8s/ #### Gitea Repository Secrets 설정 필요 -| Secret 이름 | 설명 | -|------------|------| -| `HARBOR_USERNAME` | Harbor 사용자명 | -| `HARBOR_PASSWORD` | Harbor 비밀번호 | -| `KUBECONFIG` | base64 인코딩된 Kubernetes config | +| Secret 이름 | 설명 | +| ------------------- | --------------------------------- | +| `HARBOR_USERNAME` | Harbor 사용자명 | +| `HARBOR_PASSWORD` | Harbor 비밀번호 | +| `KUBECONFIG` | base64 인코딩된 Kubernetes config | ```bash # KUBECONFIG 생성 방법 (K8s 서버에서 실행) @@ -301,4 +303,3 @@ ssh -p 12991 geonhee@211.115.91.170 - [Kubernetes 공식 문서](https://kubernetes.io/docs/) - [kubeadm 설치 가이드](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/) - [Flannel 네트워크 플러그인](https://github.com/flannel-io/flannel) -