diff --git a/.github/actions-pipeline-guide.md b/.github/actions-pipeline-guide.md new file mode 100644 index 0000000..8c29dc8 --- /dev/null +++ b/.github/actions-pipeline-guide.md @@ -0,0 +1,500 @@ +# GitHub Actions CI/CD 파이프라인 구축 가이드 + +## 📋 목차 +1. [개요](#개요) +2. [사전 준비사항](#사전-준비사항) +3. [GitHub 저장소 환경 구성](#github-저장소-환경-구성) +4. [디렉토리 구조](#디렉토리-구조) +5. [Kustomize 구조 설명](#kustomize-구조-설명) +6. [GitHub Actions 워크플로우](#github-actions-워크플로우) +7. [배포 방법](#배포-방법) +8. [롤백 방법](#롤백-방법) +9. [SonarQube 설정](#sonarqube-설정) +10. [트러블슈팅](#트러블슈팅) + +--- + +## 개요 + +HGZero 프로젝트의 백엔드 서비스를 위한 GitHub Actions 기반 CI/CD 파이프라인입니다. + +### 주요 기능 +- ✅ Gradle 기반 빌드 및 테스트 +- ✅ SonarQube 코드 품질 분석 (선택적) +- ✅ Azure Container Registry에 Docker 이미지 빌드 및 푸시 +- ✅ Kustomize를 사용한 환경별(dev/staging/prod) 배포 +- ✅ AKS 클러스터 자동 배포 + +### 지원 서비스 +- **user**: 사용자 관리 서비스 +- **meeting**: 회의 관리 서비스 +- **stt**: 음성 인식 서비스 +- **ai**: AI 처리 서비스 +- **notification**: 알림 서비스 + +--- + +## 사전 준비사항 + +### 1. 프로젝트 정보 +- **시스템명**: hgzero +- **ACR 이름**: acrdigitalgarage02 +- **리소스 그룹**: rg-digitalgarage-02 +- **AKS 클러스터**: aks-digitalgarage-02 +- **네임스페이스**: hgzero +- **JDK 버전**: 21 + +### 2. 필수 도구 +- Git +- kubectl +- Azure CLI +- Kustomize (자동 설치됨) + +--- + +## GitHub 저장소 환경 구성 + +### 1. Repository Secrets 설정 + +`Repository Settings > Secrets and variables > Actions > Repository secrets`에 다음 항목을 등록하세요: + +#### Azure 인증 정보 +```json +AZURE_CREDENTIALS: +{ + "clientId": "{클라이언트ID}", + "clientSecret": "{클라이언트시크릿}", + "subscriptionId": "{구독ID}", + "tenantId": "{테넌트ID}" +} +``` + +**예시:** +```json +{ + "clientId": "5e4b5b41-7208-48b7-b821-d6d5acf50ecf", + "clientSecret": "ldu8Q~GQEzFYU.dJX7_QsahR7n7C2xqkIM6hqbV8", + "subscriptionId": "2513dd36-7978-48e3-9a7c-b221d4874f66", + "tenantId": "4f0a3bfd-1156-4cce-8dc2-a049a13dba23" +} +``` + +#### ACR Credentials + +ACR Credential을 확인하려면: +```bash +az acr credential show --name acrdigitalgarage02 +``` + +등록할 Secrets: +``` +ACR_USERNAME: acrdigitalgarage02 +ACR_PASSWORD: {ACR 패스워드} +``` + +#### SonarQube 설정 + +**SONAR_HOST_URL 확인:** +```bash +kubectl get svc -n sonarqube +``` +출력된 External IP를 사용하여 `http://{External IP}` 형식으로 설정 + +**SONAR_TOKEN 생성:** +1. SonarQube에 로그인 (기본: admin/admin) +2. 우측 상단 'Administrator' > My Account 클릭 +3. Security 탭 선택 후 토큰 생성 + +등록할 Secrets: +``` +SONAR_TOKEN: {SonarQube 토큰} +SONAR_HOST_URL: http://{External IP} +``` + +#### Docker Hub (Rate Limit 해결용) + +**패스워드 생성:** +1. [Docker Hub](https://hub.docker.com) 로그인 +2. 우측 상단 프로필 아이콘 > Account Settings +3. 좌측 메뉴 'Personal Access Tokens' 클릭하여 생성 + +등록할 Secrets: +``` +DOCKERHUB_USERNAME: {Docker Hub 사용자명} +DOCKERHUB_PASSWORD: {Docker Hub 패스워드 또는 토큰} +``` + +### 2. Repository Variables 설정 + +`Repository Settings > Secrets and variables > Actions > Variables > Repository variables`에 등록: + +``` +ENVIRONMENT: dev +SKIP_SONARQUBE: true +``` + +--- + +## 디렉토리 구조 + +``` +.github/ +├── workflows/ +│ └── backend-cicd.yaml # GitHub Actions 워크플로우 +├── kustomize/ +│ ├── base/ # 기본 Kubernetes 매니페스트 +│ │ ├── common/ # 공통 리소스 +│ │ │ ├── cm-common.yaml +│ │ │ ├── secret-common.yaml +│ │ │ ├── secret-imagepull.yaml +│ │ │ └── ingress.yaml +│ │ ├── user/ # User 서비스 +│ │ │ ├── deployment.yaml +│ │ │ ├── service.yaml +│ │ │ └── secret-user.yaml +│ │ ├── meeting/ # Meeting 서비스 +│ │ ├── stt/ # STT 서비스 +│ │ ├── ai/ # AI 서비스 +│ │ ├── notification/ # Notification 서비스 +│ │ └── kustomization.yaml +│ └── overlays/ # 환경별 오버레이 +│ ├── dev/ # 개발 환경 +│ │ ├── kustomization.yaml +│ │ ├── cm-common-patch.yaml +│ │ ├── secret-common-patch.yaml +│ │ ├── ingress-patch.yaml +│ │ ├── deployment-{service}-patch.yaml +│ │ └── secret-{service}-patch.yaml +│ ├── staging/ # 스테이징 환경 +│ └── prod/ # 프로덕션 환경 +├── config/ # 환경별 설정 +│ ├── deploy_env_vars_dev +│ ├── deploy_env_vars_staging +│ └── deploy_env_vars_prod +└── scripts/ + └── deploy-actions.sh # 수동 배포 스크립트 +``` + +--- + +## Kustomize 구조 설명 + +### Base 구조 + +Base는 모든 환경에서 공통으로 사용되는 기본 매니페스트입니다. + +**주요 리소스:** +- **ConfigMap (cm-common)**: 환경 변수, 프로파일 설정 +- **Secret (secret-common)**: JWT 시크릿, Redis 패스워드 +- **Ingress**: API 라우팅 규칙 +- **Deployment**: 각 서비스별 배포 설정 +- **Service**: 각 서비스별 ClusterIP 서비스 +- **Secret**: 각 서비스별 데이터베이스 연결 정보 + +### Overlay 구조 + +각 환경(dev/staging/prod)별로 Base를 오버라이드합니다. + +#### DEV 환경 +- **Replicas**: 1 +- **Resources**: CPU 256m-1024m, Memory 256Mi-1024Mi +- **Profile**: dev +- **DDL**: update +- **Log Level**: DEBUG +- **Image Tag**: dev-{timestamp} + +#### STAGING 환경 +- **Replicas**: 2 +- **Resources**: CPU 512m-2048m, Memory 512Mi-2048Mi +- **Profile**: staging +- **DDL**: validate +- **Log Level**: INFO +- **Image Tag**: staging-{timestamp} +- **SSL**: Enabled + +#### PROD 환경 +- **Replicas**: 3 +- **Resources**: CPU 1024m-4096m, Memory 1024Mi-4096Mi +- **Profile**: prod +- **DDL**: validate +- **Log Level**: WARN +- **JWT Expiration**: 짧게 설정 (보안 강화) +- **Image Tag**: prod-{timestamp} +- **SSL**: Enabled + +--- + +## GitHub Actions 워크플로우 + +### 트리거 조건 + +1. **Push 이벤트**: + - 브랜치: `main`, `develop` + - 경로: 서비스 코드 변경 시 (`user/**`, `meeting/**` 등) + +2. **Pull Request**: + - 대상 브랜치: `main` + +3. **수동 실행 (workflow_dispatch)**: + - Environment 선택: dev/staging/prod + - SonarQube 분석 스킵 선택: true/false + +### 워크플로우 단계 + +#### 1. Build Job +1. 소스코드 체크아웃 +2. JDK 21 설정 +3. 환경 결정 (input 또는 기본값 dev) +4. Gradle 빌드 (테스트 제외) +5. SonarQube 분석 (선택적) +6. 빌드 아티팩트 업로드 +7. 이미지 태그 생성 (타임스탬프 기반) + +#### 2. Release Job +1. 빌드 아티팩트 다운로드 +2. Docker Buildx 설정 +3. Docker Hub 로그인 (Rate Limit 방지) +4. ACR 로그인 +5. 각 서비스별 Docker 이미지 빌드 및 푸시 + +#### 3. Deploy Job +1. Azure CLI 설치 및 로그인 +2. kubectl 설정 +3. AKS Credentials 가져오기 +4. 네임스페이스 생성 +5. Kustomize 설치 +6. 이미지 태그 업데이트 +7. 매니페스트 적용 +8. Deployment Ready 대기 + +--- + +## 배포 방법 + +### 1. 자동 배포 (Push/PR) + +코드를 `main` 또는 `develop` 브랜치에 push하면 자동으로 dev 환경에 배포됩니다. + +```bash +git add . +git commit -m "feat: 새로운 기능 추가" +git push origin develop +``` + +### 2. 수동 배포 (GitHub Actions UI) + +1. GitHub Repository > Actions 탭 이동 +2. "Backend Services CI/CD" 워크플로우 선택 +3. "Run workflow" 버튼 클릭 +4. 환경 선택 (dev/staging/prod) +5. SonarQube 분석 스킵 여부 선택 +6. "Run workflow" 실행 + +### 3. 로컬에서 수동 배포 + +```bash +# 스크립트 실행 권한 확인 +chmod +x .github/scripts/deploy-actions.sh + +# DEV 환경에 배포 (기본) +./.github/scripts/deploy-actions.sh dev latest + +# STAGING 환경에 특정 이미지 태그로 배포 +./.github/scripts/deploy-actions.sh staging 20250127120000 + +# PROD 환경에 배포 +./.github/scripts/deploy-actions.sh prod 20250127120000 +``` + +--- + +## 롤백 방법 + +### 1. GitHub Actions를 통한 롤백 + +1. GitHub > Actions > 성공한 이전 워크플로우 선택 +2. "Re-run all jobs" 클릭 +3. 이전 버전으로 재배포됨 + +### 2. kubectl을 이용한 롤백 + +```bash +# 특정 Revision으로 롤백 +kubectl rollout undo deployment/user -n hgzero --to-revision=2 + +# 이전 버전으로 롤백 +kubectl rollout undo deployment/user -n hgzero + +# 롤백 상태 확인 +kubectl rollout status deployment/user -n hgzero + +# Rollout 히스토리 확인 +kubectl rollout history deployment/user -n hgzero +``` + +### 3. 수동 스크립트를 이용한 롤백 + +```bash +# 이전 안정 버전의 이미지 태그로 배포 +./.github/scripts/deploy-actions.sh dev 20250126110000 +``` + +--- + +## SonarQube 설정 + +### 프로젝트 생성 + +각 서비스별로 SonarQube 프로젝트를 생성하세요: +- hgzero-user-dev +- hgzero-meeting-dev +- hgzero-stt-dev +- hgzero-ai-dev +- hgzero-notification-dev + +### Quality Gate 설정 + +기본 Quality Gate 설정: +- **Coverage**: >= 80% +- **Duplicated Lines**: <= 3% +- **Maintainability Rating**: <= A +- **Reliability Rating**: <= A +- **Security Rating**: <= A + +### Gradle 설정 (이미 구성됨) + +```gradle +// build.gradle +plugins { + id 'org.sonarqube' version '4.0.0.2929' + id 'jacoco' +} + +sonarqube { + properties { + property "sonar.projectKey", "hgzero-${project.name}" + property "sonar.projectName", "hgzero-${project.name}" + } +} + +jacocoTestReport { + reports { + xml.enabled true + } +} +``` + +--- + +## 트러블슈팅 + +### 1. 이미지 Pull 실패 + +**증상**: `ImagePullBackOff` 또는 `ErrImagePull` + +**해결 방법**: +```bash +# ACR 로그인 테스트 +az acr login --name acrdigitalgarage02 + +# Image Pull Secret 재생성 +kubectl delete secret acr-secret -n hgzero +kubectl create secret docker-registry acr-secret \ + --docker-server=acrdigitalgarage02.azurecr.io \ + --docker-username=acrdigitalgarage02 \ + --docker-password={ACR_PASSWORD} \ + -n hgzero +``` + +### 2. Deployment 타임아웃 + +**증상**: `deployment "user" exceeded its progress deadline` + +**해결 방법**: +```bash +# Pod 상태 확인 +kubectl get pods -n hgzero + +# Pod 로그 확인 +kubectl logs -n hgzero {pod-name} + +# Events 확인 +kubectl describe pod -n hgzero {pod-name} +``` + +### 3. Health Check 실패 + +**증상**: Deployment가 Ready 상태로 전환되지 않음 + +**해결 방법**: +```bash +# Actuator health 엔드포인트 확인 +kubectl exec -n hgzero {pod-name} -- curl http://localhost:8080/actuator/health + +# 애플리케이션 로그 확인 +kubectl logs -n hgzero {pod-name} -f +``` + +### 4. Kustomize 빌드 오류 + +**증상**: `kustomize build` 실패 + +**해결 방법**: +```bash +# 로컬에서 Kustomize 검증 +kubectl kustomize .github/kustomize/overlays/dev + +# YAML 문법 검증 +yamllint .github/kustomize/overlays/dev/*.yaml +``` + +### 5. SonarQube 연결 실패 + +**증상**: SonarQube Analysis 단계에서 연결 오류 + +**해결 방법**: +1. SONAR_HOST_URL이 올바른지 확인 +2. SONAR_TOKEN이 유효한지 확인 +3. SonarQube 서비스 상태 확인: + ```bash + kubectl get pods -n sonarqube + kubectl logs -n sonarqube {sonarqube-pod} + ``` + +### 6. 환경 변수 로드 실패 + +**증상**: 환경 설정 파일을 찾을 수 없음 + +**해결 방법**: +```bash +# 파일 존재 확인 +ls -la .github/config/ + +# 파일 내용 확인 +cat .github/config/deploy_env_vars_dev +``` + +--- + +## 참고 자료 + +- [Kustomize 공식 문서](https://kustomize.io/) +- [GitHub Actions 문서](https://docs.github.com/en/actions) +- [Azure Container Registry 문서](https://docs.microsoft.com/en-us/azure/container-registry/) +- [AKS 문서](https://docs.microsoft.com/en-us/azure/aks/) +- [SonarQube 문서](https://docs.sonarqube.org/) + +--- + +## 문의 및 지원 + +문제가 발생하거나 질문이 있으시면: +1. GitHub Issues에 등록 +2. DevOps 팀에 문의 (송주영) +3. Slack #devops 채널 + +--- + +**작성일**: 2025-01-27 +**작성자**: DevOps Team (주영) +**버전**: 1.0.0 diff --git a/.github/config/deploy_env_vars_dev b/.github/config/deploy_env_vars_dev new file mode 100644 index 0000000..e45920c --- /dev/null +++ b/.github/config/deploy_env_vars_dev @@ -0,0 +1,3 @@ +# dev Environment Configuration +resource_group=rg-digitalgarage-02 +cluster_name=aks-digitalgarage-02 diff --git a/.github/config/deploy_env_vars_prod b/.github/config/deploy_env_vars_prod new file mode 100644 index 0000000..c2bdc92 --- /dev/null +++ b/.github/config/deploy_env_vars_prod @@ -0,0 +1,3 @@ +# prod Environment Configuration +resource_group=rg-digitalgarage-02 +cluster_name=aks-digitalgarage-02 diff --git a/.github/config/deploy_env_vars_staging b/.github/config/deploy_env_vars_staging new file mode 100644 index 0000000..5fa855c --- /dev/null +++ b/.github/config/deploy_env_vars_staging @@ -0,0 +1,3 @@ +# staging Environment Configuration +resource_group=rg-digitalgarage-02 +cluster_name=aks-digitalgarage-02 diff --git a/.github/kustomize/base/ai/deployment.yaml b/.github/kustomize/base/ai/deployment.yaml new file mode 100644 index 0000000..2e41bc5 --- /dev/null +++ b/.github/kustomize/base/ai/deployment.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ai + labels: + app: ai +spec: + replicas: 1 + selector: + matchLabels: + app: ai + template: + metadata: + labels: + app: ai + spec: + containers: + - name: ai + image: acrdigitalgarage02.azurecr.io/hgzero/ai:latest + ports: + - containerPort: 8080 + protocol: TCP + envFrom: + - configMapRef: + name: cm-common + - secretRef: + name: secret-common + env: + - name: DB_URL + valueFrom: + secretKeyRef: + name: secret-ai + key: DB_URL + - name: DB_USERNAME + valueFrom: + secretKeyRef: + name: secret-ai + key: DB_USERNAME + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: secret-ai + key: DB_PASSWORD + resources: + requests: + cpu: 256m + memory: 256Mi + limits: + cpu: 1024m + memory: 1024Mi + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 diff --git a/.github/kustomize/base/ai/secret-ai.yaml b/.github/kustomize/base/ai/secret-ai.yaml new file mode 100644 index 0000000..7df0040 --- /dev/null +++ b/.github/kustomize/base/ai/secret-ai.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-ai + labels: + app: ai +type: Opaque +stringData: + DB_URL: "jdbc:postgresql://postgres-service:5432/aidb" + DB_USERNAME: "aiuser" + DB_PASSWORD: "aipass123" diff --git a/.github/kustomize/base/ai/service.yaml b/.github/kustomize/base/ai/service.yaml new file mode 100644 index 0000000..a287430 --- /dev/null +++ b/.github/kustomize/base/ai/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: ai + labels: + app: ai +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: ai diff --git a/.github/kustomize/base/common/cm-common.yaml b/.github/kustomize/base/common/cm-common.yaml new file mode 100644 index 0000000..1525bfc --- /dev/null +++ b/.github/kustomize/base/common/cm-common.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-common +data: + # Spring Profiles + SPRING_PROFILES_ACTIVE: "dev" + + # Database Configuration + DDL_AUTO: "update" + SHOW_SQL: "true" + + # JWT Configuration + JWT_ACCESS_TOKEN_EXPIRATION: "3600000" # 1 hour + JWT_REFRESH_TOKEN_EXPIRATION: "86400000" # 24 hours + + # Logging Configuration + LOG_LEVEL: "INFO" + + # Application Configuration + SERVER_PORT: "8080" + + # Redis Configuration + REDIS_HOST: "redis-service" + REDIS_PORT: "6379" diff --git a/.github/kustomize/base/common/ingress.yaml b/.github/kustomize/base/common/ingress.yaml new file mode 100644 index 0000000..6ac515e --- /dev/null +++ b/.github/kustomize/base/common/ingress.yaml @@ -0,0 +1,48 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: hgzero + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + nginx.ingress.kubernetes.io/ssl-redirect: "false" +spec: + ingressClassName: nginx + rules: + - host: hgzero-api.20.214.196.128.nip.io + http: + paths: + - path: /api/users + pathType: Prefix + backend: + service: + name: user + port: + number: 8080 + - path: /api/meetings + pathType: Prefix + backend: + service: + name: meeting + port: + number: 8080 + - path: /api/stt + pathType: Prefix + backend: + service: + name: stt + port: + number: 8080 + - path: /api/ai + pathType: Prefix + backend: + service: + name: ai + port: + number: 8080 + - path: /api/notifications + pathType: Prefix + backend: + service: + name: notification + port: + number: 8080 diff --git a/.github/kustomize/base/common/secret-common.yaml b/.github/kustomize/base/common/secret-common.yaml new file mode 100644 index 0000000..1c231d0 --- /dev/null +++ b/.github/kustomize/base/common/secret-common.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-common +type: Opaque +stringData: + # JWT Secret Key (Base64 encoded in production) + JWT_SECRET_KEY: "hgzero-jwt-secret-key-change-in-production" + + # Redis Password + REDIS_PASSWORD: "redis-password-change-in-production" diff --git a/.github/kustomize/base/common/secret-imagepull.yaml b/.github/kustomize/base/common/secret-imagepull.yaml new file mode 100644 index 0000000..0a4ff55 --- /dev/null +++ b/.github/kustomize/base/common/secret-imagepull.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: acr-secret +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: e30K diff --git a/.github/kustomize/base/kustomization.yaml b/.github/kustomize/base/kustomization.yaml new file mode 100644 index 0000000..076e551 --- /dev/null +++ b/.github/kustomize/base/kustomization.yaml @@ -0,0 +1,49 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +metadata: + name: hgzero-base + +resources: + # Common resources + - common/cm-common.yaml + - common/secret-common.yaml + - common/secret-imagepull.yaml + - common/ingress.yaml + + # User service + - user/deployment.yaml + - user/service.yaml + - user/secret-user.yaml + + # Meeting service + - meeting/deployment.yaml + - meeting/service.yaml + - meeting/secret-meeting.yaml + + # STT service + - stt/deployment.yaml + - stt/service.yaml + - stt/secret-stt.yaml + + # AI service + - ai/deployment.yaml + - ai/service.yaml + - ai/secret-ai.yaml + + # Notification service + - notification/deployment.yaml + - notification/service.yaml + - notification/secret-notification.yaml + +images: + - name: acrdigitalgarage02.azurecr.io/hgzero/user + newTag: latest + - name: acrdigitalgarage02.azurecr.io/hgzero/meeting + newTag: latest + - name: acrdigitalgarage02.azurecr.io/hgzero/stt + newTag: latest + - name: acrdigitalgarage02.azurecr.io/hgzero/ai + newTag: latest + - name: acrdigitalgarage02.azurecr.io/hgzero/notification + newTag: latest diff --git a/.github/kustomize/base/meeting/deployment.yaml b/.github/kustomize/base/meeting/deployment.yaml new file mode 100644 index 0000000..cdafa45 --- /dev/null +++ b/.github/kustomize/base/meeting/deployment.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: meeting + labels: + app: meeting +spec: + replicas: 1 + selector: + matchLabels: + app: meeting + template: + metadata: + labels: + app: meeting + spec: + containers: + - name: meeting + image: acrdigitalgarage02.azurecr.io/hgzero/meeting:latest + ports: + - containerPort: 8080 + protocol: TCP + envFrom: + - configMapRef: + name: cm-common + - secretRef: + name: secret-common + env: + - name: DB_URL + valueFrom: + secretKeyRef: + name: secret-meeting + key: DB_URL + - name: DB_USERNAME + valueFrom: + secretKeyRef: + name: secret-meeting + key: DB_USERNAME + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: secret-meeting + key: DB_PASSWORD + resources: + requests: + cpu: "256m" + memory: "256Mi" + limits: + cpu: "1024m" + memory: "1024Mi" + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 diff --git a/.github/kustomize/base/meeting/secret-meeting.yaml b/.github/kustomize/base/meeting/secret-meeting.yaml new file mode 100644 index 0000000..c2a4084 --- /dev/null +++ b/.github/kustomize/base/meeting/secret-meeting.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-meeting +type: Opaque +stringData: + # Meeting Service Database Configuration (Development) + DB_URL: "jdbc:postgresql://postgres-meeting:5432/meeting" + DB_USERNAME: "meeting_user" + DB_PASSWORD: "meeting_password" diff --git a/.github/kustomize/base/meeting/service.yaml b/.github/kustomize/base/meeting/service.yaml new file mode 100644 index 0000000..1200fb7 --- /dev/null +++ b/.github/kustomize/base/meeting/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: meeting + labels: + app: meeting +spec: + type: ClusterIP + selector: + app: meeting + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http diff --git a/.github/kustomize/base/notification/deployment.yaml b/.github/kustomize/base/notification/deployment.yaml new file mode 100644 index 0000000..080e70c --- /dev/null +++ b/.github/kustomize/base/notification/deployment.yaml @@ -0,0 +1,68 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: notification + labels: + app: notification + system: hgzero +spec: + replicas: 1 + selector: + matchLabels: + app: notification + template: + metadata: + labels: + app: notification + system: hgzero + spec: + containers: + - name: notification + image: acrdigitalgarage02.azurecr.io/hgzero/notification:latest + ports: + - containerPort: 8080 + protocol: TCP + envFrom: + - configMapRef: + name: cm-common + - secretRef: + name: secret-common + env: + - name: DB_URL + valueFrom: + secretKeyRef: + name: secret-notification + key: DB_URL + - name: DB_USERNAME + valueFrom: + secretKeyRef: + name: secret-notification + key: DB_USERNAME + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: secret-notification + key: DB_PASSWORD + resources: + requests: + cpu: "256m" + memory: "256Mi" + limits: + cpu: "1024m" + memory: "1024Mi" + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 diff --git a/.github/kustomize/base/notification/secret-notification.yaml b/.github/kustomize/base/notification/secret-notification.yaml new file mode 100644 index 0000000..6c5cfa2 --- /dev/null +++ b/.github/kustomize/base/notification/secret-notification.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-notification + labels: + app: notification + system: hgzero +type: Opaque +stringData: + DB_URL: "jdbc:postgresql://postgres-service:5432/notification_db" + DB_USERNAME: "notification_user" + DB_PASSWORD: "notification_pass" diff --git a/.github/kustomize/base/notification/service.yaml b/.github/kustomize/base/notification/service.yaml new file mode 100644 index 0000000..d123457 --- /dev/null +++ b/.github/kustomize/base/notification/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: notification + labels: + app: notification + system: hgzero +spec: + type: ClusterIP + selector: + app: notification + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http diff --git a/.github/kustomize/base/stt/deployment.yaml b/.github/kustomize/base/stt/deployment.yaml new file mode 100644 index 0000000..b14c27c --- /dev/null +++ b/.github/kustomize/base/stt/deployment.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stt + labels: + app: stt +spec: + replicas: 1 + selector: + matchLabels: + app: stt + template: + metadata: + labels: + app: stt + spec: + containers: + - name: stt + image: acrdigitalgarage02.azurecr.io/hgzero/stt:latest + ports: + - containerPort: 8080 + protocol: TCP + envFrom: + - configMapRef: + name: cm-common + - secretRef: + name: secret-common + env: + - name: DB_URL + valueFrom: + secretKeyRef: + name: secret-stt + key: DB_URL + - name: DB_USERNAME + valueFrom: + secretKeyRef: + name: secret-stt + key: DB_USERNAME + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: secret-stt + key: DB_PASSWORD + resources: + requests: + cpu: 256m + memory: 256Mi + limits: + cpu: 1024m + memory: 1024Mi + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 diff --git a/.github/kustomize/base/stt/secret-stt.yaml b/.github/kustomize/base/stt/secret-stt.yaml new file mode 100644 index 0000000..0d8825a --- /dev/null +++ b/.github/kustomize/base/stt/secret-stt.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-stt +type: Opaque +stringData: + DB_URL: "jdbc:postgresql://postgres-service:5432/sttdb" + DB_USERNAME: "sttuser" + DB_PASSWORD: "sttpass" diff --git a/.github/kustomize/base/stt/service.yaml b/.github/kustomize/base/stt/service.yaml new file mode 100644 index 0000000..29c99d0 --- /dev/null +++ b/.github/kustomize/base/stt/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: stt + labels: + app: stt +spec: + type: ClusterIP + selector: + app: stt + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http diff --git a/.github/kustomize/base/user/deployment.yaml b/.github/kustomize/base/user/deployment.yaml new file mode 100644 index 0000000..21d1469 --- /dev/null +++ b/.github/kustomize/base/user/deployment.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: user + labels: + app: user +spec: + replicas: 1 + selector: + matchLabels: + app: user + template: + metadata: + labels: + app: user + spec: + containers: + - name: user + image: acrdigitalgarage02.azurecr.io/hgzero/user:latest + ports: + - containerPort: 8080 + protocol: TCP + envFrom: + - configMapRef: + name: cm-common + - secretRef: + name: secret-common + env: + - name: DB_URL + valueFrom: + secretKeyRef: + name: secret-user + key: DB_URL + - name: DB_USERNAME + valueFrom: + secretKeyRef: + name: secret-user + key: DB_USERNAME + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: secret-user + key: DB_PASSWORD + resources: + requests: + cpu: "256m" + memory: "256Mi" + limits: + cpu: "1024m" + memory: "1024Mi" + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 diff --git a/.github/kustomize/base/user/secret-user.yaml b/.github/kustomize/base/user/secret-user.yaml new file mode 100644 index 0000000..8dcd240 --- /dev/null +++ b/.github/kustomize/base/user/secret-user.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-user + labels: + app: user +type: Opaque +stringData: + DB_URL: "jdbc:mysql://mysql-user:3306/userdb?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true" + DB_USERNAME: "user" + DB_PASSWORD: "user1234" diff --git a/.github/kustomize/base/user/service.yaml b/.github/kustomize/base/user/service.yaml new file mode 100644 index 0000000..4e32d39 --- /dev/null +++ b/.github/kustomize/base/user/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: user + labels: + app: user +spec: + type: ClusterIP + selector: + app: user + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http diff --git a/.github/kustomize/overlays/dev/cm-common-patch.yaml b/.github/kustomize/overlays/dev/cm-common-patch.yaml new file mode 100644 index 0000000..4047dd8 --- /dev/null +++ b/.github/kustomize/overlays/dev/cm-common-patch.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-common +data: + # Spring Profiles + SPRING_PROFILES_ACTIVE: "dev" + + # Database Configuration + DDL_AUTO: "update" + SHOW_SQL: "true" + + # JWT Configuration + JWT_ACCESS_TOKEN_EXPIRATION: "3600000" # 1 hour + JWT_REFRESH_TOKEN_EXPIRATION: "86400000" # 24 hours + + # Logging Configuration + LOG_LEVEL: "DEBUG" diff --git a/.github/kustomize/overlays/dev/deployment-ai-patch.yaml b/.github/kustomize/overlays/dev/deployment-ai-patch.yaml new file mode 100644 index 0000000..5c37383 --- /dev/null +++ b/.github/kustomize/overlays/dev/deployment-ai-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ai +spec: + replicas: 1 + template: + spec: + containers: + - name: ai + resources: + requests: + cpu: "256m" + memory: "256Mi" + limits: + cpu: "1024m" + memory: "1024Mi" diff --git a/.github/kustomize/overlays/dev/deployment-meeting-patch.yaml b/.github/kustomize/overlays/dev/deployment-meeting-patch.yaml new file mode 100644 index 0000000..797ff08 --- /dev/null +++ b/.github/kustomize/overlays/dev/deployment-meeting-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: meeting +spec: + replicas: 1 + template: + spec: + containers: + - name: meeting + resources: + requests: + cpu: "256m" + memory: "256Mi" + limits: + cpu: "1024m" + memory: "1024Mi" diff --git a/.github/kustomize/overlays/dev/deployment-notification-patch.yaml b/.github/kustomize/overlays/dev/deployment-notification-patch.yaml new file mode 100644 index 0000000..ed4cea8 --- /dev/null +++ b/.github/kustomize/overlays/dev/deployment-notification-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: notification +spec: + replicas: 1 + template: + spec: + containers: + - name: notification + resources: + requests: + cpu: "256m" + memory: "256Mi" + limits: + cpu: "1024m" + memory: "1024Mi" diff --git a/.github/kustomize/overlays/dev/deployment-stt-patch.yaml b/.github/kustomize/overlays/dev/deployment-stt-patch.yaml new file mode 100644 index 0000000..f9ad444 --- /dev/null +++ b/.github/kustomize/overlays/dev/deployment-stt-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stt +spec: + replicas: 1 + template: + spec: + containers: + - name: stt + resources: + requests: + cpu: "256m" + memory: "256Mi" + limits: + cpu: "1024m" + memory: "1024Mi" diff --git a/.github/kustomize/overlays/dev/deployment-user-patch.yaml b/.github/kustomize/overlays/dev/deployment-user-patch.yaml new file mode 100644 index 0000000..1db2284 --- /dev/null +++ b/.github/kustomize/overlays/dev/deployment-user-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: user +spec: + replicas: 1 + template: + spec: + containers: + - name: user + resources: + requests: + cpu: "256m" + memory: "256Mi" + limits: + cpu: "1024m" + memory: "1024Mi" diff --git a/.github/kustomize/overlays/dev/ingress-patch.yaml b/.github/kustomize/overlays/dev/ingress-patch.yaml new file mode 100644 index 0000000..6ac515e --- /dev/null +++ b/.github/kustomize/overlays/dev/ingress-patch.yaml @@ -0,0 +1,48 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: hgzero + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + nginx.ingress.kubernetes.io/ssl-redirect: "false" +spec: + ingressClassName: nginx + rules: + - host: hgzero-api.20.214.196.128.nip.io + http: + paths: + - path: /api/users + pathType: Prefix + backend: + service: + name: user + port: + number: 8080 + - path: /api/meetings + pathType: Prefix + backend: + service: + name: meeting + port: + number: 8080 + - path: /api/stt + pathType: Prefix + backend: + service: + name: stt + port: + number: 8080 + - path: /api/ai + pathType: Prefix + backend: + service: + name: ai + port: + number: 8080 + - path: /api/notifications + pathType: Prefix + backend: + service: + name: notification + port: + number: 8080 diff --git a/.github/kustomize/overlays/dev/kustomization.yaml b/.github/kustomize/overlays/dev/kustomization.yaml new file mode 100644 index 0000000..edb6bc5 --- /dev/null +++ b/.github/kustomize/overlays/dev/kustomization.yaml @@ -0,0 +1,84 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: hgzero + +resources: + - ../../base + +patches: + # Common patches + - path: cm-common-patch.yaml + target: + kind: ConfigMap + name: cm-common + - path: secret-common-patch.yaml + target: + kind: Secret + name: secret-common + - path: ingress-patch.yaml + target: + kind: Ingress + name: hgzero + + # User service patches + - path: deployment-user-patch.yaml + target: + kind: Deployment + name: user + - path: secret-user-patch.yaml + target: + kind: Secret + name: secret-user + + # Meeting service patches + - path: deployment-meeting-patch.yaml + target: + kind: Deployment + name: meeting + - path: secret-meeting-patch.yaml + target: + kind: Secret + name: secret-meeting + + # STT service patches + - path: deployment-stt-patch.yaml + target: + kind: Deployment + name: stt + - path: secret-stt-patch.yaml + target: + kind: Secret + name: secret-stt + + # AI service patches + - path: deployment-ai-patch.yaml + target: + kind: Deployment + name: ai + - path: secret-ai-patch.yaml + target: + kind: Secret + name: secret-ai + + # Notification service patches + - path: deployment-notification-patch.yaml + target: + kind: Deployment + name: notification + - path: secret-notification-patch.yaml + target: + kind: Secret + name: secret-notification + +images: + - name: acrdigitalgarage02.azurecr.io/hgzero/user + newTag: dev-latest + - name: acrdigitalgarage02.azurecr.io/hgzero/meeting + newTag: dev-latest + - name: acrdigitalgarage02.azurecr.io/hgzero/stt + newTag: dev-latest + - name: acrdigitalgarage02.azurecr.io/hgzero/ai + newTag: dev-latest + - name: acrdigitalgarage02.azurecr.io/hgzero/notification + newTag: dev-latest diff --git a/.github/kustomize/overlays/dev/secret-ai-patch.yaml b/.github/kustomize/overlays/dev/secret-ai-patch.yaml new file mode 100644 index 0000000..e99705c --- /dev/null +++ b/.github/kustomize/overlays/dev/secret-ai-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-ai + labels: + app: ai +type: Opaque +stringData: + DB_URL: "jdbc:postgresql://postgres-service-dev:5432/aidb_dev" + DB_USERNAME: "aiuser_dev" + DB_PASSWORD: "aipass_dev123" diff --git a/.github/kustomize/overlays/dev/secret-common-patch.yaml b/.github/kustomize/overlays/dev/secret-common-patch.yaml new file mode 100644 index 0000000..7ec3e2f --- /dev/null +++ b/.github/kustomize/overlays/dev/secret-common-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-common +type: Opaque +stringData: + # JWT Secret Key (개발용) + JWT_SECRET_KEY: "hgzero-jwt-secret-key-change-in-production" + + # Redis Password (개발용) + REDIS_PASSWORD: "redis-password-change-in-production" diff --git a/.github/kustomize/overlays/dev/secret-meeting-patch.yaml b/.github/kustomize/overlays/dev/secret-meeting-patch.yaml new file mode 100644 index 0000000..aa64ac7 --- /dev/null +++ b/.github/kustomize/overlays/dev/secret-meeting-patch.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-meeting +type: Opaque +stringData: + # Meeting Service Database Configuration (Development) + DB_URL: "jdbc:postgresql://postgres-meeting-dev:5432/meeting_dev" + DB_USERNAME: "meeting_dev_user" + DB_PASSWORD: "meeting_dev_password" diff --git a/.github/kustomize/overlays/dev/secret-notification-patch.yaml b/.github/kustomize/overlays/dev/secret-notification-patch.yaml new file mode 100644 index 0000000..36a7b59 --- /dev/null +++ b/.github/kustomize/overlays/dev/secret-notification-patch.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-notification + labels: + app: notification + system: hgzero +type: Opaque +stringData: + DB_URL: "jdbc:postgresql://postgres-service-dev:5432/notification_db_dev" + DB_USERNAME: "notification_dev_user" + DB_PASSWORD: "notification_dev_pass" diff --git a/.github/kustomize/overlays/dev/secret-stt-patch.yaml b/.github/kustomize/overlays/dev/secret-stt-patch.yaml new file mode 100644 index 0000000..1d0c9e6 --- /dev/null +++ b/.github/kustomize/overlays/dev/secret-stt-patch.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-stt +type: Opaque +stringData: + DB_URL: "jdbc:postgresql://postgres-service-dev:5432/sttdb_dev" + DB_USERNAME: "sttuser_dev" + DB_PASSWORD: "sttpass_dev" diff --git a/.github/kustomize/overlays/dev/secret-user-patch.yaml b/.github/kustomize/overlays/dev/secret-user-patch.yaml new file mode 100644 index 0000000..be99004 --- /dev/null +++ b/.github/kustomize/overlays/dev/secret-user-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-user + labels: + app: user +type: Opaque +stringData: + DB_URL: "jdbc:mysql://mysql-user-dev:3306/userdb_dev?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true" + DB_USERNAME: "user_dev" + DB_PASSWORD: "user_dev1234" diff --git a/.github/kustomize/overlays/prod/cm-common-patch.yaml b/.github/kustomize/overlays/prod/cm-common-patch.yaml new file mode 100644 index 0000000..5874c91 --- /dev/null +++ b/.github/kustomize/overlays/prod/cm-common-patch.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: common-config + namespace: hgzero +data: + SPRING_PROFILES_ACTIVE: "prod" + DDL_AUTO: "validate" + LOG_LEVEL: "WARN" + JWT_ACCESS_TOKEN_EXPIRATION: "1800000" + JWT_REFRESH_TOKEN_EXPIRATION: "43200000" + REDIS_HOST: "redis-svc.hgzero.svc.cluster.local" + REDIS_PORT: "6379" + KAFKA_BOOTSTRAP_SERVERS: "kafka-svc.hgzero.svc.cluster.local:9092" diff --git a/.github/kustomize/overlays/prod/deployment-ai-patch.yaml b/.github/kustomize/overlays/prod/deployment-ai-patch.yaml new file mode 100644 index 0000000..7fa27c9 --- /dev/null +++ b/.github/kustomize/overlays/prod/deployment-ai-patch.yaml @@ -0,0 +1,18 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ai-deploy + namespace: hgzero +spec: + replicas: 3 + template: + spec: + containers: + - name: ai-container + resources: + requests: + cpu: "1024m" + memory: "1024Mi" + limits: + cpu: "4096m" + memory: "4096Mi" diff --git a/.github/kustomize/overlays/prod/deployment-meeting-patch.yaml b/.github/kustomize/overlays/prod/deployment-meeting-patch.yaml new file mode 100644 index 0000000..11fcfeb --- /dev/null +++ b/.github/kustomize/overlays/prod/deployment-meeting-patch.yaml @@ -0,0 +1,18 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: meeting-deploy + namespace: hgzero +spec: + replicas: 3 + template: + spec: + containers: + - name: meeting-container + resources: + requests: + cpu: "1024m" + memory: "1024Mi" + limits: + cpu: "4096m" + memory: "4096Mi" diff --git a/.github/kustomize/overlays/prod/deployment-notification-patch.yaml b/.github/kustomize/overlays/prod/deployment-notification-patch.yaml new file mode 100644 index 0000000..b832ef2 --- /dev/null +++ b/.github/kustomize/overlays/prod/deployment-notification-patch.yaml @@ -0,0 +1,18 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: notification-deploy + namespace: hgzero +spec: + replicas: 3 + template: + spec: + containers: + - name: notification-container + resources: + requests: + cpu: "1024m" + memory: "1024Mi" + limits: + cpu: "4096m" + memory: "4096Mi" diff --git a/.github/kustomize/overlays/prod/deployment-stt-patch.yaml b/.github/kustomize/overlays/prod/deployment-stt-patch.yaml new file mode 100644 index 0000000..1dc8fb7 --- /dev/null +++ b/.github/kustomize/overlays/prod/deployment-stt-patch.yaml @@ -0,0 +1,18 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stt-deploy + namespace: hgzero +spec: + replicas: 3 + template: + spec: + containers: + - name: stt-container + resources: + requests: + cpu: "1024m" + memory: "1024Mi" + limits: + cpu: "4096m" + memory: "4096Mi" diff --git a/.github/kustomize/overlays/prod/deployment-user-patch.yaml b/.github/kustomize/overlays/prod/deployment-user-patch.yaml new file mode 100644 index 0000000..5eddde0 --- /dev/null +++ b/.github/kustomize/overlays/prod/deployment-user-patch.yaml @@ -0,0 +1,18 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: user-deploy + namespace: hgzero +spec: + replicas: 3 + template: + spec: + containers: + - name: user-container + resources: + requests: + cpu: "1024m" + memory: "1024Mi" + limits: + cpu: "4096m" + memory: "4096Mi" diff --git a/.github/kustomize/overlays/prod/ingress-patch.yaml b/.github/kustomize/overlays/prod/ingress-patch.yaml new file mode 100644 index 0000000..96b2659 --- /dev/null +++ b/.github/kustomize/overlays/prod/ingress-patch.yaml @@ -0,0 +1,49 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: hgzero-ingress + namespace: hgzero + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + ingressClassName: nginx + rules: + - host: hgzero-api.example.com + http: + paths: + - path: /user(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: user-svc + port: + number: 8080 + - path: /meeting(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: meeting-svc + port: + number: 8081 + - path: /stt(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: stt-svc + port: + number: 8082 + - path: /ai(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: ai-svc + port: + number: 8083 + - path: /notification(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: notification-svc + port: + number: 8084 diff --git a/.github/kustomize/overlays/prod/kustomization.yaml b/.github/kustomize/overlays/prod/kustomization.yaml new file mode 100644 index 0000000..134c306 --- /dev/null +++ b/.github/kustomize/overlays/prod/kustomization.yaml @@ -0,0 +1,84 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: hgzero + +bases: + - ../../base + +patches: + # Common patches + - path: cm-common-patch.yaml + target: + kind: ConfigMap + name: common-config + - path: secret-common-patch.yaml + target: + kind: Secret + name: common-secret + - path: ingress-patch.yaml + target: + kind: Ingress + name: hgzero-ingress + + # User service patches + - path: deployment-user-patch.yaml + target: + kind: Deployment + name: user-deploy + - path: secret-user-patch.yaml + target: + kind: Secret + name: user-secret + + # Meeting service patches + - path: deployment-meeting-patch.yaml + target: + kind: Deployment + name: meeting-deploy + - path: secret-meeting-patch.yaml + target: + kind: Secret + name: meeting-secret + + # STT service patches + - path: deployment-stt-patch.yaml + target: + kind: Deployment + name: stt-deploy + - path: secret-stt-patch.yaml + target: + kind: Secret + name: stt-secret + + # AI service patches + - path: deployment-ai-patch.yaml + target: + kind: Deployment + name: ai-deploy + - path: secret-ai-patch.yaml + target: + kind: Secret + name: ai-secret + + # Notification service patches + - path: deployment-notification-patch.yaml + target: + kind: Deployment + name: notification-deploy + - path: secret-notification-patch.yaml + target: + kind: Secret + name: notification-secret + +images: + - name: user-service + newTag: prod-latest + - name: meeting-service + newTag: prod-latest + - name: stt-service + newTag: prod-latest + - name: ai-service + newTag: prod-latest + - name: notification-service + newTag: prod-latest diff --git a/.github/kustomize/overlays/prod/secret-ai-patch.yaml b/.github/kustomize/overlays/prod/secret-ai-patch.yaml new file mode 100644 index 0000000..bf17d50 --- /dev/null +++ b/.github/kustomize/overlays/prod/secret-ai-patch.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: ai-secret + namespace: hgzero +type: Opaque +stringData: + OPENAI_API_KEY: "your-openai-api-key" + LANGCHAIN_API_KEY: "your-langchain-api-key" diff --git a/.github/kustomize/overlays/prod/secret-common-patch.yaml b/.github/kustomize/overlays/prod/secret-common-patch.yaml new file mode 100644 index 0000000..5d14280 --- /dev/null +++ b/.github/kustomize/overlays/prod/secret-common-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: common-secret + namespace: hgzero +type: Opaque +stringData: + JWT_SECRET_KEY: "your-prod-secret-key-change-this-in-production" + REDIS_PASSWORD: "your-prod-redis-password" + KAFKA_USERNAME: "admin" + KAFKA_PASSWORD: "admin-secret" diff --git a/.github/kustomize/overlays/prod/secret-meeting-patch.yaml b/.github/kustomize/overlays/prod/secret-meeting-patch.yaml new file mode 100644 index 0000000..5ad2f33 --- /dev/null +++ b/.github/kustomize/overlays/prod/secret-meeting-patch.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Secret +metadata: + name: meeting-secret + namespace: hgzero +type: Opaque +stringData: + DB_URL: "jdbc:postgresql://postgres-prod:5432/meeting_prod" + DB_USERNAME: "meeting_admin" + DB_PASSWORD: "meeting_prod_password" + OPENAI_API_KEY: "your-openai-api-key" + S3_ACCESS_KEY: "your-s3-access-key" + S3_SECRET_KEY: "your-s3-secret-key" diff --git a/.github/kustomize/overlays/prod/secret-notification-patch.yaml b/.github/kustomize/overlays/prod/secret-notification-patch.yaml new file mode 100644 index 0000000..22f448b --- /dev/null +++ b/.github/kustomize/overlays/prod/secret-notification-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: notification-secret + namespace: hgzero +type: Opaque +stringData: + DB_URL: "jdbc:postgresql://postgres-prod:5432/notification_prod" + DB_USERNAME: "notification_admin" + DB_PASSWORD: "notification_prod_password" + OPENAI_API_KEY: "your-openai-api-key" diff --git a/.github/kustomize/overlays/prod/secret-stt-patch.yaml b/.github/kustomize/overlays/prod/secret-stt-patch.yaml new file mode 100644 index 0000000..acc37b7 --- /dev/null +++ b/.github/kustomize/overlays/prod/secret-stt-patch.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: stt-secret + namespace: hgzero +type: Opaque +stringData: + OPENAI_API_KEY: "your-openai-api-key" + S3_ACCESS_KEY: "your-s3-access-key" + S3_SECRET_KEY: "your-s3-secret-key" diff --git a/.github/kustomize/overlays/prod/secret-user-patch.yaml b/.github/kustomize/overlays/prod/secret-user-patch.yaml new file mode 100644 index 0000000..2f14bb1 --- /dev/null +++ b/.github/kustomize/overlays/prod/secret-user-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: user-secret + namespace: hgzero +type: Opaque +stringData: + DB_URL: "jdbc:postgresql://postgres-prod:5432/user_prod" + DB_USERNAME: "user_admin" + DB_PASSWORD: "user_prod_password" + OPENAI_API_KEY: "your-openai-api-key" diff --git a/.github/kustomize/overlays/staging/README.md b/.github/kustomize/overlays/staging/README.md new file mode 100644 index 0000000..5c034eb --- /dev/null +++ b/.github/kustomize/overlays/staging/README.md @@ -0,0 +1,99 @@ +# STAGING Environment Kustomize Overlay + +## 개요 +STAGING 환경을 위한 Kustomize overlay 설정입니다. + +## 환경 설정 + +### 네임스페이스 +- `hgzero` + +### 공통 설정 +- **프로파일**: staging +- **DDL 모드**: validate +- **로그 레벨**: INFO +- **Ingress 호스트**: hgzero-staging-api.example.com +- **SSL 리다이렉트**: 활성화 + +### 리소스 설정 +- **Replicas**: 2 +- **Resource Requests**: + - CPU: 512m + - Memory: 512Mi +- **Resource Limits**: + - CPU: 2048m + - Memory: 2048Mi + +## 생성된 파일 목록 (총 14개) + +### Common Patches (3개) +1. `cm-common-patch.yaml` - 공통 ConfigMap 패치 +2. `secret-common-patch.yaml` - 공통 Secret 패치 (JWT, Redis) +3. `ingress-patch.yaml` - Ingress 패치 (호스트, SSL) + +### Service-specific Patches (10개) +각 서비스(user, meeting, stt, ai, notification)별 2개 파일: +- `deployment-{서비스명}-patch.yaml` - Deployment 리소스 패치 +- `secret-{서비스명}-patch.yaml` - DB 연결 정보 패치 + +4. `deployment-user-patch.yaml` +5. `secret-user-patch.yaml` +6. `deployment-meeting-patch.yaml` +7. `secret-meeting-patch.yaml` +8. `deployment-stt-patch.yaml` +9. `secret-stt-patch.yaml` +10. `deployment-ai-patch.yaml` +11. `secret-ai-patch.yaml` +12. `deployment-notification-patch.yaml` +13. `secret-notification-patch.yaml` + +### Kustomization (1개) +14. `kustomization.yaml` - Kustomize 설정 파일 + +## 데이터베이스 설정 + +각 서비스별 STAGING 환경 DB 정보: +- **호스트**: {서비스명}-db-staging +- **포트**: 5432 +- **데이터베이스명**: {서비스명}_db_staging +- **사용자명**: {서비스명}_service +- **비밀번호**: stringData로 정의 (실제 환경에서 변경 필요) + +## 이미지 태그 +모든 서비스: `staging-latest` + +## 사용 방법 + +### 1. Kustomize 빌드 확인 +```bash +kubectl kustomize .github/kustomize/overlays/staging +``` + +### 2. STAGING 환경 배포 +```bash +kubectl apply -k .github/kustomize/overlays/staging +``` + +### 3. 배포 상태 확인 +```bash +kubectl get all -n hgzero +``` + +### 4. Secret 업데이트 (실제 배포 시) +```bash +# Secret 파일들의 stringData를 실제 STAGING 환경 값으로 변경 +vi .github/kustomize/overlays/staging/secret-common-patch.yaml +vi .github/kustomize/overlays/staging/secret-user-patch.yaml +# ... (각 서비스별 secret 파일 수정) +``` + +## 주의사항 +1. Secret 파일들의 비밀번호는 반드시 실제 환경에 맞게 변경해야 합니다 +2. Ingress 호스트명을 실제 STAGING 도메인으로 변경해야 합니다 +3. DB 호스트명이 실제 STAGING 환경과 일치하는지 확인해야 합니다 +4. 리소스 제한은 실제 부하 테스트 결과에 따라 조정이 필요할 수 있습니다 + +## 다음 단계 +- PROD 환경 overlay 생성 +- CI/CD 파이프라인과 통합 +- Monitoring 및 Logging 설정 추가 diff --git a/.github/kustomize/overlays/staging/cm-common-patch.yaml b/.github/kustomize/overlays/staging/cm-common-patch.yaml new file mode 100644 index 0000000..c859d70 --- /dev/null +++ b/.github/kustomize/overlays/staging/cm-common-patch.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: hgzero-common-config +data: + SPRING_PROFILES_ACTIVE: "staging" + DDL_AUTO: "validate" + LOG_LEVEL: "INFO" diff --git a/.github/kustomize/overlays/staging/deployment-ai-patch.yaml b/.github/kustomize/overlays/staging/deployment-ai-patch.yaml new file mode 100644 index 0000000..f63445c --- /dev/null +++ b/.github/kustomize/overlays/staging/deployment-ai-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ai-service +spec: + replicas: 2 + template: + spec: + containers: + - name: ai-service + resources: + requests: + memory: "512Mi" + cpu: "512m" + limits: + memory: "2048Mi" + cpu: "2048m" diff --git a/.github/kustomize/overlays/staging/deployment-meeting-patch.yaml b/.github/kustomize/overlays/staging/deployment-meeting-patch.yaml new file mode 100644 index 0000000..0976b94 --- /dev/null +++ b/.github/kustomize/overlays/staging/deployment-meeting-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: meeting-service +spec: + replicas: 2 + template: + spec: + containers: + - name: meeting-service + resources: + requests: + memory: "512Mi" + cpu: "512m" + limits: + memory: "2048Mi" + cpu: "2048m" diff --git a/.github/kustomize/overlays/staging/deployment-notification-patch.yaml b/.github/kustomize/overlays/staging/deployment-notification-patch.yaml new file mode 100644 index 0000000..e4b04c1 --- /dev/null +++ b/.github/kustomize/overlays/staging/deployment-notification-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: notification-service +spec: + replicas: 2 + template: + spec: + containers: + - name: notification-service + resources: + requests: + memory: "512Mi" + cpu: "512m" + limits: + memory: "2048Mi" + cpu: "2048m" diff --git a/.github/kustomize/overlays/staging/deployment-stt-patch.yaml b/.github/kustomize/overlays/staging/deployment-stt-patch.yaml new file mode 100644 index 0000000..48b2409 --- /dev/null +++ b/.github/kustomize/overlays/staging/deployment-stt-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stt-service +spec: + replicas: 2 + template: + spec: + containers: + - name: stt-service + resources: + requests: + memory: "512Mi" + cpu: "512m" + limits: + memory: "2048Mi" + cpu: "2048m" diff --git a/.github/kustomize/overlays/staging/deployment-user-patch.yaml b/.github/kustomize/overlays/staging/deployment-user-patch.yaml new file mode 100644 index 0000000..2a75059 --- /dev/null +++ b/.github/kustomize/overlays/staging/deployment-user-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: user-service +spec: + replicas: 2 + template: + spec: + containers: + - name: user-service + resources: + requests: + memory: "512Mi" + cpu: "512m" + limits: + memory: "2048Mi" + cpu: "2048m" diff --git a/.github/kustomize/overlays/staging/ingress-patch.yaml b/.github/kustomize/overlays/staging/ingress-patch.yaml new file mode 100644 index 0000000..9de7857 --- /dev/null +++ b/.github/kustomize/overlays/staging/ingress-patch.yaml @@ -0,0 +1,46 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: hgzero-ingress + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + rules: + - host: hgzero-staging-api.example.com + http: + paths: + - path: /api/users + pathType: Prefix + backend: + service: + name: user-service + port: + number: 8080 + - path: /api/meetings + pathType: Prefix + backend: + service: + name: meeting-service + port: + number: 8080 + - path: /api/stt + pathType: Prefix + backend: + service: + name: stt-service + port: + number: 8080 + - path: /api/ai + pathType: Prefix + backend: + service: + name: ai-service + port: + number: 8080 + - path: /api/notifications + pathType: Prefix + backend: + service: + name: notification-service + port: + number: 8080 diff --git a/.github/kustomize/overlays/staging/kustomization.yaml b/.github/kustomize/overlays/staging/kustomization.yaml new file mode 100644 index 0000000..4210839 --- /dev/null +++ b/.github/kustomize/overlays/staging/kustomization.yaml @@ -0,0 +1,91 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: hgzero + +resources: +- ../../base + +patches: +# Common patches +- path: cm-common-patch.yaml + target: + kind: ConfigMap + name: hgzero-common-config + +- path: secret-common-patch.yaml + target: + kind: Secret + name: hgzero-common-secret + +- path: ingress-patch.yaml + target: + kind: Ingress + name: hgzero-ingress + +# User service patches +- path: deployment-user-patch.yaml + target: + kind: Deployment + name: user-service + +- path: secret-user-patch.yaml + target: + kind: Secret + name: user-service-secret + +# Meeting service patches +- path: deployment-meeting-patch.yaml + target: + kind: Deployment + name: meeting-service + +- path: secret-meeting-patch.yaml + target: + kind: Secret + name: meeting-service-secret + +# STT service patches +- path: deployment-stt-patch.yaml + target: + kind: Deployment + name: stt-service + +- path: secret-stt-patch.yaml + target: + kind: Secret + name: stt-service-secret + +# AI service patches +- path: deployment-ai-patch.yaml + target: + kind: Deployment + name: ai-service + +- path: secret-ai-patch.yaml + target: + kind: Secret + name: ai-service-secret + +# Notification service patches +- path: deployment-notification-patch.yaml + target: + kind: Deployment + name: notification-service + +- path: secret-notification-patch.yaml + target: + kind: Secret + name: notification-service-secret + +images: +- name: user-service + newTag: staging-latest +- name: meeting-service + newTag: staging-latest +- name: stt-service + newTag: staging-latest +- name: ai-service + newTag: staging-latest +- name: notification-service + newTag: staging-latest diff --git a/.github/kustomize/overlays/staging/secret-ai-patch.yaml b/.github/kustomize/overlays/staging/secret-ai-patch.yaml new file mode 100644 index 0000000..16fe297 --- /dev/null +++ b/.github/kustomize/overlays/staging/secret-ai-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: ai-service-secret +type: Opaque +stringData: + DB_HOST: "ai-db-staging" + DB_PORT: "5432" + DB_NAME: "ai_db_staging" + DB_USERNAME: "ai_service" + DB_PASSWORD: "your-staging-ai-db-password" diff --git a/.github/kustomize/overlays/staging/secret-common-patch.yaml b/.github/kustomize/overlays/staging/secret-common-patch.yaml new file mode 100644 index 0000000..92c4565 --- /dev/null +++ b/.github/kustomize/overlays/staging/secret-common-patch.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: hgzero-common-secret +type: Opaque +stringData: + JWT_SECRET: "your-staging-jwt-secret-key-here-change-in-production" + JWT_EXPIRATION: "3600000" + REDIS_PASSWORD: "your-staging-redis-password" diff --git a/.github/kustomize/overlays/staging/secret-meeting-patch.yaml b/.github/kustomize/overlays/staging/secret-meeting-patch.yaml new file mode 100644 index 0000000..4408298 --- /dev/null +++ b/.github/kustomize/overlays/staging/secret-meeting-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: meeting-service-secret +type: Opaque +stringData: + DB_HOST: "meeting-db-staging" + DB_PORT: "5432" + DB_NAME: "meeting_db_staging" + DB_USERNAME: "meeting_service" + DB_PASSWORD: "your-staging-meeting-db-password" diff --git a/.github/kustomize/overlays/staging/secret-notification-patch.yaml b/.github/kustomize/overlays/staging/secret-notification-patch.yaml new file mode 100644 index 0000000..7314016 --- /dev/null +++ b/.github/kustomize/overlays/staging/secret-notification-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: notification-service-secret +type: Opaque +stringData: + DB_HOST: "notification-db-staging" + DB_PORT: "5432" + DB_NAME: "notification_db_staging" + DB_USERNAME: "notification_service" + DB_PASSWORD: "your-staging-notification-db-password" diff --git a/.github/kustomize/overlays/staging/secret-stt-patch.yaml b/.github/kustomize/overlays/staging/secret-stt-patch.yaml new file mode 100644 index 0000000..726a148 --- /dev/null +++ b/.github/kustomize/overlays/staging/secret-stt-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: stt-service-secret +type: Opaque +stringData: + DB_HOST: "stt-db-staging" + DB_PORT: "5432" + DB_NAME: "stt_db_staging" + DB_USERNAME: "stt_service" + DB_PASSWORD: "your-staging-stt-db-password" diff --git a/.github/kustomize/overlays/staging/secret-user-patch.yaml b/.github/kustomize/overlays/staging/secret-user-patch.yaml new file mode 100644 index 0000000..a956838 --- /dev/null +++ b/.github/kustomize/overlays/staging/secret-user-patch.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: user-service-secret +type: Opaque +stringData: + DB_HOST: "user-db-staging" + DB_PORT: "5432" + DB_NAME: "user_db_staging" + DB_USERNAME: "user_service" + DB_PASSWORD: "your-staging-user-db-password" diff --git a/.github/scripts/deploy-actions.sh b/.github/scripts/deploy-actions.sh new file mode 100755 index 0000000..37d311d --- /dev/null +++ b/.github/scripts/deploy-actions.sh @@ -0,0 +1,69 @@ +#!/bin/bash +set -e + +ENVIRONMENT=${1:-dev} +IMAGE_TAG=${2:-latest} + +echo "🚀 Manual deployment starting..." +echo "Environment: $ENVIRONMENT" +echo "Image Tag: $IMAGE_TAG" + +# Check if kustomize is installed +if ! command -v kustomize &> /dev/null; then + echo "Installing Kustomize..." + curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash + sudo mv kustomize /usr/local/bin/ +fi + +# Load environment variables from .github/config +if [[ -f ".github/config/deploy_env_vars_${ENVIRONMENT}" ]]; then + source ".github/config/deploy_env_vars_${ENVIRONMENT}" + echo "✅ Environment variables loaded for $ENVIRONMENT" +else + echo "❌ Environment configuration file not found: .github/config/deploy_env_vars_${ENVIRONMENT}" + exit 1 +fi + +# Create namespace +echo "📝 Creating namespace hgzero..." +kubectl create namespace hgzero --dry-run=client -o yaml | kubectl apply -f - + +# 환경별 이미지 태그 업데이트 (.github/kustomize 사용) +cd .github/kustomize/overlays/${ENVIRONMENT} + +echo "🔄 Updating image tags..." +# 서비스 배열 정의 +services=(user meeting stt ai notification) + +# 각 서비스별 이미지 태그 업데이트 +for service in "${services[@]}"; do + kustomize edit set image acrdigitalgarage02.azurecr.io/hgzero/$service:${ENVIRONMENT}-${IMAGE_TAG} +done + +echo "🚀 Deploying to Kubernetes..." +# 배포 실행 +kubectl apply -k . + +echo "⏳ Waiting for deployments to be ready..." +# 서비스별 배포 상태 확인 +for service in "${services[@]}"; do + kubectl rollout status deployment/$service -n hgzero --timeout=300s || echo "⚠️ $service deployment timeout" +done + +echo "🔍 Health check..." +# 각 서비스의 Health Check +for service in "${services[@]}"; do + POD=$(kubectl get pod -n hgzero -l app.kubernetes.io/name=$service -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "") + if [[ -n "$POD" ]]; then + kubectl -n hgzero exec $POD -- curl -f http://localhost:8080/actuator/health 2>/dev/null || echo "⚠️ $service health check failed" + else + echo "⚠️ $service pod not found" + fi +done + +echo "📋 Service Information:" +kubectl get pods -n hgzero +kubectl get services -n hgzero +kubectl get ingress -n hgzero + +echo "✅ GitHub Actions deployment completed successfully!" diff --git a/.github/workflows/backend-cicd.yaml b/.github/workflows/backend-cicd.yaml new file mode 100644 index 0000000..3d66175 --- /dev/null +++ b/.github/workflows/backend-cicd.yaml @@ -0,0 +1,276 @@ +name: Backend Services CI/CD + +on: + push: + branches: [ main, develop ] + paths: + - 'user/**' + - 'meeting/**' + - 'stt/**' + - 'ai/**' + - 'notification/**' + - 'common/**' + - '.github/**' + pull_request: + branches: [ main ] + workflow_dispatch: + inputs: + ENVIRONMENT: + description: 'Target environment' + required: true + default: 'dev' + type: choice + options: + - dev + - staging + - prod + SKIP_SONARQUBE: + description: 'Skip SonarQube Analysis' + required: false + default: 'true' + type: choice + options: + - 'true' + - 'false' + +env: + REGISTRY: acrdigitalgarage02.azurecr.io + IMAGE_ORG: hgzero + RESOURCE_GROUP: rg-digitalgarage-02 + AKS_CLUSTER: aks-digitalgarage-02 + NAMESPACE: hgzero + +jobs: + build: + name: Build and Test + runs-on: ubuntu-latest + outputs: + image_tag: ${{ steps.set_outputs.outputs.image_tag }} + environment: ${{ steps.set_outputs.outputs.environment }} + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + cache: 'gradle' + + - name: Determine environment + id: determine_env + run: | + # Use input parameter or default to 'dev' + ENVIRONMENT="${{ github.event.inputs.ENVIRONMENT || 'dev' }}" + echo "environment=$ENVIRONMENT" >> $GITHUB_OUTPUT + + - name: Load environment variables + id: env_vars + run: | + ENV=${{ steps.determine_env.outputs.environment }} + + # Initialize variables with defaults + REGISTRY="acrdigitalgarage02.azurecr.io" + IMAGE_ORG="hgzero" + RESOURCE_GROUP="rg-digitalgarage-02" + AKS_CLUSTER="aks-digitalgarage-02" + NAMESPACE="hgzero" + + # Read environment variables from .github/config file + if [[ -f ".github/config/deploy_env_vars_${ENV}" ]]; then + while IFS= read -r line || [[ -n "$line" ]]; do + # Skip comments and empty lines + [[ "$line" =~ ^#.*$ ]] && continue + [[ -z "$line" ]] && continue + + # Extract key-value pairs + key=$(echo "$line" | cut -d '=' -f1) + value=$(echo "$line" | cut -d '=' -f2-) + + # Override defaults if found in config + case "$key" in + "resource_group") RESOURCE_GROUP="$value" ;; + "cluster_name") AKS_CLUSTER="$value" ;; + esac + done < ".github/config/deploy_env_vars_${ENV}" + fi + + # Export for other jobs + echo "REGISTRY=$REGISTRY" >> $GITHUB_ENV + echo "IMAGE_ORG=$IMAGE_ORG" >> $GITHUB_ENV + echo "RESOURCE_GROUP=$RESOURCE_GROUP" >> $GITHUB_ENV + echo "AKS_CLUSTER=$AKS_CLUSTER" >> $GITHUB_ENV + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: | + ./gradlew build -x test + + - name: SonarQube Analysis & Quality Gate + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + run: | + # Check if SonarQube should be skipped + SKIP_SONARQUBE="${{ github.event.inputs.SKIP_SONARQUBE || 'true' }}" + + if [[ "$SKIP_SONARQUBE" == "true" ]]; then + echo "⏭️ Skipping SonarQube Analysis (SKIP_SONARQUBE=$SKIP_SONARQUBE)" + exit 0 + fi + + # Define services array + services=(user meeting stt ai notification) + + # Run tests, coverage reports, and SonarQube analysis for each service + for service in "${services[@]}"; do + ./gradlew :$service:test :$service:jacocoTestReport :$service:sonar \ + -Dsonar.projectKey=hgzero-$service-${{ steps.determine_env.outputs.environment }} \ + -Dsonar.projectName=hgzero-$service-${{ steps.determine_env.outputs.environment }} \ + -Dsonar.host.url=$SONAR_HOST_URL \ + -Dsonar.token=$SONAR_TOKEN \ + -Dsonar.java.binaries=build/classes/java/main \ + -Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \ + -Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/** + done + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: app-builds + path: | + user/build/libs/*.jar + meeting/build/libs/*.jar + stt/build/libs/*.jar + ai/build/libs/*.jar + notification/build/libs/*.jar + + - name: Set outputs + id: set_outputs + run: | + # Generate timestamp for image tag + IMAGE_TAG=$(date +%Y%m%d%H%M%S) + echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + echo "environment=${{ steps.determine_env.outputs.environment }}" >> $GITHUB_OUTPUT + + release: + name: Build and Push Docker Images + needs: build + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: app-builds + + - name: Set environment variables from build job + run: | + echo "REGISTRY=${{ env.REGISTRY }}" >> $GITHUB_ENV + echo "IMAGE_ORG=${{ env.IMAGE_ORG }}" >> $GITHUB_ENV + echo "ENVIRONMENT=${{ needs.build.outputs.environment }}" >> $GITHUB_ENV + echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub (prevent rate limit) + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Login to Azure Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.ACR_USERNAME }} + password: ${{ secrets.ACR_PASSWORD }} + + - name: Build and push Docker images for all services + run: | + # Define services array + services=(user meeting stt ai notification) + + # Build and push each service image + for service in "${services[@]}"; do + echo "Building and pushing $service..." + docker build \ + --build-arg BUILD_LIB_DIR="$service/build/libs" \ + --build-arg ARTIFACTORY_FILE="$service.jar" \ + -f deployment/container/Dockerfile-backend \ + -t ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/$service:${{ needs.build.outputs.environment }}-${{ needs.build.outputs.image_tag }} . + + docker push ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/$service:${{ needs.build.outputs.environment }}-${{ needs.build.outputs.image_tag }} + done + + deploy: + name: Deploy to Kubernetes + needs: [build, release] + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set image tag environment variable + run: | + echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV + echo "ENVIRONMENT=${{ needs.build.outputs.environment }}" >> $GITHUB_ENV + + - name: Install Azure CLI + run: | + curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash + + - name: Azure Login + uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Setup kubectl + uses: azure/setup-kubectl@v3 + + - name: Get AKS Credentials + run: | + az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.AKS_CLUSTER }} --overwrite-existing + + - name: Create namespace + run: | + kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f - + + - name: Install Kustomize + run: | + curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash + sudo mv kustomize /usr/local/bin/ + + - name: Update Kustomize images and deploy + run: | + # 환경별 디렉토리로 이동 + cd .github/kustomize/overlays/${{ env.ENVIRONMENT }} + + # 각 서비스별 이미지 태그 업데이트 + kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/user:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} + kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/meeting:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} + kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/stt:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} + kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/ai:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} + kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/notification:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }} + + # 매니페스트 적용 + kubectl apply -k . + + - name: Wait for deployments to be ready + run: | + echo "Waiting for deployments to be ready..." + kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/user --timeout=300s || true + kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/meeting --timeout=300s || true + kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/stt --timeout=300s || true + kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/ai --timeout=300s || true + kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/notification --timeout=300s || true diff --git a/claude/유저스토리_변경사항_보고서_v2.2.0에서_v2.3.0.md b/claude/유저스토리_변경사항_보고서_v2.2.0에서_v2.3.0.md deleted file mode 100644 index eed679f..0000000 --- a/claude/유저스토리_변경사항_보고서_v2.2.0에서_v2.3.0.md +++ /dev/null @@ -1,454 +0,0 @@ -# 유저스토리 v2.2.0 → v2.3.0 변경사항 보고서 - -**작성일**: 2025-10-25 -**작성자**: 지수 (Product Designer), 민준 (Product Owner) -**문서 버전**: 1.0 - ---- - -## 📋 개요 - -본 보고서는 AI기반 회의록 작성 및 이력 관리 개선 서비스의 유저스토리 문서가 v2.2.0에서 v2.3.0으로 업데이트되면서 변경된 내용과 그 의미를 분석합니다. - -### 요약 통계 - -| 항목 | v2.2.0 | v2.3.0 | 변화 | -|------|--------|--------|------| -| **유저스토리 수** | 25개 | 27개 | +2개 (+8%) | -| **신규 추가** | - | 5개 | UFR-USER-010, UFR-USER-020, UFR-MEET-015, UFR-AI-030, UFR-NOTI-010 | -| **삭제/전환** | - | 2개 | AFR-USER-010, AFR-USER-020 → UFR로 전환 | -| **AFR 코드** | 2개 | 0개 | -2개 (100% 제거) | -| **UFR 코드** | 23개 | 27개 | +4개 (+17%) | -| **평균 상세도** | 20-30줄 | 60-100줄 | **약 3배 증가** | -| **프로토타입 연계** | 부분적 | 100% (10개 화면) | - | -| **표준 형식 적용** | 0% | 100% (27개) | - | - ---- - -## 📊 한눈에 보는 변경사항 - -``` -v2.2.0 (25개) v2.3.0 (27개) -┌─────────────────┐ ┌─────────────────┐ -│ AFR-USER-010 │ ──────────────────>│ UFR-USER-010 ✨ │ (로그인 상세화) -│ AFR-USER-020 │ ──────────────────>│ UFR-USER-020 ✨ │ (대시보드 재설계) -├─────────────────┤ ├─────────────────┤ -│ UFR-MEET-010 │ ──────────────────>│ UFR-MEET-010 ✨ │ (회의예약 개선) -│ │ │ UFR-MEET-015 🆕 │ (참석자 실시간 초대) -│ UFR-MEET-020 │ ──────────────────>│ UFR-MEET-020 ✨ │ (템플릿선택 상세화) -│ UFR-MEET-030 │ ──────────────────>│ UFR-MEET-030 ✨ │ (회의시작 4개 탭) -│ UFR-MEET-040 │ ──────────────────>│ UFR-MEET-040 ✨ │ (회의종료 3가지 액션) -│ UFR-MEET-050 │ ──────────────────>│ UFR-MEET-050 ✨ │ (최종확정 2가지 시나리오) -│ UFR-MEET-046 │ ──────────────────>│ UFR-MEET-046 ✨ │ (목록조회 샘플 30개) -│ UFR-MEET-047 │ ──────────────────>│ UFR-MEET-047 ✨ │ (상세조회 관련회의록) -│ UFR-MEET-055 │ ──────────────────>│ UFR-MEET-055 ✨ │ (회의록수정 3가지 시나리오) -├─────────────────┤ ├─────────────────┤ -│ UFR-AI-010 │ ──────────────────>│ UFR-AI-010 │ -│ UFR-AI-020 │ ──────────────────>│ UFR-AI-020 │ -│ │ │ UFR-AI-030 🆕🎯 │ (실시간 AI 제안 - 차별화!) -│ UFR-AI-035 │ ──────────────────>│ UFR-AI-035 │ -│ UFR-AI-036 │ ──────────────────>│ UFR-AI-036 │ -│ UFR-AI-040 │ ──────────────────>│ UFR-AI-040 │ -├─────────────────┤ ├─────────────────┤ -│ UFR-STT-010 │ ──────────────────>│ UFR-STT-010 │ -│ UFR-STT-020 │ ──────────────────>│ UFR-STT-020 │ -├─────────────────┤ ├─────────────────┤ -│ UFR-RAG-010 │ ──────────────────>│ UFR-RAG-010 │ -│ UFR-RAG-020 │ ──────────────────>│ UFR-RAG-020 │ -├─────────────────┤ ├─────────────────┤ -│ UFR-COLLAB-010 │ ──────────────────>│ UFR-COLLAB-010 │ -│ UFR-COLLAB-020 │ ──────────────────>│ UFR-COLLAB-020 │ -│ UFR-COLLAB-030 │ ──────────────────>│ UFR-COLLAB-030 │ -├─────────────────┤ ├─────────────────┤ -│ UFR-TODO-010 │ ──────────────────>│ UFR-TODO-010 │ -│ UFR-TODO-030 │ ──────────────────>│ UFR-TODO-030 │ -│ UFR-TODO-040 │ ──────────────────>│ UFR-TODO-040 │ -└─────────────────┘ ├─────────────────┤ - │ UFR-NOTI-010 🆕 │ (알림발송 - 폴링 방식) - └─────────────────┘ - -범례: -🆕 = 완전 신규 추가 -🎯 = 차별화 핵심 기능 -✨ = 대폭 개선 (프로토타입 기반 재작성) -``` - ---- - -## 🎯 핵심 변경사항 - -### 1. 신규 추가된 유저스토리 (5개) - -#### 1.1 UFR-USER-010: 로그인 🆕 -- **이전**: AFR-USER-010 (간략한 인증 설명) -- **변경**: UFR-USER-010으로 전환 및 상세화 -- **의미**: - - 로그인 프로세스 단계별 명시 (Enter 키 동작, 로딩 상태 등) - - 예외처리 시나리오 구체화 (사번 미입력, 비밀번호 8자 미만 등) - - 프로토타입 `01-로그인.html`과 1:1 매핑 - -#### 1.2 UFR-USER-020: 대시보드 🆕 -- **이전**: AFR-USER-020 (간략한 대시보드 설명) -- **변경**: UFR-USER-020으로 전환 및 대폭 확장 -- **의미**: - - 통계 블록, 최근 회의, 나의 Todo, 나의 회의록 위젯 상세 명세 - - FAB 버튼 2가지 액션 (회의예약/바로 시작) 명확화 - - 프로토타입 `02-대시보드.html`과 1:1 매핑 - -#### 1.3 UFR-MEET-015: 참석자 실시간 초대 🆕 -- **이전**: 없음 -- **변경**: 완전 신규 추가 -- **의미**: - - 회의 진행 중 "참석자" 탭에서 실시간으로 참석자 추가 기능 - - 검색 모달 → 추가 → WebSocket 동기화 → 알림 발송 흐름 명시 - - **효과**: 회의 진행 중 동적 참석자 관리로 유연성 향상 - - 프로토타입 `05-회의진행.html`의 "참석자" 탭과 연계 - -#### 1.4 UFR-AI-030: 실시간 AI 제안 🆕🎯 -- **이전**: 없음 -- **변경**: 완전 신규 추가 -- **의미**: - - **차별화 전략 "지능형 회의 진행 지원" 실현** - - STT 텍스트 실시간 분석 → 주요 내용 감지 → AI 제안 카드 생성 - - 제안 카드에서 메모 탭으로 드래그 앤 드롭으로 추가 - - **효과**: 회의 중 놓치는 내용 최소화, 차별화 핵심 기능 - - 프로토타입 `05-회의진행.html`의 "AI 제안" 탭과 연계 - -#### 1.5 UFR-NOTI-010: 알림 발송 🆕 -- **이전**: 없음 (암묵적으로 Meeting Service에서 직접 발송) -- **변경**: Notification 서비스의 독립적인 유저스토리로 추가 -- **의미**: - - **알림 아키텍처를 폴링 방식으로 통일** - - 1분 간격 폴링 → 이메일 발송 → 최대 3회 재시도 - - 6가지 알림 유형 명시 (Todo 할당, Todo 완료, 회의 시작, 회의록 확정, 참석자 초대, 회의록 수정) - - **효과**: Notification 서비스 독립성 확보, 시스템 안정성 향상 - ---- - -### 2. 대폭 개선된 유저스토리 (주요 8개) - -#### 2.1 UFR-MEET-010: 회의예약 -- **변경사항**: - - 수행절차 10단계 명시 (FAB 버튼 → 입력 → 저장/완료) - - 입력 필드별 상세 명세 (타입, 필수 여부, 최대/최소값, UI 요소) - - 임시저장/예약 완료 2가지 시나리오 구분 - - 예외처리 7가지 추가 (제목 미입력, 과거 날짜, 참석자 미선택 등) -- **의미**: 프로토타입 `03-회의예약.html` 기반 전면 재작성 - -#### 2.2 UFR-MEET-030: 회의시작 -- **변경사항**: - - 회의 진행 화면 4개 탭 상세 명세 (녹음/메모, 참석자, AI 제안, 안건) - - 녹음 시작/일시정지/재시작 플로우 명시 - - 참석자 상태 표시 (온라인/오프라인/참석중) - - 탭별 UI 요소와 인터랙션 상세화 -- **의미**: 프로토타입 `05-회의진행.html` 4개 탭 구조 반영 - -#### 2.3 UFR-MEET-040: 회의종료 -- **변경사항**: - - 회의 종료 후 3가지 액션 명시 (바로 확정, 나중에 확정, 검토 후 확정) - - 각 액션별 이동 화면 명확화 - - 안건 요약 및 검증 상태 표시 추가 -- **의미**: 프로토타입 `07-회의종료.html` 반영, 사용자 선택권 강화 - -#### 2.4 UFR-MEET-050: 최종확정 -- **변경사항**: - - 2가지 시나리오 분리 (검토 후 확정, 회의 종료 화면에서 바로 확정) - - 안건별 검증 완료 여부 체크 로직 추가 - - 미검증 안건 있을 시 확정 불가 정책 명시 -- **의미**: 회의록 품질 보증 메커니즘 강화 - -#### 2.5 UFR-MEET-046: 회의록목록조회 -- **변경사항**: - - 샘플 데이터 30개 명시 (제목, 날짜, 상태, 검증 현황 등) - - 필터/정렬 기능 상세화 (기간, 상태, 폴더별) - - 상태 배지 5종 추가 (진행중, 검토중, 확정완료 등) -- **의미**: 프로토타입 `12-회의록목록조회.html` 반영 - -#### 2.6 UFR-MEET-047: 회의록상세조회 -- **변경사항**: - - 관련 회의록 섹션 추가 (AI가 자동 연결한 회의록 3개 표시) - - 안건별 검증 상태 표시 추가 - - 용어 팝업 연계 (UFR-RAG-010) 명시 -- **의미**: 프로토타입 `10-회의록상세조회.html` 반영, RAG 기능 연계 - -#### 2.7 UFR-MEET-055: 회의록수정 -- **변경사항**: - - 3가지 진입 시나리오 명시 (회의종료 화면, 목록 화면, 상세조회 화면) - - 실시간 협업 플로우 상세화 (UFR-COLLAB-010, UFR-COLLAB-020 연계) - - 수정 저장/임시저장/취소 3가지 액션 구분 -- **의미**: 프로토타입 `11-회의록수정.html` 반영, 협업 기능 강화 - -#### 2.8 UFR-COLLAB-020: 충돌해결 -- **변경사항**: - - 안건 기반 충돌 방지 메커니즘 상세화 - - 동일 안건 동시 수정 시 경고 표시 및 잠금 정책 명시 - - 충돌 해결 시나리오 3가지 (대기, 새 안건 작성, 취소) -- **의미**: 실시간 협업 안정성 강화 - ---- - -### 3. 유지된 유저스토리 (14개) - -다음 유저스토리들은 v2.2.0과 v2.3.0에서 ID와 핵심 내용이 유지되었습니다: - -- UFR-AI-010 (회의록 자동 작성) -- UFR-AI-020 (Todo 자동 추출) -- UFR-AI-035 (섹션 AI 요약) -- UFR-AI-036 (AI 한줄 요약) -- UFR-AI-040 (관련 회의록 연결) -- UFR-STT-010 (음성 녹음 인식) -- UFR-STT-020 (텍스트 변환) -- UFR-RAG-010 (전문용어 감지) -- UFR-RAG-020 (맥락 기반 용어 설명) -- UFR-COLLAB-010 (회의록 수정 동기화) -- UFR-COLLAB-030 (검증 완료) -- UFR-TODO-010 (Todo 할당) -- UFR-TODO-030 (Todo 완료 처리) -- UFR-TODO-040 (Todo 관리) - ---- - -## 📈 문서 품질 개선 - -### 3.1 유저스토리 형식 표준화 - -#### Before (v2.2.0) - 자유 형식 -``` -UFR-MEET-010: [회의예약] 회의 생성자로서 | 나는, ... -- 시나리오: 회의 예약 및 참석자 초대 - 회의 예약 화면에 접근한 상황에서 | ... - - [입력 요구사항] - - 회의 제목: 최대 100자 (필수) - ... - - [처리 결과] - - 회의가 예약됨 - ... - -- M/13 -``` - -#### After (v2.3.0) - 표준 5단계 형식 -``` -### UFR-MEET-010: [회의예약] 회의 생성자로서 | 나는, ... - -**수행절차:** -1. 대시보드에서 "회의예약" FAB 버튼 클릭 -2. 회의 제목 입력 (최대 100자) -3. 날짜 선택 (오늘 이후 날짜, 달력 UI) -... -10. "임시저장" 버튼 또는 "예약 완료" 버튼 클릭 - -**입력:** -- 회의 제목: 텍스트 입력, 필수, 최대 100자, 문자 카운터 표시 -- 날짜: date 타입, 필수, 오늘 이후 날짜만 선택 가능 -... - -**출력/결과:** -- 예약 완료: "회의가 예약되었습니다" 토스트 메시지, 대시보드로 이동 -- 임시저장: "임시 저장되었습니다" 토스트 메시지 -... - -**예외처리:** -- 제목 미입력: "회의 제목을 입력해주세요" 토스트, 제목 필드 포커스 -- 과거 날짜 선택: "과거 날짜는 선택할 수 없습니다" 토스트 -... - -**관련 유저스토리:** -- UFR-USER-020: 대시보드 조회 -- UFR-MEET-020: 템플릿선택 -``` - -### 3.2 개선 효과 - -| 섹션 | 개선 효과 | -|------|-----------| -| **수행절차** | 단계별 명확한 작업 흐름, 개발자가 UI 플로우 이해 가능 | -| **입력** | 필드 타입, 검증 규칙, UI 요소 상세 명세, API 명세서 작성 기준 제공 | -| **출력/결과** | 성공/실패 시나리오별 응답 명시, 테스트 케이스 작성 기준 제공 | -| **예외처리** | 에러 상황별 처리 방법 구체화, QA 시나리오 명확화 | -| **관련 유저스토리** | 기능 간 연계성 추적, 통합 테스트 범위 파악 용이 | - ---- - -## 🏗️ 프로토타입 연계 강화 - -v2.3.0에서는 모든 유저스토리가 프로토타입 화면과 명확하게 연계되었습니다. - -| 프로토타입 화면 | 연계 유저스토리 | 상태 | -|----------------|----------------|------| -| 01-로그인.html | UFR-USER-010 | ✅ 1:1 매핑 | -| 02-대시보드.html | UFR-USER-020 | ✅ 1:1 매핑 | -| 03-회의예약.html | UFR-MEET-010 | ✅ 1:1 매핑 | -| 04-템플릿선택.html | UFR-MEET-020 | ✅ 1:1 매핑 | -| 05-회의진행.html | UFR-MEET-030, UFR-MEET-015 (신규), UFR-AI-030 (신규) | ✅ 1:N 매핑 | -| 07-회의종료.html | UFR-MEET-040 | ✅ 1:1 매핑 | -| 10-회의록상세조회.html | UFR-MEET-047 | ✅ 1:1 매핑 | -| 11-회의록수정.html | UFR-MEET-055 | ✅ 1:1 매핑 | -| 12-회의록목록조회.html | UFR-MEET-046 | ✅ 1:1 매핑 | -| 08-최종확정.html | UFR-MEET-050 | ✅ 1:1 매핑 | - -**결과**: 10개 프로토타입 화면 100% 유저스토리 연계 완료 - ---- - -## 🔑 핵심 아키텍처 변경 - -### 알림 아키텍처: 실시간 → 폴링 방식 - -#### Before (v2.2.0) -``` -[Meeting Service] ──(실시간 발송)──> [Notification Service] ──> [Email] - ↓ - Todo 할당 발생 → 즉시 이메일 발송 -``` - -**문제점**: -- Meeting Service와 Notification Service 간 강한 결합 -- 이메일 발송 실패 시 Meeting Service에 영향 - -#### After (v2.3.0) -``` -[Meeting Service] ──(DB 레코드 생성)──> [Notification 테이블] - ↓ - (1분 간격 폴링) - ↓ - [Notification Service] ──> [Email] - ↓ - (발송 상태 업데이트) -``` - -**개선 효과**: -- ✅ **Notification 서비스 독립성 강화**: 마이크로서비스 간 느슨한 결합 -- ✅ **시스템 안정성 향상**: 이메일 발송 실패 시 자동 재시도 (최대 3회) -- ✅ **확장성 확보**: 폴링 주기 조정으로 트래픽 제어 가능 -- ✅ **모니터링 용이**: 발송 대기/성공/실패 상태 DB에서 추적 - ---- - -## 💡 변경의 의미와 개선 효과 - -### 1. 사용자 경험 (UX) 개선 - -| 영역 | 개선 내용 | 효과 | -|------|----------|------| -| **회의 진행 중 유연성** | UFR-MEET-015 (참석자 실시간 초대) | 회의 중 동적 참석자 관리 가능 | -| **회의 중 놓침 방지** | UFR-AI-030 (실시간 AI 제안) 🎯 | 차별화 핵심 기능, 회의 중 주요 내용 실시간 감지 | -| **회의 종료 후 선택권** | UFR-MEET-040 (3가지 액션) | 바로 확정/나중에 확정/검토 후 확정 | -| **회의록 품질 보증** | UFR-MEET-050 (검증 후 확정) | 미검증 안건 있을 시 확정 불가 정책 | -| **실시간 협업 안정성** | UFR-COLLAB-020 (안건 기반 충돌 방지) | 동일 안건 동시 수정 시 경고 및 잠금 | - -### 2. 기능적 개선 - -| 영역 | 개선 내용 | 효과 | -|------|----------|------| -| **알림 시스템 안정성** | UFR-NOTI-010 (폴링 방식) | Notification 서비스 독립성 확보, 재시도 메커니즘 | -| **차별화 전략 실현** | UFR-AI-030 (실시간 AI 제안) 🎯 | "지능형 회의 진행 지원" 구체화 | -| **프로토타입 정합성** | 10개 화면 100% 매핑 | 기획-디자인-개발 간 일관성 확보 | -| **유저스토리 표준화** | 5단계 표준 형식 | 개발 가이드 역할 강화, API 명세서 작성 기준 제공 | - -### 3. 문서화 개선 - -| 영역 | 개선 내용 | 효과 | -|------|----------|------| -| **상세도 3배 증가** | 20-30줄 → 60-100줄 | 개발자가 구현에 필요한 모든 정보 확보 | -| **AFR 코드 폐지** | AFR → UFR 통일 | 유저스토리 체계 단순화 | -| **예외처리 명시** | 각 유저스토리별 5-7개 예외 시나리오 | QA 테스트 케이스 작성 기준 제공 | -| **관련 유저스토리 연계** | 기능 간 의존성 추적 | 통합 테스트 범위 명확화 | - ---- - -## 📋 권장 후속 조치 - -### 🔴 긴급 (1주 내) - -- [ ] **신규 유저스토리 3개 기반 API 설계** - - UFR-MEET-015: 참석자 실시간 초대 API - - UFR-AI-030: 실시간 AI 제안 API (SSE 또는 WebSocket) - - UFR-NOTI-010: 알림 폴링 및 발송 API - -- [ ] **알림 아키텍처 폴링 방식 반영** - - 물리 아키텍처 다이어그램 업데이트 - - Notification 테이블 스키마 정의 - - 폴링 스케줄러 설계 - -- [ ] **프로토타입 ↔ 유저스토리 1:1 매핑 검증** - - 10개 화면별 유저스토리 매핑 검증 - - 누락된 화면 또는 유저스토리 확인 - -### 🟡 중요 (2주 내) - -- [ ] **API 설계서 v2.3.0 기반 전면 업데이트** - - 입력/출력 명세 반영 (타입, 필수 여부, 검증 규칙) - - 예외처리 시나리오 → HTTP 상태 코드 및 에러 메시지 매핑 - - 관련 유저스토리 기반 API 그룹핑 - -- [ ] **예외처리 시나리오 → 테스트 케이스 전환** - - 각 유저스토리의 예외처리 섹션을 테스트 케이스로 변환 - - 입력 검증 테스트 케이스 작성 - -- [ ] **관련 유저스토리 기반 통합 테스트 시나리오 작성** - - 예: UFR-MEET-010 → UFR-MEET-020 → UFR-MEET-030 전체 플로우 테스트 - -### 🟢 일반 (3주 내) - -- [ ] **유저스토리별 개발 우선순위 재평가** - - 신규 유저스토리 3개 우선순위 결정 - - 차별화 핵심 기능 (UFR-AI-030) 우선 개발 검토 - -- [ ] **신규 기능 3개 개발 일정 수립** - - UFR-MEET-015: 참석자 실시간 초대 - - UFR-AI-030: 실시간 AI 제안 (Sprint 목표로 권장) - - UFR-NOTI-010: 알림 발송 - -- [ ] **프로토타입 기반 개발 가이드 작성** - - 프로토타입 → 유저스토리 → API → 컴포넌트 매핑 가이드 - - 프론트엔드 개발자를 위한 프로토타입 활용 가이드 - ---- - -## 🔍 핵심 시사점 (Key Takeaways) - -1. **v2.3.0은 프로토타입 분석을 통해 유저스토리를 전면 재정비한 버전** - - 10개 프로토타입 화면과 100% 매핑 - - 실제 UI/UX 플로우를 유저스토리에 반영 - -2. **신규 기능 3개 추가로 차별화 강화** - - 특히 UFR-AI-030 (실시간 AI 제안)은 차별화 핵심 기능 - -3. **알림 아키텍처 폴링 방식으로 통일하여 시스템 안정성 확보** - - Notification 서비스 독립성 강화 - - 재시도 메커니즘으로 안정성 향상 - -4. **유저스토리 형식 표준화로 개발 가이드 역할 강화** - - 5단계 표준 형식 (수행절차, 입력, 출력/결과, 예외처리, 관련 유저스토리) - - API 명세서 및 테스트 케이스 작성 기준 제공 - -5. **평균 유저스토리 상세도 약 3배 증가로 품질 대폭 향상** - - 개발자가 구현에 필요한 모든 정보 포함 - - 예외처리, 검증 규칙, UI 요소까지 상세 명시 - -6. **기존 24개 유저스토리 ID 승계하여 연속성 유지** - - AFR-USER-010 → UFR-USER-010 전환 - - 기존 설계 문서와의 연계성 유지 - -7. **프로토타입-유저스토리 1:1 매핑으로 개발 명확성 확보** - - 기획-디자인-개발 간 일관성 확보 - - 개발 우선순위 및 Sprint 계획 수립 용이 - ---- - -## 📎 참고 자료 - -- **상세 분석 (JSON)**: `claude/userstory-comparison-v2.2.0-to-v2.3.0.json` (19KB) -- **상세 분석 (Markdown)**: `claude/userstory-comparison-v2.2.0-to-v2.3.0.md` (16KB) -- **요약 분석**: `claude/userstory-comparison-summary.md` (11KB) -- **유저스토리 v2.2.0 백업**: `design/userstory_v2.2.0_backup.md` -- **유저스토리 v2.3.0 현재**: `design/userstory.md` - ---- - -**보고서 작성**: 지수 (Product Designer), 민준 (Product Owner) -**분석 일시**: 2025-10-25 -**문서 버전**: 1.0 diff --git a/design/uiux/prototype/05-회의진행.html b/design/uiux/prototype/05-회의진행.html index 462e732..e40fd03 100644 --- a/design/uiux/prototype/05-회의진행.html +++ b/design/uiux/prototype/05-회의진행.html @@ -60,7 +60,7 @@ } .meeting-title { - font-size: var(--font-large); + font-size: var(--font-h2); font-weight: var(--font-weight-bold); color: var(--gray-800); margin: 0; @@ -100,7 +100,7 @@ } .recording-time { - font-size: var(--font-small); + font-size: var(--font-body); font-weight: var(--font-weight-medium); } @@ -173,9 +173,9 @@ .info-row { display: flex; flex-direction: row; - align-items: center; - gap: var(--space-sm); - padding: var(--space-xs) 0; + align-items: flex-start; + gap: 0; + padding: var(--space-sm) 0; border-bottom: 1px solid var(--gray-300); } @@ -186,14 +186,28 @@ .info-label { font-weight: var(--font-weight-medium); color: var(--gray-600); - font-size: var(--font-caption); - min-width: 60px; + font-size: var(--font-small); + width: 70px; + min-width: 70px; + max-width: 70px; flex-shrink: 0; + padding-right: var(--space-sm); + border-right: 1px solid var(--gray-300); + margin-right: var(--space-sm); + } + + /* 데스크톱에서 라벨 폭 확장 */ + @media (min-width: 768px) { + .info-label { + width: 100px; + min-width: 100px; + max-width: 100px; + } } .info-value { color: var(--gray-800); - font-size: var(--font-small); + font-size: var(--font-body); line-height: 1.5; flex: 1; } @@ -229,7 +243,7 @@ background: transparent; border: none; cursor: pointer; - font-size: var(--font-small); + font-size: var(--font-body); font-weight: var(--font-weight-medium); color: var(--gray-600); transition: all var(--transition-fast); @@ -289,13 +303,13 @@ } .participant-section-title { - font-size: var(--font-small); + font-size: var(--font-body); font-weight: var(--font-weight-bold); margin: 0; } .participant-count { - font-size: var(--font-caption); + font-size: var(--font-small); color: var(--gray-600); } @@ -307,13 +321,13 @@ .invite-input { flex: 1; - font-size: var(--font-small); + font-size: var(--font-body); padding: var(--space-sm); } .invite-btn { padding: var(--space-sm) var(--space-md); - font-size: var(--font-small); + font-size: var(--font-body); white-space: nowrap; flex-shrink: 0; } @@ -337,13 +351,13 @@ } .participant-name { - font-size: var(--font-small); + font-size: var(--font-body); font-weight: var(--font-weight-medium); color: var(--gray-800); } .participant-email { - font-size: var(--font-caption); + font-size: var(--font-small); color: var(--gray-500); margin-top: 2px; overflow: hidden; @@ -360,7 +374,7 @@ } .memo-input-label { - font-size: var(--font-small); + font-size: var(--font-body); font-weight: var(--font-weight-medium); color: var(--gray-700); margin-bottom: var(--space-xs); @@ -373,7 +387,7 @@ padding: var(--space-sm); border: 1px solid var(--gray-300); border-radius: var(--radius-md); - font-size: var(--font-small); + font-size: var(--font-body); font-family: inherit; resize: vertical; line-height: 1.5; @@ -388,11 +402,11 @@ width: 100%; margin-top: var(--space-xs); padding: var(--space-sm); - font-size: var(--font-small); + font-size: var(--font-body); } .ai-suggestion-list-title { - font-size: var(--font-body); + font-size: var(--font-h3); font-weight: var(--font-weight-bold); color: var(--gray-900); margin-bottom: var(--space-sm); @@ -416,7 +430,7 @@ } .ai-suggestion-time { - font-size: var(--font-caption); + font-size: var(--font-small); color: var(--gray-500); font-weight: var(--font-weight-regular); } @@ -446,7 +460,7 @@ } .ai-suggestion-text { - font-size: var(--font-small); + font-size: var(--font-body); color: var(--gray-700); line-height: 1.5; } @@ -460,13 +474,13 @@ .term-search-input { flex: 1; - font-size: var(--font-small); + font-size: var(--font-body); padding: var(--space-sm); } .term-search-btn { padding: var(--space-sm) var(--space-md); - font-size: var(--font-small); + font-size: var(--font-body); white-space: nowrap; flex-shrink: 0; } @@ -493,7 +507,7 @@ } .term-name { - font-size: var(--font-small); + font-size: var(--font-body); font-weight: var(--font-weight-bold); color: var(--primary); display: flex; @@ -512,14 +526,14 @@ } .term-definition { - font-size: var(--font-small); + font-size: var(--font-body); color: var(--gray-700); line-height: 1.5; margin-bottom: var(--space-xs); } .term-context { - font-size: 11px; + font-size: var(--font-small); color: var(--gray-500); padding-top: var(--space-xs); border-top: 1px dashed #D0D0D0; @@ -546,7 +560,7 @@ } .related-doc-title { - font-size: var(--font-small); + font-size: var(--font-body); font-weight: var(--font-weight-bold); color: var(--primary); margin-bottom: var(--space-xs); @@ -555,7 +569,7 @@ .related-doc-meta { display: flex; gap: var(--space-xs); - font-size: var(--font-caption); + font-size: var(--font-small); color: var(--gray-600); margin-bottom: var(--space-xs); } @@ -566,7 +580,7 @@ } .related-doc-text { - font-size: var(--font-small); + font-size: var(--font-body); color: var(--gray-700); line-height: 1.5; } diff --git a/design/uiux/prototype/09-Todo관리.html b/design/uiux/prototype/09-Todo관리.html deleted file mode 100644 index 70c3f70..0000000 --- a/design/uiux/prototype/09-Todo관리.html +++ /dev/null @@ -1,1014 +0,0 @@ - - -
- - -Todo 항목은 조회만 가능합니다. 제목, 담당자, 마감일 정보만 표시됩니다.
- -