mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 08:46:23 +00:00
백엔드 서비스 AKS 배포 및 설정 완료
- Kubernetes 매니페스트 파일 생성 (7개 서비스) * user-service, event-service, ai-service, content-service * participation-service, analytics-service, distribution-service * 공통 리소스: Ingress, ConfigMap, Secret, ImagePullSecret - analytics-service 배포 문제 해결 * Hibernate PostgreSQL dialect 추가 * DB 자격증명 수정 (eventuser/Hi5Jessica!) * analytics_db 데이터베이스 생성 - content-service Probe 경로 수정 * Context path 포함 (/api/v1/content/actuator/health) - distribution-service 신규 배포 * Docker 이미지 빌드 및 ACR 푸시 * K8s 매니페스트 생성 및 배포 * Ingress 경로 추가 (/distribution) - Gradle bootJar 설정 추가 * 5개 서비스에 archiveFileName 설정 - 배포 가이드 문서 추가 * deployment/k8s/deploy-k8s-guide.md * claude/deploy-k8s-back.md * deployment/container/build-image.md 업데이트 배포 완료: 모든 백엔드 서비스(7개) 정상 실행 중 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
23265b5849
commit
df04f85346
@ -1,3 +1,7 @@
|
|||||||
|
bootJar {
|
||||||
|
archiveFileName = 'ai-service.jar'
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Kafka Consumer
|
// Kafka Consumer
|
||||||
implementation 'org.springframework.kafka:spring-kafka'
|
implementation 'org.springframework.kafka:spring-kafka'
|
||||||
|
|||||||
@ -5,23 +5,23 @@ spring:
|
|||||||
# Redis Configuration
|
# Redis Configuration
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: 20.214.210.71
|
host: ${REDIS_HOST:20.214.210.71}
|
||||||
port: 6379
|
port: ${REDIS_PORT:6379}
|
||||||
password: Hi5Jessica!
|
password: ${REDIS_PASSWORD:Hi5Jessica!}
|
||||||
database: 3
|
database: ${REDIS_DATABASE:3}
|
||||||
timeout: 3000
|
timeout: ${REDIS_TIMEOUT:3000}
|
||||||
lettuce:
|
lettuce:
|
||||||
pool:
|
pool:
|
||||||
max-active: 8
|
max-active: ${REDIS_POOL_MAX:8}
|
||||||
max-idle: 8
|
max-idle: ${REDIS_POOL_IDLE:8}
|
||||||
min-idle: 2
|
min-idle: ${REDIS_POOL_MIN:2}
|
||||||
max-wait: -1ms
|
max-wait: ${REDIS_POOL_WAIT:-1ms}
|
||||||
|
|
||||||
# Kafka Consumer Configuration
|
# Kafka Consumer Configuration
|
||||||
kafka:
|
kafka:
|
||||||
bootstrap-servers: 4.230.50.63:9092
|
bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:4.230.50.63:9092}
|
||||||
consumer:
|
consumer:
|
||||||
group-id: ai-service-consumers
|
group-id: ${KAFKA_CONSUMER_GROUP:ai-service-consumers}
|
||||||
auto-offset-reset: earliest
|
auto-offset-reset: earliest
|
||||||
enable-auto-commit: false
|
enable-auto-commit: false
|
||||||
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||||
@ -35,7 +35,7 @@ spring:
|
|||||||
|
|
||||||
# Server Configuration
|
# Server Configuration
|
||||||
server:
|
server:
|
||||||
port: 8083
|
port: ${SERVER_PORT:8083}
|
||||||
servlet:
|
servlet:
|
||||||
context-path: /
|
context-path: /
|
||||||
encoding:
|
encoding:
|
||||||
@ -45,17 +45,17 @@ server:
|
|||||||
|
|
||||||
# JWT Configuration
|
# JWT Configuration
|
||||||
jwt:
|
jwt:
|
||||||
secret: kt-event-marketing-secret-key-for-development-only-please-change-in-production
|
secret: ${JWT_SECRET:kt-event-marketing-secret-key-for-development-only-please-change-in-production}
|
||||||
access-token-validity: 604800000
|
access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:604800000}
|
||||||
refresh-token-validity: 86400
|
refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:86400}
|
||||||
|
|
||||||
# CORS Configuration
|
# CORS Configuration
|
||||||
cors:
|
cors:
|
||||||
allowed-origins: http://localhost:*
|
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:*}
|
||||||
allowed-methods: GET,POST,PUT,DELETE,OPTIONS,PATCH
|
allowed-methods: ${CORS_ALLOWED_METHODS:GET,POST,PUT,DELETE,OPTIONS,PATCH}
|
||||||
allowed-headers: "*"
|
allowed-headers: ${CORS_ALLOWED_HEADERS:*}
|
||||||
allow-credentials: true
|
allow-credentials: ${CORS_ALLOW_CREDENTIALS:true}
|
||||||
max-age: 3600
|
max-age: ${CORS_MAX_AGE:3600}
|
||||||
|
|
||||||
# Actuator Configuration
|
# Actuator Configuration
|
||||||
management:
|
management:
|
||||||
@ -91,39 +91,39 @@ springdoc:
|
|||||||
# Logging Configuration
|
# Logging Configuration
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
root: INFO
|
root: ${LOG_LEVEL_ROOT:INFO}
|
||||||
com.kt.ai: DEBUG
|
com.kt.ai: ${LOG_LEVEL_AI:DEBUG}
|
||||||
org.springframework.kafka: INFO
|
org.springframework.kafka: ${LOG_LEVEL_KAFKA:INFO}
|
||||||
org.springframework.data.redis: INFO
|
org.springframework.data.redis: ${LOG_LEVEL_REDIS:INFO}
|
||||||
io.github.resilience4j: DEBUG
|
io.github.resilience4j: ${LOG_LEVEL_RESILIENCE4J:DEBUG}
|
||||||
pattern:
|
pattern:
|
||||||
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||||
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||||
file:
|
file:
|
||||||
name: logs/ai-service.log
|
name: ${LOG_FILE_NAME:logs/ai-service.log}
|
||||||
logback:
|
logback:
|
||||||
rollingpolicy:
|
rollingpolicy:
|
||||||
max-file-size: 10MB
|
max-file-size: ${LOG_FILE_MAX_SIZE:10MB}
|
||||||
max-history: 7
|
max-history: ${LOG_FILE_MAX_HISTORY:7}
|
||||||
total-size-cap: 100MB
|
total-size-cap: ${LOG_FILE_TOTAL_CAP:100MB}
|
||||||
|
|
||||||
# Kafka Topics Configuration
|
# Kafka Topics Configuration
|
||||||
kafka:
|
kafka:
|
||||||
topics:
|
topics:
|
||||||
ai-job: ai-event-generation-job
|
ai-job: ${KAFKA_TOPICS_AI_JOB:ai-event-generation-job}
|
||||||
ai-job-dlq: ai-event-generation-job-dlq
|
ai-job-dlq: ${KAFKA_TOPICS_AI_JOB_DLQ:ai-event-generation-job-dlq}
|
||||||
|
|
||||||
# AI API Configuration (실제 API 사용)
|
# AI API Configuration (실제 API 사용)
|
||||||
ai:
|
ai:
|
||||||
provider: CLAUDE
|
provider: ${AI_PROVIDER:CLAUDE}
|
||||||
claude:
|
claude:
|
||||||
api-url: https://api.anthropic.com/v1/messages
|
api-url: ${AI_CLAUDE_API_URL:https://api.anthropic.com/v1/messages}
|
||||||
api-key: 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
|
api-key: ${AI_CLAUDE_API_KEY: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}
|
||||||
anthropic-version: 2023-06-01
|
anthropic-version: ${AI_CLAUDE_ANTHROPIC_VERSION:2023-06-01}
|
||||||
model: claude-sonnet-4-5-20250929
|
model: ${AI_CLAUDE_MODEL:claude-sonnet-4-5-20250929}
|
||||||
max-tokens: 4096
|
max-tokens: ${AI_CLAUDE_MAX_TOKENS:4096}
|
||||||
temperature: 0.7
|
temperature: ${AI_CLAUDE_TEMPERATURE:0.7}
|
||||||
timeout: 300000
|
timeout: ${AI_CLAUDE_TIMEOUT:300000}
|
||||||
|
|
||||||
# Circuit Breaker Configuration
|
# Circuit Breaker Configuration
|
||||||
resilience4j:
|
resilience4j:
|
||||||
@ -162,7 +162,7 @@ resilience4j:
|
|||||||
# Redis Cache TTL Configuration (seconds)
|
# Redis Cache TTL Configuration (seconds)
|
||||||
cache:
|
cache:
|
||||||
ttl:
|
ttl:
|
||||||
recommendation: 86400 # 24 hours
|
recommendation: ${CACHE_TTL_RECOMMENDATION:86400} # 24 hours
|
||||||
job-status: 86400 # 24 hours
|
job-status: ${CACHE_TTL_JOB_STATUS:86400} # 24 hours
|
||||||
trend: 3600 # 1 hour
|
trend: ${CACHE_TTL_TREND:3600} # 1 hour
|
||||||
fallback: 604800 # 7 days
|
fallback: ${CACHE_TTL_FALLBACK:604800} # 7 days
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
bootJar {
|
||||||
|
archiveFileName = 'analytics-service.jar'
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Kafka Consumer
|
// Kafka Consumer
|
||||||
implementation 'org.springframework.kafka:spring-kafka'
|
implementation 'org.springframework.kafka:spring-kafka'
|
||||||
|
|||||||
@ -4,7 +4,7 @@ spring:
|
|||||||
|
|
||||||
# Database
|
# Database
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:${DB_KIND:postgresql}://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:analytics_db}
|
url: jdbc:postgresql://${DB_HOST:4.230.49.9}:${DB_PORT:5432}/${DB_NAME:analytics_db}
|
||||||
username: ${DB_USERNAME:analytics_user}
|
username: ${DB_USERNAME:analytics_user}
|
||||||
password: ${DB_PASSWORD:analytics_pass}
|
password: ${DB_PASSWORD:analytics_pass}
|
||||||
driver-class-name: org.postgresql.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
@ -23,6 +23,7 @@ spring:
|
|||||||
hibernate:
|
hibernate:
|
||||||
format_sql: true
|
format_sql: true
|
||||||
use_sql_comments: true
|
use_sql_comments: true
|
||||||
|
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: ${DDL_AUTO:update}
|
ddl-auto: ${DDL_AUTO:update}
|
||||||
|
|
||||||
|
|||||||
770
claude/deploy-actions-cicd-back.md
Normal file
770
claude/deploy-actions-cicd-back.md
Normal file
@ -0,0 +1,770 @@
|
|||||||
|
# 백엔드 GitHub Actions 파이프라인 작성 가이드
|
||||||
|
|
||||||
|
[요청사항]
|
||||||
|
- GitHub Actions 기반 CI/CD 파이프라인 구축 가이드 작성
|
||||||
|
- 환경별(dev/staging/prod) Kustomize 매니페스트 관리 및 자동 배포 구현
|
||||||
|
- SonarQube 코드 품질 분석과 Quality Gate 포함
|
||||||
|
- Kustomize 매니페스트 생성부터 배포까지 전체 과정 안내
|
||||||
|
- '[결과파일]'에 구축 방법 및 파이프라인 작성 가이드 생성
|
||||||
|
- 아래 작업은 실제 수행하여 파일 생성
|
||||||
|
- Kustomize 디렉토리 구조 생성
|
||||||
|
- Base Kustomization 작성
|
||||||
|
- 환경별 Overlay 작성
|
||||||
|
- 환경별 Patch 파일 생성
|
||||||
|
- GitHub Actions 워크플로우 파일 작성
|
||||||
|
- 환경별 배포 변수 파일 작성
|
||||||
|
- 수동 배포 스크립트 작성
|
||||||
|
|
||||||
|
[작업순서]
|
||||||
|
- 사전 준비사항 확인
|
||||||
|
프롬프트의 '[실행정보]'섹션에서 아래정보를 확인
|
||||||
|
- {ACR_NAME}: Azure Container Registry 이름
|
||||||
|
- {RESOURCE_GROUP}: Azure 리소스 그룹명
|
||||||
|
- {AKS_CLUSTER}: AKS 클러스터명
|
||||||
|
- {NAMESPACE}: Namespace명
|
||||||
|
예시)
|
||||||
|
```
|
||||||
|
[실행정보]
|
||||||
|
- ACR_NAME: acrdigitalgarage01
|
||||||
|
- RESOURCE_GROUP: rg-digitalgarage-01
|
||||||
|
- AKS_CLUSTER: aks-digitalgarage-01
|
||||||
|
- NAMESPACE: phonebill-dg0500
|
||||||
|
```
|
||||||
|
|
||||||
|
- 시스템명과 서비스명 확인
|
||||||
|
settings.gradle에서 확인.
|
||||||
|
- {SYSTEM_NAME}: rootProject.name
|
||||||
|
- {SERVICE_NAMES}: include 'common'하위의 include문 뒤의 값임
|
||||||
|
|
||||||
|
예시) include 'common'하위의 서비스명들.
|
||||||
|
```
|
||||||
|
rootProject.name = 'phonebill'
|
||||||
|
|
||||||
|
include 'common'
|
||||||
|
include 'api-gateway'
|
||||||
|
include 'user-service'
|
||||||
|
include 'order-service'
|
||||||
|
include 'payment-service'
|
||||||
|
```
|
||||||
|
|
||||||
|
- JDK버전 확인
|
||||||
|
루트 build.gradle에서 JDK 버전 확인.
|
||||||
|
{JDK_VERSION}: 'java' 섹션에서 JDK 버전 확인. 아래 예에서는 21임.
|
||||||
|
```
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- GitHub 저장소 환경 구성 안내
|
||||||
|
- GitHub Repository Secrets 설정
|
||||||
|
- Azure 접근 인증정보 설정
|
||||||
|
```
|
||||||
|
# Azure Service Principal
|
||||||
|
Repository Settings > Secrets and variables > Actions > Repository secrets에 등록
|
||||||
|
|
||||||
|
AZURE_CREDENTIALS:
|
||||||
|
{
|
||||||
|
"clientId": "{클라이언트ID}",
|
||||||
|
"clientSecret": "{클라이언트시크릿}",
|
||||||
|
"subscriptionId": "{구독ID}",
|
||||||
|
"tenantId": "{테넌트ID}"
|
||||||
|
}
|
||||||
|
예시)
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
Credential 구하는 방법 안내
|
||||||
|
az acr credential show --name {acr 이름}
|
||||||
|
예) az acr credential show --name acrdigitalgarage01
|
||||||
|
```
|
||||||
|
ACR_USERNAME: {ACR_NAME}
|
||||||
|
ACR_PASSWORD: {ACR패스워드}
|
||||||
|
```
|
||||||
|
- SonarQube URL과 인증 토큰
|
||||||
|
SONAR_HOST_URL 구하는 방법과 SONAR_TOKEN 작성법 안내
|
||||||
|
SONAR_HOST_URL: 아래 명령 수행 후 http://{External IP}를 지정
|
||||||
|
k get svc -n sonarqube
|
||||||
|
예) http://20.249.187.69
|
||||||
|
|
||||||
|
SONAR_TOKEN 값은 아래와 같이 작성
|
||||||
|
- SonarQube 로그인 후 우측 상단 'Administrator' > My Account 클릭
|
||||||
|
- Security 탭 선택 후 토큰 생성
|
||||||
|
|
||||||
|
```
|
||||||
|
SONAR_TOKEN: {SonarQube토큰}
|
||||||
|
SONAR_HOST_URL: {SonarQube서버URL}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Docker Hub (Rate Limit 해결용)
|
||||||
|
Docker Hub 패스워드 작성 방법 안내
|
||||||
|
- DockerHub(https://hub.docker.com)에 로그인
|
||||||
|
- 우측 상단 프로필 아이콘 클릭 후 Account Settings를 선택
|
||||||
|
- 좌측메뉴에서 'Personal Access Tokens' 클릭하여 생성
|
||||||
|
```
|
||||||
|
DOCKERHUB_USERNAME: {Docker Hub 사용자명}
|
||||||
|
DOCKERHUB_PASSWORD: {Docker Hub 패스워드}
|
||||||
|
```
|
||||||
|
|
||||||
|
- GitHub Repository Variables 설정
|
||||||
|
```
|
||||||
|
# Workflow 제어 변수
|
||||||
|
Repository Settings > Secrets and variables > Actions > Variables > Repository variables에 등록
|
||||||
|
|
||||||
|
ENVIRONMENT: dev (기본값, 수동실행시 선택 가능: dev/staging/prod)
|
||||||
|
SKIP_SONARQUBE: true (기본값, 수동실행시 선택 가능: true/false)
|
||||||
|
```
|
||||||
|
|
||||||
|
**사용 방법:**
|
||||||
|
- **자동 실행**: Push/PR 시 기본값 사용 (ENVIRONMENT=dev, SKIP_SONARQUBE=true)
|
||||||
|
- **수동 실행**: Actions 탭 > "Backend Services CI/CD" > "Run workflow" 버튼 클릭
|
||||||
|
- Environment: dev/staging/prod 선택
|
||||||
|
- Skip SonarQube Analysis: true/false 선택
|
||||||
|
|
||||||
|
- Kustomize 디렉토리 구조 생성
|
||||||
|
- GitHub Actions 전용 Kustomize 디렉토리 생성
|
||||||
|
```bash
|
||||||
|
mkdir -p .github/kustomize/{base,overlays/{dev,staging,prod}}
|
||||||
|
mkdir -p .github/kustomize/base/{common,{서비스명1},{서비스명2},...}
|
||||||
|
mkdir -p .github/{config,scripts}
|
||||||
|
```
|
||||||
|
- 기존 k8s 매니페스트를 base로 복사
|
||||||
|
```bash
|
||||||
|
# 기존 deployment/k8s/* 파일들을 base로 복사
|
||||||
|
cp deployment/k8s/common/* .github/kustomize/base/common/
|
||||||
|
cp deployment/k8s/{서비스명}/* .github/kustomize/base/{서비스명}/
|
||||||
|
|
||||||
|
# 네임스페이스 하드코딩 제거
|
||||||
|
find .github/kustomize/base -name "*.yaml" -exec sed -i 's/namespace: .*//' {} \;
|
||||||
|
```
|
||||||
|
|
||||||
|
- Base Kustomization 작성
|
||||||
|
`.github/kustomize/base/kustomization.yaml` 파일 생성
|
||||||
|
```yaml
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
name: {SYSTEM_NAME}-base
|
||||||
|
|
||||||
|
resources:
|
||||||
|
# Common resources
|
||||||
|
- common/configmap-common.yaml
|
||||||
|
- common/secret-common.yaml
|
||||||
|
- common/secret-imagepull.yaml
|
||||||
|
- common/ingress.yaml
|
||||||
|
|
||||||
|
# 각 서비스별 리소스
|
||||||
|
- {SERVICE_NAME}/deployment.yaml
|
||||||
|
- {SERVICE_NAME}/service.yaml
|
||||||
|
- {SERVICE_NAME}/configmap.yaml
|
||||||
|
- {SERVICE_NAME}/secret.yaml
|
||||||
|
|
||||||
|
images:
|
||||||
|
- name: {ACR_NAME}.azurecr.io/{SYSTEM_NAME}/{SERVICE_NAME}
|
||||||
|
newTag: latest
|
||||||
|
```
|
||||||
|
|
||||||
|
- 환경별 Patch 파일 생성
|
||||||
|
각 환경별로 필요한 patch 파일들을 생성합니다.
|
||||||
|
**중요원칙**:
|
||||||
|
- **base 매니페스트에 없는 항목은 추가 안함**
|
||||||
|
- **base 매니페스트와 항목이 일치해야 함**
|
||||||
|
- Secret 매니페스트에 'data'가 아닌 'stringData'사용
|
||||||
|
|
||||||
|
**1. ConfigMap Common Patch 파일 생성**
|
||||||
|
`.github/kustomize/overlays/{ENVIRONMENT}/cm-common-patch.yaml`
|
||||||
|
|
||||||
|
- base 매니페스트를 환경별로 복사
|
||||||
|
```
|
||||||
|
cp .github/kustomize/base/common/cm-common.yaml .github/kustomize/overlays/{ENVIRONMENT}/cm-common-patch.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
- SPRING_PROFILES_ACTIVE를 환경에 맞게 설정 (dev/staging/prod)
|
||||||
|
- DDL_AUTO 설정: dev는 "update", staging/prod는 "validate"
|
||||||
|
- JWT 토큰 유효시간은 prod에서 보안을 위해 짧게 설정
|
||||||
|
|
||||||
|
**2. Secret Common Patch 파일 생성**
|
||||||
|
`.github/kustomize/overlays/{ENVIRONMENT}/secret-common-patch.yaml`
|
||||||
|
|
||||||
|
- base 매니페스트를 환경별로 복사
|
||||||
|
```
|
||||||
|
cp .github/kustomize/base/common/secret-common.yaml .github/kustomize/overlays/{ENVIRONMENT}/secret-common-patch.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Ingress Patch 파일 생성**
|
||||||
|
`.github/kustomize/overlays/{ENVIRONMENT}/ingress-patch.yaml`
|
||||||
|
- base의 ingress.yaml을 환경별로 오버라이드
|
||||||
|
- **⚠️ 중요**: 개발환경 Ingress Host의 기본값은 base의 ingress.yaml과 **정확히 동일하게** 함
|
||||||
|
- base에서 `host: {SYSTEM_NAME}-api.20.214.196.128.nip.io` 이면
|
||||||
|
- dev에서도 `host: {SYSTEM_NAME}-api.20.214.196.128.nip.io` 로 동일하게 설정
|
||||||
|
- **절대** `{SYSTEM_NAME}-dev-api.xxx` 처럼 변경하지 말 것
|
||||||
|
- Staging/Prod 환경별 도메인 설정: {SYSTEM_NAME}.도메인 형식
|
||||||
|
- service name을 '{서비스명}'으로 함.
|
||||||
|
- Staging/prod 환경은 HTTPS 강제 적용 및 SSL 인증서 설정
|
||||||
|
- staging/prod는 nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||||
|
- dev는 nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||||
|
|
||||||
|
**4. deployment Patch 파일 생성** ⚠️ **중요**
|
||||||
|
각 서비스별로 별도 파일 생성
|
||||||
|
`.github/kustomize/overlays/{ENVIRONMENT}/deployment-{SERVICE_NAME}-patch.yaml`
|
||||||
|
|
||||||
|
**필수 포함 사항:**
|
||||||
|
- ✅ **replicas 설정**: 각 서비스별 Deployment의 replica 수를 환경별로 설정
|
||||||
|
- dev: 모든 서비스 1 replica (리소스 절약)
|
||||||
|
- staging: 모든 서비스 2 replicas
|
||||||
|
- prod: 모든 서비스 3 replicas
|
||||||
|
- ✅ **resources 설정**: 각 서비스별 Deployment의 resources를 환경별로 설정
|
||||||
|
- dev: requests(256m CPU, 256Mi Memory), limits(1024m CPU, 1024Mi Memory)
|
||||||
|
- staging: requests(512m CPU, 512Mi Memory), limits(2048m CPU, 2048Mi Memory)
|
||||||
|
- prod: requests(1024m CPU, 1024Mi Memory), limits(4096m CPU, 4096Mi Memory)
|
||||||
|
|
||||||
|
**5. Secret Service Patch 파일 생성**
|
||||||
|
각 서비스별로 별도 파일 생성
|
||||||
|
`.github/kustomize/overlays/{ENVIRONMENT}/secret-{SERVICE_NAME}-patch.yaml`
|
||||||
|
|
||||||
|
- base 매니페스트를 환경별로 복사
|
||||||
|
```
|
||||||
|
cp .github/kustomize/base/{SERVICE_NAME}/secret-{SERVICE_NAME}.yaml .github/kustomize/overlays/{ENVIRONMENT}/secret-{SERVICE_NAME}-patch.yaml
|
||||||
|
```
|
||||||
|
- 환경별 데이터베이스 연결 정보로 수정
|
||||||
|
- **⚠️ 중요**: 패스워드 등 민감정보는 실제 환경 구축 시 별도 설정
|
||||||
|
|
||||||
|
- 환경별 Overlay 작성
|
||||||
|
각 환경별로 `overlays/{환경}/kustomization.yaml` 생성
|
||||||
|
```yaml
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
namespace: {NAMESPACE}
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- ../../base
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- path: cm-common-patch.yaml
|
||||||
|
target:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: cm-common
|
||||||
|
- path: deployment-{SERVICE_NAME}-patch.yaml
|
||||||
|
target:
|
||||||
|
kind: Deployment
|
||||||
|
name: {SERVICE_NAME}
|
||||||
|
- path: ingress-patch.yaml
|
||||||
|
target:
|
||||||
|
kind: Ingress
|
||||||
|
name: {SYSTEM_NAME}
|
||||||
|
- path: secret-common-patch.yaml
|
||||||
|
target:
|
||||||
|
kind: Secret
|
||||||
|
name: secret-common
|
||||||
|
- path: secret-{SERVICE_NAME}-patch.yaml
|
||||||
|
target:
|
||||||
|
kind: Secret
|
||||||
|
name: secret-{SERVICE_NAME}
|
||||||
|
|
||||||
|
images:
|
||||||
|
- name: {ACR_NAME}.azurecr.io/{SYSTEM_NAME}/{SERVICE_NAME}
|
||||||
|
newTag: {ENVIRONMENT}-latest
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- GitHub Actions 워크플로우 작성
|
||||||
|
`.github/workflows/backend-cicd.yaml` 파일 생성 방법을 안내합니다.
|
||||||
|
|
||||||
|
주요 구성 요소:
|
||||||
|
- **Build & Test**: Gradle 기반 빌드 및 단위 테스트
|
||||||
|
- **SonarQube Analysis**: 코드 품질 분석 및 Quality Gate
|
||||||
|
- **Container Build & Push**: 환경별 이미지 태그로 빌드 및 푸시
|
||||||
|
- **Kustomize Deploy**: 환경별 매니페스트 적용
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Backend Services CI/CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
paths:
|
||||||
|
- '{서비스명1}/**'
|
||||||
|
- '{서비스명2}/**'
|
||||||
|
- '{서비스명3}/**'
|
||||||
|
- '{서비스명N}/**'
|
||||||
|
- '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: ${{ secrets.REGISTRY }}
|
||||||
|
IMAGE_ORG: ${{ secrets.IMAGE_ORG }}
|
||||||
|
RESOURCE_GROUP: ${{ secrets.RESOURCE_GROUP }}
|
||||||
|
AKS_CLUSTER: ${{ secrets.AKS_CLUSTER }}
|
||||||
|
|
||||||
|
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 {버전}
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '{JDK버전}'
|
||||||
|
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="{ACR_NAME}.azurecr.io"
|
||||||
|
IMAGE_ORG="{SYSTEM_NAME}"
|
||||||
|
RESOURCE_GROUP="{RESOURCE_GROUP}"
|
||||||
|
AKS_CLUSTER="{AKS_CLUSTER}"
|
||||||
|
NAMESPACE="{NAMESPACE}"
|
||||||
|
|
||||||
|
# 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=({SERVICE_NAME1} {SERVICE_NAME2} {SERVICE_NAME3} {SERVICE_NAMEN})
|
||||||
|
|
||||||
|
# Run tests, coverage reports, and SonarQube analysis for each service
|
||||||
|
for service in "${services[@]}"; do
|
||||||
|
./gradlew :$service:test :$service:jacocoTestReport :$service:sonar \
|
||||||
|
-Dsonar.projectKey={SYSTEM_NAME}-$service-${{ steps.determine_env.outputs.environment }} \
|
||||||
|
-Dsonar.projectName={SYSTEM_NAME}-$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: |
|
||||||
|
{SERVICE_NAME1}/build/libs/*.jar
|
||||||
|
{SERVICE_NAME2}/build/libs/*.jar
|
||||||
|
{SERVICE_NAME3}/build/libs/*.jar
|
||||||
|
{SERVICE_NAMEN}/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=${{ needs.build.outputs.registry }}" >> $GITHUB_ENV
|
||||||
|
echo "IMAGE_ORG=${{ needs.build.outputs.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=({SERVICE_NAME1} {SERVICE_NAME2} {SERVICE_NAME3} {SERVICE_NAMEN})
|
||||||
|
|
||||||
|
# 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 deployment/cicd/kustomize/overlays/${{ env.ENVIRONMENT }}
|
||||||
|
|
||||||
|
# 각 서비스별 이미지 태그 업데이트
|
||||||
|
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/api-gateway:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}
|
||||||
|
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/user-service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}
|
||||||
|
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/bill-service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}
|
||||||
|
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/product-service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}
|
||||||
|
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/kos-mock:${{ 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/${{ env.ENVIRONMENT }}-api-gateway --timeout=300s
|
||||||
|
kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/${{ env.ENVIRONMENT }}-user-service --timeout=300s
|
||||||
|
kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/${{ env.ENVIRONMENT }}-bill-service --timeout=300s
|
||||||
|
kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/${{ env.ENVIRONMENT }}-product-service --timeout=300s
|
||||||
|
kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/${{ env.ENVIRONMENT }}-kos-mock --timeout=300s
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- GitHub Actions 전용 환경별 설정 파일 작성
|
||||||
|
`.github/config/deploy_env_vars_{환경}` 파일 생성 방법
|
||||||
|
|
||||||
|
**.github/config/deploy_env_vars_dev**
|
||||||
|
```bash
|
||||||
|
# dev Environment Configuration
|
||||||
|
resource_group={RESOURCE_GROUP}
|
||||||
|
cluster_name={AKS_CLUSTER}
|
||||||
|
```
|
||||||
|
|
||||||
|
**.github/config/deploy_env_vars_staging**
|
||||||
|
```bash
|
||||||
|
# staging Environment Configuration
|
||||||
|
resource_group={RESOURCE_GROUP}
|
||||||
|
cluster_name={AKS_CLUSTER}
|
||||||
|
```
|
||||||
|
|
||||||
|
**.github/config/deploy_env_vars_prod**
|
||||||
|
```bash
|
||||||
|
# prod Environment Configuration
|
||||||
|
resource_group={RESOURCE_GROUP}
|
||||||
|
cluster_name={AKS_CLUSTER}
|
||||||
|
```
|
||||||
|
|
||||||
|
**참고**: Kustomize 방식에서는 namespace, replicas, resources 등은 kustomization.yaml과 patch 파일에서 관리됩니다.
|
||||||
|
|
||||||
|
- GitHub Actions 전용 수동 배포 스크립트 작성
|
||||||
|
`.github/scripts/deploy-actions.sh` 파일 생성:
|
||||||
|
```bash
|
||||||
|
#!/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 {NAMESPACE}..."
|
||||||
|
kubectl create namespace {NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
# 환경별 이미지 태그 업데이트 (.github/kustomize 사용)
|
||||||
|
cd .github/kustomize/overlays/${ENVIRONMENT}
|
||||||
|
|
||||||
|
echo "🔄 Updating image tags..."
|
||||||
|
# 서비스 배열 정의
|
||||||
|
services=({SERVICE_NAME1} {SERVICE_NAME2} {SERVICE_NAME3} {SERVICE_NAMEN})
|
||||||
|
|
||||||
|
# 각 서비스별 이미지 태그 업데이트
|
||||||
|
for service in "${services[@]}"; do
|
||||||
|
kustomize edit set image {ACR_NAME}.azurecr.io/{SYSTEM_NAME}/$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/${ENVIRONMENT}-$service -n {NAMESPACE} --timeout=300s
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "🔍 Health check..."
|
||||||
|
# API Gateway Health Check (첫 번째 서비스가 API Gateway라고 가정)
|
||||||
|
GATEWAY_SERVICE=${services[0]}
|
||||||
|
GATEWAY_POD=$(kubectl get pod -n {NAMESPACE} -l app.kubernetes.io/name=${ENVIRONMENT}-$GATEWAY_SERVICE -o jsonpath='{.items[0].metadata.name}')
|
||||||
|
kubectl -n {NAMESPACE} exec $GATEWAY_POD -- curl -f http://localhost:8080/actuator/health || echo "Health check failed, but deployment completed"
|
||||||
|
|
||||||
|
echo "📋 Service Information:"
|
||||||
|
kubectl get pods -n {NAMESPACE}
|
||||||
|
kubectl get services -n {NAMESPACE}
|
||||||
|
kubectl get ingress -n {NAMESPACE}
|
||||||
|
|
||||||
|
echo "✅ GitHub Actions deployment completed successfully!"
|
||||||
|
```
|
||||||
|
|
||||||
|
- SonarQube 프로젝트 설정 방법 작성
|
||||||
|
- SonarQube에서 각 서비스별 프로젝트 생성
|
||||||
|
- Quality Gate 설정:
|
||||||
|
```
|
||||||
|
Coverage: >= 80%
|
||||||
|
Duplicated Lines: <= 3%
|
||||||
|
Maintainability Rating: <= A
|
||||||
|
Reliability Rating: <= A
|
||||||
|
Security Rating: <= A
|
||||||
|
```
|
||||||
|
|
||||||
|
- 롤백 방법 작성
|
||||||
|
- GitHub Actions에서 이전 버전으로 롤백:
|
||||||
|
```bash
|
||||||
|
# 이전 워크플로우 실행으로 롤백
|
||||||
|
1. GitHub > Actions > 성공한 이전 워크플로우 선택
|
||||||
|
2. Re-run all jobs 클릭
|
||||||
|
```
|
||||||
|
- kubectl을 이용한 롤백:
|
||||||
|
```bash
|
||||||
|
# 특정 버전으로 롤백
|
||||||
|
kubectl rollout undo deployment/{환경}-{서비스명} -n {NAMESPACE} --to-revision=2
|
||||||
|
|
||||||
|
# 롤백 상태 확인
|
||||||
|
kubectl rollout status deployment/{환경}-{서비스명} -n {NAMESPACE}
|
||||||
|
```
|
||||||
|
- 수동 스크립트를 이용한 롤백:
|
||||||
|
```bash
|
||||||
|
# 이전 안정 버전 이미지 태그로 배포
|
||||||
|
./deployment/cicd/scripts/deploy-actions.sh {환경} {이전태그}
|
||||||
|
```
|
||||||
|
|
||||||
|
[체크리스트]
|
||||||
|
GitHub Actions CI/CD 파이프라인 구축 작업을 누락 없이 진행하기 위한 체크리스트입니다.
|
||||||
|
|
||||||
|
## 📋 사전 준비 체크리스트
|
||||||
|
- [ ] settings.gradle에서 시스템명과 서비스명 확인 완료
|
||||||
|
- [ ] 실행정보 섹션에서 ACR명, 리소스 그룹, AKS 클러스터명 확인 완료
|
||||||
|
|
||||||
|
## 📂 GitHub Actions 전용 Kustomize 구조 생성 체크리스트
|
||||||
|
- [ ] 디렉토리 구조 생성: `.github/kustomize/{base,overlays/{dev,staging,prod}}`
|
||||||
|
- [ ] 서비스별 base 디렉토리 생성: `.github/kustomize/base/{common,{서비스명들}}`
|
||||||
|
- [ ] 기존 k8s 매니페스트를 base로 복사 완료
|
||||||
|
- [ ] **리소스 누락 방지 검증 완료**:
|
||||||
|
- [ ] `ls .github/kustomize/base/*/` 명령으로 모든 서비스 디렉토리의 파일 확인
|
||||||
|
- [ ] 각 서비스별 필수 파일 존재 확인 (deployment.yaml, service.yaml 필수)
|
||||||
|
- [ ] ConfigMap 파일 존재 시 `cm-{서비스명}.yaml` 명명 규칙 준수 확인
|
||||||
|
- [ ] Secret 파일 존재 시 `secret-{서비스명}.yaml` 명명 규칙 준수 확인
|
||||||
|
- [ ] Base kustomization.yaml 파일 생성 완료
|
||||||
|
- [ ] 모든 서비스의 deployment.yaml, service.yaml 포함 확인
|
||||||
|
- [ ] 존재하는 모든 ConfigMap 파일 포함 확인 (`cm-{서비스명}.yaml`)
|
||||||
|
- [ ] 존재하는 모든 Secret 파일 포함 확인 (`secret-{서비스명}.yaml`)
|
||||||
|
- [ ] **검증 명령어 실행 완료**:
|
||||||
|
- [ ] `kubectl kustomize .github/kustomize/base/` 정상 실행 확인
|
||||||
|
- [ ] 에러 메시지 없이 모든 리소스 출력 확인
|
||||||
|
|
||||||
|
## 🔧 GitHub Actions 전용 환경별 Overlay 구성 체크리스트
|
||||||
|
### 중요 체크 사항
|
||||||
|
- Base Kustomization에서 존재하지 않는 Secret 파일들 제거
|
||||||
|
|
||||||
|
### 공통 체크 사항
|
||||||
|
- **base 매니페스트에 없는 항목을 추가하지 않았는지 체크**
|
||||||
|
- **base 매니페스트와 항목이 일치 하는지 체크**
|
||||||
|
- Secret 매니페스트에 'data'가 아닌 'stringData'사용했는지 체크
|
||||||
|
- **⚠️ Kustomize patch 방법 변경**: `patchesStrategicMerge` → `patches` (target 명시)
|
||||||
|
|
||||||
|
### DEV 환경
|
||||||
|
- [ ] `.github/kustomize/overlays/dev/kustomization.yaml` 생성 완료
|
||||||
|
- [ ] `.github/kustomize/overlays/dev/cm-common-patch.yaml` 생성 완료 (dev 프로파일, update DDL)
|
||||||
|
- [ ] `.github/kustomize/overlays/dev/secret-common-patch.yaml` 생성 완료
|
||||||
|
- [ ] `.github/kustomize/overlays/dev/ingress-patch.yaml` 생성 완료 (**Host 기본값은 base의 ingress.yaml과 동일**)
|
||||||
|
- [ ] `.github/kustomize/overlays/dev/deployment-{서비스명}-patch.yaml` 생성 완료 (replicas, resources 지정)
|
||||||
|
- [ ] 각 서비스별 `.github/kustomize/overlays/dev/secret-{서비스명}-patch.yaml` 생성 완료
|
||||||
|
|
||||||
|
### STAGING 환경
|
||||||
|
- [ ] `.github/kustomize/overlays/staging/kustomization.yaml` 생성 완료
|
||||||
|
- [ ] `.github/kustomize/overlays/staging/cm-common-patch.yaml` 생성 완료 (staging 프로파일, validate DDL)
|
||||||
|
- [ ] `.github/kustomize/overlays/staging/secret-common-patch.yaml` 생성 완료
|
||||||
|
- [ ] `.github/kustomize/overlays/staging/ingress-patch.yaml` 생성 완료 (prod 도메인, HTTPS, SSL 인증서)
|
||||||
|
- [ ] `.github/kustomize/overlays/staging/deployment-{서비스명}-patch.yaml` 생성 완료 (replicas, resources 지정)
|
||||||
|
- [ ] 각 서비스별 `.github/kustomize/overlays/staging/secret-{서비스명}-patch.yaml` 생성 완료
|
||||||
|
|
||||||
|
### PROD 환경
|
||||||
|
- [ ] `.github/kustomize/overlays/prod/kustomization.yaml` 생성 완료
|
||||||
|
- [ ] `.github/kustomize/overlays/prod/cm-common-patch.yaml` 생성 완료 (prod 프로파일, validate DDL, 짧은 JWT)
|
||||||
|
- [ ] `.github/kustomize/overlays/prod/secret-common-patch.yaml` 생성 완료
|
||||||
|
- [ ] `.github/kustomize/overlays/prod/ingress-patch.yaml` 생성 완료 (prod 도메인, HTTPS, SSL 인증서)
|
||||||
|
- [ ] `.github/kustomize/overlays/prod/deployment-{서비스명}-patch.yaml` 생성 완료 (replicas, resources 지정)
|
||||||
|
- [ ] 각 서비스별 `.github/kustomize/overlays/prod/secret-{서비스명}-patch.yaml` 생성 완료
|
||||||
|
|
||||||
|
## ⚙️ GitHub Actions 설정 및 스크립트 체크리스트
|
||||||
|
- [ ] 환경별 설정 파일 생성: `.github/config/deploy_env_vars_{dev,staging,prod}`
|
||||||
|
- [ ] GitHub Actions 워크플로우 파일 `.github/workflows/backend-cicd.yaml` 생성 완료
|
||||||
|
- [ ] 워크플로우 주요 내용 확인
|
||||||
|
- Build, SonarQube, Docker Build & Push, Deploy 단계 포함
|
||||||
|
- JDK 버전 확인: `java-version: '{JDK버전}'`
|
||||||
|
- 변수 참조 문법 확인: `${{ needs.build.outputs.* }}` 사용
|
||||||
|
- 모든 서비스명이 실제 프로젝트 서비스명으로 치환되었는지 확인
|
||||||
|
- **환경 변수 SKIP_SONARQUBE 처리 확인**: 기본값 'true', 조건부 실행
|
||||||
|
- **플레이스홀더 사용 확인**: {ACR_NAME}, {SYSTEM_NAME}, {SERVICE_NAME} 등
|
||||||
|
|
||||||
|
- [ ] 수동 배포 스크립트 `.github/scripts/deploy-actions.sh` 생성 완료
|
||||||
|
- [ ] 스크립트 실행 권한 설정 완료 (`chmod +x .github/scripts/*.sh`)
|
||||||
|
|
||||||
|
[결과파일]
|
||||||
|
- 가이드: .github/actions-pipeline-guide.md
|
||||||
|
- GitHub Actions 워크플로우: .github/workflows/backend-cicd.yaml
|
||||||
|
- GitHub Actions 전용 Kustomize 매니페스트: .github/kustomize/*
|
||||||
|
- GitHub Actions 전용 환경별 설정 파일: .github/config/*
|
||||||
|
- GitHub Actions 전용 수동배포 스크립트: .github/scripts/deploy-actions.sh
|
||||||
206
claude/deploy-k8s-back.md
Normal file
206
claude/deploy-k8s-back.md
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
# 백엔드 배포 가이드
|
||||||
|
|
||||||
|
[요청사항]
|
||||||
|
- 백엔드 서비스를 쿠버네티스에 배포하기 위한 매니페스트 파일 작성
|
||||||
|
- 매니페스트 파일 작성까지만 하고 실제 배포는 수행방법만 가이드
|
||||||
|
- '[결과파일]'에 수행한 명령어를 포함하여 배포 가이드 레포트 생성
|
||||||
|
|
||||||
|
[작업순서]
|
||||||
|
- 실행정보 확인
|
||||||
|
프롬프트의 '[실행정보]'섹션에서 아래정보를 확인
|
||||||
|
- {ACR명}: 컨테이너 레지스트리 이름
|
||||||
|
- {k8s명}: Kubernetes 클러스터 이름
|
||||||
|
- {네임스페이스}: 배포할 네임스페이스
|
||||||
|
- {파드수}: 생성할 파드수
|
||||||
|
- {리소스(CPU)}: 요청값/최대값
|
||||||
|
- {리소스(메모리)}: 요청값/최대값
|
||||||
|
예시)
|
||||||
|
```
|
||||||
|
[실행정보]
|
||||||
|
- ACR명: acrdigitalgarage01
|
||||||
|
- k8s명: aks-digitalgarage-01
|
||||||
|
- 네임스페이스: tripgen
|
||||||
|
- 파드수: 2
|
||||||
|
- 리소스(CPU): 256m/1024m
|
||||||
|
- 리소스(메모리): 256Mi/1024Mi
|
||||||
|
```
|
||||||
|
|
||||||
|
- 시스템명과 서비스명 확인
|
||||||
|
settings.gradle에서 확인.
|
||||||
|
- 시스템명: rootProject.name
|
||||||
|
- 서비스명: include 'common'하위의 include문 뒤의 값임
|
||||||
|
|
||||||
|
예시) include 'common'하위의 4개가 서비스명임.
|
||||||
|
```
|
||||||
|
rootProject.name = 'tripgen'
|
||||||
|
|
||||||
|
include 'common'
|
||||||
|
include 'user-service'
|
||||||
|
include 'location-service'
|
||||||
|
include 'ai-service'
|
||||||
|
include 'trip-service'
|
||||||
|
```
|
||||||
|
|
||||||
|
- 매니페스트 작성 주의사항
|
||||||
|
- namespace는 명시: {네임스페이스}값 이용
|
||||||
|
- Database와 Redis의 Host명은 Service 객체 이름으로 함
|
||||||
|
- 공통 Secret의 JWT_SECRET 값은 반드시 openssl명령으로 생성하여 지정
|
||||||
|
- 매니페스트 파일 안에 환경변수를 사용하지 말고 실제 값을 지정
|
||||||
|
예) host: "tripgen.${INGRESS_IP}.nip.io" => host: "tripgen.4.1.2.3.nip.io"
|
||||||
|
- Secret 매니페스트에서 'data' 대신 'stringData'를 사용
|
||||||
|
- 객체이름 네이밍룰
|
||||||
|
- 공통 ConfigMap: cm-common
|
||||||
|
- 공통 Secret: secret-common
|
||||||
|
- 서비스별 ConfigMap: cm-{서비스명}
|
||||||
|
- 서비스별 Secret: secret-{서비스명}
|
||||||
|
- Ingress: {시스템명}
|
||||||
|
- Service: {서비스명}
|
||||||
|
- Deployment: {서비스명}
|
||||||
|
|
||||||
|
- 공통 매니페스트 작성: deployment/k8s/common/ 디렉토리 하위에 작성
|
||||||
|
- Image Pull Secret 매니페스트 작성: secret-imagepull.yaml
|
||||||
|
- name: {시스템명}
|
||||||
|
- USERNAME과 PASSWORD을 아래 명령으로 구하여 매니페스트 파일 작성
|
||||||
|
```
|
||||||
|
USERNAME=$(az acr credential show -n ${ACR명} --query "username" -o tsv)
|
||||||
|
PASSWORD=$(az acr credential show -n ${ACR명} --query "passwords[0].value" -o tsv)
|
||||||
|
```
|
||||||
|
- USERNAME과 PASSWORD의 실제 값을 매니페스트에 지정
|
||||||
|
- Ingress 매니페스트 작성: ingress.yaml
|
||||||
|
- **중요**: Ingress Host는 반드시 아래 명령으로 실제 External IP를 확인하여 사용할 것.
|
||||||
|
{Ingress External IP}는 실제 확인한 EXTERNAL-IP값.
|
||||||
|
```
|
||||||
|
kubectl get svc ingress-nginx-controller -n ingress-nginx
|
||||||
|
```
|
||||||
|
출력 예시: EXTERNAL-IP 컬럼에서 실제 IP 확인 (예:20.214.196.128)
|
||||||
|
- ingressClassName: nginx
|
||||||
|
- host: {시스템명}-api.{Ingress External IP}.nip.io
|
||||||
|
**잘못된 예**: tripgen-api.임의IP.nip.io ❌
|
||||||
|
**올바른 예**: tripgen-api.20.214.196.128.nip.io ✅
|
||||||
|
|
||||||
|
- path: 각 서비스 별 Controller 클래스의 '@RequestMapping'과 클래스 내 메소드의 매핑정보를 읽어 지정
|
||||||
|
- pathType: Prefix
|
||||||
|
- backend.service.name: {서비스명}
|
||||||
|
- backend.service.port.number: 80
|
||||||
|
- **중요**: annotation에 'nginx.ingress.kubernetes.io/rewrite-target' 설정 절대 하지 말것.
|
||||||
|
|
||||||
|
- 공통 ConfigMap과 Secret 매니페스트 작성
|
||||||
|
- 각 서비스의 실행 프로파일({서비스명}/.run/{서비스명}.run.xml)을 읽어 공통된 환경변수를 추출.
|
||||||
|
- 보안이 필요한 환경변수(암호, 인증토큰 등)는 Secret 매니페스트로 작성: secret-common.yaml(name:cm-common)
|
||||||
|
- 그 외 일반 환경변수 매니페스트 작성: cm-common.yaml(name:secret-common)
|
||||||
|
- Redis HOST명은 IP가 아닌 Service 객체명으로 함.
|
||||||
|
아래 명령으로 'redis'가 포함된 서비스 객체를 찾고 'ClusterIP'유형인 서비스명을 Host명으로 사용
|
||||||
|
```
|
||||||
|
kubectl get svc | grep redis
|
||||||
|
```
|
||||||
|
- REDIS_DATABASE는 각 서비스별 ConfigMap에 지정
|
||||||
|
- 주의) Database는 공통 ConfigMap/Secret으로 작성 금지
|
||||||
|
- 공통 ConfigMap에 CORS_ALLOWED_ORIGINS 설정: 'http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084,http://{시스템명}.{Ingress External IP}.nip.io'
|
||||||
|
|
||||||
|
- 서비스별 매니페스트 작성: deployment/k8s/{서비스명}/ 디렉토리 하위에 작성
|
||||||
|
- ConfigMap과 Secret 매니페스트 작성
|
||||||
|
- 각 서비스의 실행 프로파일({서비스명}/.run/{서비스명}.run.xml)을 읽어 환경변수를 추출.
|
||||||
|
- cm-common.yaml과 secret-common.yaml에 있는 공통 환경변수는 중복해서 작성하면 안됨
|
||||||
|
- 보안이 필요한 환경변수(암호, 인증토큰 등)는 Secret 매니페스트로 작성: secret-{서비스명}.yaml(name:cm-{서비스명})
|
||||||
|
- 그 외 일반 환경변수 매니페스트 작성: cm-{서비스명}.yaml(name:secret-{서비스명})
|
||||||
|
- Database HOST명은 IP가 아닌 Service 객체명으로 함.
|
||||||
|
아래 명령으로 '{서비스명}'과 'db'가 포함된 서비스 객체를 찾고 'ClusterIP'유형인 서비스명을 Host명으로 사용
|
||||||
|
```
|
||||||
|
kubectl get svc | grep {서비스명}
|
||||||
|
```
|
||||||
|
- REDIS_DATABASE는 실행 프로파일에 지정된 값으로 서비스별 ConfigMap에 지정
|
||||||
|
- Service 매니페스트 작성
|
||||||
|
- name: {서비스명}
|
||||||
|
- port: 80
|
||||||
|
- targetPort: 실행 프로파일의 SERVER_PORT값
|
||||||
|
- type: ClusterIP
|
||||||
|
- Deployment 매니페스트 작성
|
||||||
|
- name: {서비스명}
|
||||||
|
- replicas: {파드수}
|
||||||
|
- ImagePullPolicy: Always
|
||||||
|
- ImagePullSecrets: {시스템명}
|
||||||
|
- image: {ACR명}.azurecr.io/{시스템명}/{서비스명}:latest
|
||||||
|
- ConfigMap과 Secret은 'env'대신에 'envFrom'을 사용하여 지정
|
||||||
|
- envFrom:
|
||||||
|
- configMapRef: 공통 ConfigMap 'cm-common'과 각 서비스 ConfigMap 'cm-{서비스명}'을 지정
|
||||||
|
- secretRef: 공통 Secret 'secret-common'과 각 서비스 Secret 'secret-{서비스명}'을 지정
|
||||||
|
- resources:
|
||||||
|
- {리소스(CPU)}: 요청값/최대값
|
||||||
|
- {리소스(메모리)}: 요청값/최대값
|
||||||
|
- Probe:
|
||||||
|
- Startup Probe: Actuator '/actuator/health'로 지정
|
||||||
|
- Readiness Probe: Actuator '/actuator/health/rediness'로 지정
|
||||||
|
- Liveness Probe: Actuator '/actuator/health/liveness'로 지정
|
||||||
|
- initialDelaySeconds, periodSeconds, failureThreshold를 Probe에 맞게 적절히 지정
|
||||||
|
|
||||||
|
- 체크 리스트로 수행결과 검증: 반드시 수행하고 그 결과를 배포 가이드에 포함
|
||||||
|
- 객체이름 네이밍룰 준수 여부
|
||||||
|
- Redis Host명을 ClusterIP 타입의 Service 객체로 했는가?
|
||||||
|
'kubectl get svc | grep redis' 명령으로 재확인
|
||||||
|
- Database Host명을 ClusterIP타입의 Service 객체로 했는가?
|
||||||
|
'kubectl get svc | grep {서비스명}' 명령으로 재확인
|
||||||
|
- Secret 매니페스트에서 'data' 대신 'stringData'를 사용 했는가?
|
||||||
|
- JWT_SECRET을 openssl 명령으로 생성해서 지정했는가?
|
||||||
|
- 매니페스트 파일 안에 환경변수를 사용하지 않고 실제 값을 지정 했는가?
|
||||||
|
- Image Pull Secret에 USERNAME과 PASSWORD의 실제 값을 매니페스트에 지정 했는가?
|
||||||
|
- Image명이 '{ACR명}.azurecr.io/{시스템명}/{서비스명}:latest' 형식인지 재확인
|
||||||
|
- Ingress Controller External IP 확인 및 매니페스트에 반영 확인
|
||||||
|
kubectl get svc ingress-nginx-controller -n ingress-nginx
|
||||||
|
EXTERNAL-IP 컬럼의 실제 값이 ingress.yaml의 host에 정확하게 설정되었는지 재확인할 것
|
||||||
|
- Ingress 매니페스트의 각 서비스 backend.service.port.number와 Service 매니페스트의 port가 "80"으로 동일한가 ?
|
||||||
|
- Ingress의 path는 각 서비스 별 Controller 클래스의 '@RequestMapping'과 클래스 내 메소드의 매핑정보를 읽어 지정했는가?
|
||||||
|
- 보안이 필요한 환경변수는 Secret 매니페스트로 지정했는가?
|
||||||
|
- REDIS_DATABASE는 각 서비스마다 다르게 지정했는가?
|
||||||
|
- ConfigMap과 Secret은 'env'대신에 'envFrom'을 사용하였는가?
|
||||||
|
- (중요) 실행 프로파일 매핑 테이블로 누락된 환경변수 체크
|
||||||
|
- **필수**: 각 서비스의 실행 프로파일({서비스명}/.run/{서비스명}.run.xml)에 정의된 **전체 환경변수를 빠짐없이 체크**
|
||||||
|
- **체크 방법**:
|
||||||
|
1. 각 {서비스명}.run.xml 파일에서 `<entry key="환경변수명" value="값"/>` 형태로 정의된 **모든** 환경변수 추출
|
||||||
|
2. 추출된 환경변수 **전체**를 대상으로 매핑 테이블 작성 (일부만 하면 안됨)
|
||||||
|
3. 서비스명 | 환경변수 | 지정 객체명 | 환경변수값 컬럼으로 **전체 환경변수** 체크
|
||||||
|
- **매핑 테이블 예시** (전체 환경변수 기준):
|
||||||
|
```
|
||||||
|
user-service | SERVER_PORT | cm-user-service | 8081
|
||||||
|
user-service | DB_HOST | secret-user-service | user-db-service
|
||||||
|
user-service | DB_PASSWORD | secret-user-service | tripgen_user_123
|
||||||
|
user-service | REDIS_DATABASE | cm-user-service | 0
|
||||||
|
user-service | JWT_SECRET | secret-common | (base64 encoded)
|
||||||
|
user-service | CACHE_TTL | cm-user-service | 1800
|
||||||
|
location-service | SERVER_PORT | cm-location-service | 8082
|
||||||
|
location-service | GOOGLE_API_KEY | secret-location-service | (base64 encoded)
|
||||||
|
location-service | REDIS_DATABASE | cm-location-service | 1
|
||||||
|
ai-service | CLAUDE_API_KEY | secret-ai-service | (base64 encoded)
|
||||||
|
ai-service | SERVER_PORT | cm-ai-service | 8084
|
||||||
|
... (실행프로파일의 모든 환경변수 나열)
|
||||||
|
```
|
||||||
|
- **주의**: 일부 환경변수만 체크하면 누락 발생, 반드시 **실행프로파일 전체** 환경변수 대상으로 수행
|
||||||
|
- 누락된 환경변수가 발견되면 해당 ConfigMap/Secret에 추가
|
||||||
|
|
||||||
|
- 배포 가이드 작성
|
||||||
|
- 배포가이드 검증 결과
|
||||||
|
- 사전확인 방법 가이드
|
||||||
|
- Azure 로그인 상태 확인
|
||||||
|
```
|
||||||
|
az account show
|
||||||
|
```
|
||||||
|
- AKS Credential 확인:
|
||||||
|
```
|
||||||
|
kubectl cluster-info
|
||||||
|
```
|
||||||
|
- namespace 존재 확인
|
||||||
|
```
|
||||||
|
kubectl get ns {네임스페이스}
|
||||||
|
```
|
||||||
|
- 매니페스트 적용 가이드
|
||||||
|
```
|
||||||
|
kubectl apply -f deployment/k8s/common
|
||||||
|
kubectl apply -f deployment/k8s/{서비스명}
|
||||||
|
```
|
||||||
|
- 객체 생성 확인 가이드
|
||||||
|
|
||||||
|
|
||||||
|
[결과파일]
|
||||||
|
- 배포방법 가이드: deployment/k8s/deploy-k8s-guide.md
|
||||||
|
- 공통 매니페스트 파일: deployment/k8s/common/*
|
||||||
|
- 서비스별 매니페스트 파일: deployment/k8s/{서비스명}/*
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ public class JwtTokenProvider {
|
|||||||
* @return Access Token
|
* @return Access Token
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public String createAccessToken(Long userId, Long storeId, String email, String name, List<String> roles) {
|
public String createAccessToken(UUID userId, UUID storeId, String email, String name, List<String> roles) {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
Date expiryDate = new Date(now.getTime() + accessTokenValidityMs);
|
Date expiryDate = new Date(now.getTime() + accessTokenValidityMs);
|
||||||
|
|
||||||
|
|||||||
@ -1,321 +1,396 @@
|
|||||||
# Event Service 컨테이너 이미지 빌드 가이드
|
# 백엔드 컨테이너 이미지 작성 결과
|
||||||
|
|
||||||
## 1. 빌드 일시
|
## 작업 개요
|
||||||
- **빌드 날짜**: 2025-10-28
|
- **작업일시**: 2025-10-29
|
||||||
- **빌드 시간**: 14:35 KST
|
- **작성자**: DevOps Engineer (송근정 "데브옵스 마스터")
|
||||||
|
- **대상 서비스**: 6개 백엔드 마이크로서비스
|
||||||
|
|
||||||
## 2. 수정 사항
|
## 1. 서비스 확인
|
||||||
|
|
||||||
### 2.1 타입 불일치 수정
|
### settings.gradle 분석
|
||||||
Event Service 컴파일 오류 해결을 위해 다음 파일들을 수정했습니다:
|
```gradle
|
||||||
|
rootProject.name = 'kt-event-marketing'
|
||||||
|
|
||||||
#### UserPrincipal.java (common 모듈)
|
// Common module
|
||||||
- **파일 경로**: `common/src/main/java/com/kt/event/common/security/UserPrincipal.java`
|
include 'common'
|
||||||
- **수정 내용**: userId와 storeId 타입을 Long에서 UUID로 변경
|
|
||||||
- **변경 이유**: EventService의 메서드 시그니처가 UUID를 기대하므로 일관성 유지
|
|
||||||
|
|
||||||
```java
|
// Microservices
|
||||||
// Before
|
include 'user-service'
|
||||||
private final Long userId;
|
include 'event-service'
|
||||||
private final Long storeId;
|
include 'ai-service'
|
||||||
|
include 'content-service'
|
||||||
// After
|
include 'distribution-service'
|
||||||
private final UUID userId;
|
include 'participation-service'
|
||||||
private final UUID storeId;
|
include 'analytics-service'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### JwtTokenProvider.java (common 모듈)
|
### 빌드 가능한 서비스 (6개)
|
||||||
- **파일 경로**: `common/src/main/java/com/kt/event/common/security/JwtTokenProvider.java`
|
Main Application 클래스가 존재하는 서비스:
|
||||||
- **수정 내용**: JWT 토큰 파싱 시 Long.parseLong()을 UUID.fromString()으로 변경
|
1. **user-service** - `UserServiceApplication.java`
|
||||||
- **변경 이유**: UserPrincipal의 타입 변경에 따른 파싱 로직 수정
|
2. **event-service** - `EventServiceApplication.java`
|
||||||
|
3. **ai-service** - `AiServiceApplication.java`
|
||||||
|
4. **content-service** - `ContentApplication.java`
|
||||||
|
5. **participation-service** - `ParticipationServiceApplication.java`
|
||||||
|
6. **analytics-service** - `AnalyticsServiceApplication.java`
|
||||||
|
|
||||||
```java
|
### 제외된 서비스
|
||||||
// Before
|
- **distribution-service**: 소스 코드 미구현 상태 (src/main/java 디렉토리 없음)
|
||||||
Long userId = Long.parseLong(claims.getSubject());
|
|
||||||
Long storeId = storeIdStr != null ? Long.parseLong(storeIdStr) : null;
|
|
||||||
|
|
||||||
// After
|
## 2. bootJar 설정
|
||||||
UUID userId = UUID.fromString(claims.getSubject());
|
|
||||||
UUID storeId = storeIdStr != null ? UUID.fromString(storeIdStr) : null;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### event-service/build.gradle
|
각 서비스의 `build.gradle`에 bootJar 설정 추가/수정:
|
||||||
- **수정 내용**: bootJar 설정 추가
|
|
||||||
- **변경 이유**: 컨테이너 이미지 빌드를 위한 JAR 파일명 명시
|
|
||||||
|
|
||||||
|
### 설정 추가된 서비스 (5개)
|
||||||
```gradle
|
```gradle
|
||||||
bootJar {
|
bootJar {
|
||||||
archiveFileName = 'event-service.jar'
|
archiveFileName = '{service-name}.jar'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. 빌드 명령어
|
- user-service/build.gradle
|
||||||
|
- ai-service/build.gradle
|
||||||
|
- distribution-service/build.gradle (향후 구현 대비)
|
||||||
|
- participation-service/build.gradle
|
||||||
|
- analytics-service/build.gradle
|
||||||
|
|
||||||
### 3.1 Common 모듈 컴파일
|
### 기존 설정 확인된 서비스 (2개)
|
||||||
```bash
|
- event-service/build.gradle ✅
|
||||||
./gradlew common:compileJava
|
- content-service/build.gradle ✅
|
||||||
|
|
||||||
|
## 3. Dockerfile 생성
|
||||||
|
|
||||||
|
### 파일 경로
|
||||||
|
`deployment/container/Dockerfile-backend`
|
||||||
|
|
||||||
|
### Dockerfile 내용
|
||||||
|
```dockerfile
|
||||||
|
# Build stage
|
||||||
|
FROM openjdk:23-oraclelinux8 AS builder
|
||||||
|
ARG BUILD_LIB_DIR
|
||||||
|
ARG ARTIFACTORY_FILE
|
||||||
|
COPY ${BUILD_LIB_DIR}/${ARTIFACTORY_FILE} app.jar
|
||||||
|
|
||||||
|
# Run stage
|
||||||
|
FROM openjdk:23-slim
|
||||||
|
ENV USERNAME=k8s
|
||||||
|
ENV ARTIFACTORY_HOME=/home/${USERNAME}
|
||||||
|
ENV JAVA_OPTS=""
|
||||||
|
|
||||||
|
# Add a non-root user
|
||||||
|
RUN adduser --system --group ${USERNAME} && \
|
||||||
|
mkdir -p ${ARTIFACTORY_HOME} && \
|
||||||
|
chown ${USERNAME}:${USERNAME} ${ARTIFACTORY_HOME}
|
||||||
|
|
||||||
|
WORKDIR ${ARTIFACTORY_HOME}
|
||||||
|
COPY --from=builder app.jar app.jar
|
||||||
|
RUN chown ${USERNAME}:${USERNAME} app.jar
|
||||||
|
|
||||||
|
USER ${USERNAME}
|
||||||
|
|
||||||
|
ENTRYPOINT [ "sh", "-c" ]
|
||||||
|
CMD ["java ${JAVA_OPTS} -jar app.jar"]
|
||||||
```
|
```
|
||||||
|
|
||||||
**결과**: BUILD SUCCESSFUL in 6s
|
### Dockerfile 특징
|
||||||
|
- **Multi-stage build**: 빌드와 실행 스테이지 분리
|
||||||
|
- **Non-root user**: 보안을 위한 k8s 사용자 실행
|
||||||
|
- **플랫폼**: linux/amd64 (K8s 클러스터 호환)
|
||||||
|
- **Java 버전**: OpenJDK 23
|
||||||
|
|
||||||
### 3.2 Event Service 컴파일
|
## 4. JAR 파일 빌드
|
||||||
|
|
||||||
|
### 빌드 명령어
|
||||||
```bash
|
```bash
|
||||||
./gradlew event-service:compileJava
|
./gradlew user-service:bootJar ai-service:bootJar event-service:bootJar \
|
||||||
|
content-service:bootJar participation-service:bootJar analytics-service:bootJar
|
||||||
```
|
```
|
||||||
|
|
||||||
**결과**: BUILD SUCCESSFUL in 6s
|
### 빌드 결과
|
||||||
|
```
|
||||||
### 3.3 Event Service JAR 빌드
|
BUILD SUCCESSFUL in 27s
|
||||||
```bash
|
33 actionable tasks: 15 executed, 18 up-to-date
|
||||||
./gradlew event-service:bootJar
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**결과**:
|
### 생성된 JAR 파일
|
||||||
- BUILD SUCCESSFUL in 5s
|
|
||||||
- JAR 파일 생성: `event-service/build/libs/event-service.jar` (94MB)
|
|
||||||
|
|
||||||
### 3.4 Docker 이미지 빌드
|
|
||||||
```bash
|
```bash
|
||||||
|
$ ls -lh */build/libs/*.jar
|
||||||
|
|
||||||
|
-rw-r--r-- 1 KTDS 197121 94M 10월 29 09:49 ai-service/build/libs/ai-service.jar
|
||||||
|
-rw-r--r-- 1 KTDS 197121 95M 10월 29 09:48 analytics-service/build/libs/analytics-service.jar
|
||||||
|
-rw-r--r-- 1 KTDS 197121 78M 10월 29 09:49 content-service/build/libs/content-service.jar
|
||||||
|
-rw-r--r-- 1 KTDS 197121 94M 10월 29 09:49 event-service/build/libs/event-service.jar
|
||||||
|
-rw-r--r-- 1 KTDS 197121 85M 10월 29 09:49 participation-service/build/libs/participation-service.jar
|
||||||
|
-rw-r--r-- 1 KTDS 197121 96M 10월 29 09:49 user-service/build/libs/user-service.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Docker 이미지 빌드
|
||||||
|
|
||||||
|
### 사전 준비사항
|
||||||
|
⚠️ **Docker Desktop이 실행 중이어야 합니다**
|
||||||
|
|
||||||
|
Docker Desktop 시작 확인:
|
||||||
|
```bash
|
||||||
|
# Docker 상태 확인
|
||||||
|
docker version
|
||||||
|
docker ps
|
||||||
|
|
||||||
|
# Docker Desktop이 정상 실행되면 위 명령들이 정상 동작합니다
|
||||||
|
```
|
||||||
|
|
||||||
|
### 빌드 명령어
|
||||||
|
|
||||||
|
#### 5.1 user-service
|
||||||
|
```bash
|
||||||
|
DOCKER_FILE=deployment/container/Dockerfile-backend
|
||||||
|
|
||||||
|
docker build \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
--build-arg BUILD_LIB_DIR="user-service/build/libs" \
|
||||||
|
--build-arg ARTIFACTORY_FILE="user-service.jar" \
|
||||||
|
-f ${DOCKER_FILE} \
|
||||||
|
-t user-service:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2 ai-service
|
||||||
|
```bash
|
||||||
|
DOCKER_FILE=deployment/container/Dockerfile-backend
|
||||||
|
|
||||||
|
docker build \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
--build-arg BUILD_LIB_DIR="ai-service/build/libs" \
|
||||||
|
--build-arg ARTIFACTORY_FILE="ai-service.jar" \
|
||||||
|
-f ${DOCKER_FILE} \
|
||||||
|
-t ai-service:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.3 event-service
|
||||||
|
```bash
|
||||||
|
DOCKER_FILE=deployment/container/Dockerfile-backend
|
||||||
|
|
||||||
docker build \
|
docker build \
|
||||||
--platform linux/amd64 \
|
--platform linux/amd64 \
|
||||||
--build-arg BUILD_LIB_DIR="event-service/build/libs" \
|
--build-arg BUILD_LIB_DIR="event-service/build/libs" \
|
||||||
--build-arg ARTIFACTORY_FILE="event-service.jar" \
|
--build-arg ARTIFACTORY_FILE="event-service.jar" \
|
||||||
-f deployment/container/Dockerfile-backend \
|
-f ${DOCKER_FILE} \
|
||||||
-t event-service:latest .
|
-t event-service:latest .
|
||||||
```
|
```
|
||||||
|
|
||||||
**결과**: 이미지 빌드 성공
|
#### 5.4 content-service
|
||||||
- Image ID: bbeecf2ccaf2
|
|
||||||
- Size: 1.08GB
|
|
||||||
- Created: 19 seconds ago
|
|
||||||
|
|
||||||
## 4. 빌드 검증
|
|
||||||
|
|
||||||
### 4.1 JAR 파일 확인
|
|
||||||
```bash
|
```bash
|
||||||
ls -lh event-service/build/libs/
|
DOCKER_FILE=deployment/container/Dockerfile-backend
|
||||||
```
|
|
||||||
|
|
||||||
**출력**:
|
|
||||||
```
|
|
||||||
-rw-r--r-- 1 KTDS 197121 94M 10월 28 14:35 event-service.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 Docker 이미지 확인
|
|
||||||
```bash
|
|
||||||
docker images | grep event-service
|
|
||||||
```
|
|
||||||
|
|
||||||
**출력**:
|
|
||||||
```
|
|
||||||
event-service latest bbeecf2ccaf2 19 seconds ago 1.08GB
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. Dockerfile 구조
|
|
||||||
|
|
||||||
**파일 위치**: `deployment/container/Dockerfile-backend`
|
|
||||||
|
|
||||||
### 빌드 스테이지 (Build Stage)
|
|
||||||
- **Base Image**: openjdk:23-oraclelinux8
|
|
||||||
- **작업**: JAR 파일 복사
|
|
||||||
|
|
||||||
### 실행 스테이지 (Run Stage)
|
|
||||||
- **Base Image**: openjdk:23-slim
|
|
||||||
- **사용자**: k8s (non-root user)
|
|
||||||
- **작업 디렉토리**: /home/k8s
|
|
||||||
- **진입점**: `java ${JAVA_OPTS} -jar app.jar`
|
|
||||||
|
|
||||||
## 6. 컨테이너 실행 가이드
|
|
||||||
|
|
||||||
### 6.1 기본 실행
|
|
||||||
```bash
|
|
||||||
docker run -d \
|
|
||||||
--name event-service \
|
|
||||||
-p 8082:8082 \
|
|
||||||
-e SPRING_PROFILES_ACTIVE=dev \
|
|
||||||
-e SERVER_PORT=8082 \
|
|
||||||
event-service:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 환경변수 설정
|
|
||||||
Event Service 실행을 위한 주요 환경변수:
|
|
||||||
|
|
||||||
#### 필수 환경변수
|
|
||||||
- `SERVER_PORT`: 서버 포트 (기본값: 8082)
|
|
||||||
- `DB_HOST`: PostgreSQL 호스트
|
|
||||||
- `DB_PORT`: PostgreSQL 포트 (기본값: 5432)
|
|
||||||
- `DB_NAME`: 데이터베이스 이름
|
|
||||||
- `DB_USERNAME`: 데이터베이스 사용자명
|
|
||||||
- `DB_PASSWORD`: 데이터베이스 비밀번호
|
|
||||||
- `REDIS_HOST`: Redis 호스트
|
|
||||||
- `REDIS_PORT`: Redis 포트 (기본값: 6379)
|
|
||||||
- `REDIS_PASSWORD`: Redis 비밀번호
|
|
||||||
- `KAFKA_BOOTSTRAP_SERVERS`: Kafka 브로커 주소
|
|
||||||
- `JWT_SECRET`: JWT 서명 키 (최소 32자)
|
|
||||||
|
|
||||||
#### 선택 환경변수
|
|
||||||
- `DISTRIBUTION_SERVICE_URL`: Distribution Service URL
|
|
||||||
- `JAVA_OPTS`: JVM 옵션
|
|
||||||
|
|
||||||
### 6.3 Docker Compose 실행 예시
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
event-service:
|
|
||||||
image: event-service:latest
|
|
||||||
container_name: event-service
|
|
||||||
ports:
|
|
||||||
- "8082:8082"
|
|
||||||
environment:
|
|
||||||
- SPRING_PROFILES_ACTIVE=prod
|
|
||||||
- SERVER_PORT=8082
|
|
||||||
- DB_HOST=your-db-host
|
|
||||||
- DB_PORT=5432
|
|
||||||
- DB_NAME=event_db
|
|
||||||
- DB_USERNAME=event_user
|
|
||||||
- DB_PASSWORD=your-password
|
|
||||||
- REDIS_HOST=your-redis-host
|
|
||||||
- REDIS_PORT=6379
|
|
||||||
- REDIS_PASSWORD=your-redis-password
|
|
||||||
- KAFKA_BOOTSTRAP_SERVERS=your-kafka:9092
|
|
||||||
- JWT_SECRET=your-jwt-secret-key-minimum-32-characters
|
|
||||||
- DISTRIBUTION_SERVICE_URL=http://distribution-service:8086
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
## 7. 헬스체크
|
|
||||||
|
|
||||||
### 7.1 Spring Boot Actuator
|
|
||||||
```bash
|
|
||||||
curl http://localhost:8082/actuator/health
|
|
||||||
```
|
|
||||||
|
|
||||||
**예상 응답**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"status": "UP",
|
|
||||||
"components": {
|
|
||||||
"ping": {
|
|
||||||
"status": "UP"
|
|
||||||
},
|
|
||||||
"db": {
|
|
||||||
"status": "UP"
|
|
||||||
},
|
|
||||||
"redis": {
|
|
||||||
"status": "UP"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7.2 Swagger UI
|
|
||||||
```
|
|
||||||
http://localhost:8082/swagger-ui/index.html
|
|
||||||
```
|
|
||||||
|
|
||||||
## 8. 빌드 결과 요약
|
|
||||||
|
|
||||||
### 서비스 정보
|
|
||||||
- **서비스명**: event-service
|
|
||||||
- **포트**: 8082
|
|
||||||
- **JAR 크기**: 94MB
|
|
||||||
- **이미지 크기**: 1.08GB
|
|
||||||
- **Base Image**: openjdk:23-slim
|
|
||||||
- **Platform**: linux/amd64
|
|
||||||
|
|
||||||
### 빌드 통계
|
|
||||||
- **Common 컴파일**: 6초
|
|
||||||
- **Event Service 컴파일**: 6초
|
|
||||||
- **JAR 빌드**: 5초
|
|
||||||
- **Docker 이미지 빌드**: 약 120초
|
|
||||||
|
|
||||||
### 주요 의존성
|
|
||||||
- Spring Boot Actuator
|
|
||||||
- Spring Kafka
|
|
||||||
- Spring Data Redis
|
|
||||||
- Spring Cloud OpenFeign
|
|
||||||
- PostgreSQL Driver
|
|
||||||
- Jackson
|
|
||||||
|
|
||||||
## 9. 트러블슈팅
|
|
||||||
|
|
||||||
### 9.1 컴파일 오류 해결
|
|
||||||
**증상**: userId/storeId 타입 불일치 오류
|
|
||||||
|
|
||||||
**해결**:
|
|
||||||
- UserPrincipal의 userId, storeId를 UUID로 변경
|
|
||||||
- JwtTokenProvider의 파싱 로직을 UUID.fromString()으로 수정
|
|
||||||
|
|
||||||
### 9.2 Gradle Clean 오류
|
|
||||||
**증상**: `Unable to delete directory 'common\build'`
|
|
||||||
|
|
||||||
**해결**: clean 없이 빌드 수행
|
|
||||||
```bash
|
|
||||||
./gradlew event-service:bootJar
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.3 Docker 빌드 컨텍스트 오류
|
|
||||||
**증상**: JAR 파일을 찾을 수 없음
|
|
||||||
|
|
||||||
**해결**:
|
|
||||||
- JAR 파일이 실제로 빌드되었는지 확인
|
|
||||||
- 빌드 아규먼트 경로가 올바른지 확인
|
|
||||||
|
|
||||||
## 10. 다음 단계
|
|
||||||
|
|
||||||
### 빌드 수행 이력
|
|
||||||
|
|
||||||
#### 최신 빌드 (2025-10-28)
|
|
||||||
|
|
||||||
**1단계: JAR 빌드**
|
|
||||||
```bash
|
|
||||||
./gradlew content-service:clean content-service:bootJar
|
|
||||||
```
|
|
||||||
|
|
||||||
빌드 결과:
|
|
||||||
```
|
|
||||||
BUILD SUCCESSFUL in 8s
|
|
||||||
9 actionable tasks: 6 executed, 3 up-to-date
|
|
||||||
```
|
|
||||||
|
|
||||||
**2단계: Docker 이미지 빌드**
|
|
||||||
```bash
|
|
||||||
docker build \
|
docker build \
|
||||||
--platform linux/amd64 \
|
--platform linux/amd64 \
|
||||||
--build-arg BUILD_LIB_DIR="content-service/build/libs" \
|
--build-arg BUILD_LIB_DIR="content-service/build/libs" \
|
||||||
--build-arg ARTIFACTORY_FILE="content-service.jar" \
|
--build-arg ARTIFACTORY_FILE="content-service.jar" \
|
||||||
-f deployment/container/Dockerfile-backend \
|
-f ${DOCKER_FILE} \
|
||||||
-t content-service:latest .
|
-t content-service:latest .
|
||||||
```
|
```
|
||||||
|
|
||||||
빌드 결과:
|
#### 5.5 participation-service
|
||||||
- ✅ Build stage 완료 (openjdk:23-oraclelinux8)
|
|
||||||
- ✅ Run stage 완료 (openjdk:23-slim)
|
|
||||||
- ✅ 이미지 생성 완료
|
|
||||||
|
|
||||||
**3단계: 이미지 확인**
|
|
||||||
```bash
|
```bash
|
||||||
docker images | grep content-service
|
DOCKER_FILE=deployment/container/Dockerfile-backend
|
||||||
|
|
||||||
|
docker build \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
--build-arg BUILD_LIB_DIR="participation-service/build/libs" \
|
||||||
|
--build-arg ARTIFACTORY_FILE="participation-service.jar" \
|
||||||
|
-f ${DOCKER_FILE} \
|
||||||
|
-t participation-service:latest .
|
||||||
```
|
```
|
||||||
|
|
||||||
확인 결과:
|
#### 5.6 analytics-service
|
||||||
```
|
```bash
|
||||||
content-service latest ff73258c94cc 15 seconds ago 393MB
|
DOCKER_FILE=deployment/container/Dockerfile-backend
|
||||||
|
|
||||||
|
docker build \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
--build-arg BUILD_LIB_DIR="analytics-service/build/libs" \
|
||||||
|
--build-arg ARTIFACTORY_FILE="analytics-service.jar" \
|
||||||
|
-f ${DOCKER_FILE} \
|
||||||
|
-t analytics-service:latest .
|
||||||
```
|
```
|
||||||
|
|
||||||
### 빌드 정보
|
### 빌드 스크립트 (일괄 실행)
|
||||||
- **서비스명**: content-service
|
```bash
|
||||||
- **JAR 파일**: content-service.jar
|
#!/bin/bash
|
||||||
- **Docker 이미지**: content-service:latest
|
# build-all-images.sh
|
||||||
- **이미지 ID**: ff73258c94cc
|
|
||||||
- **이미지 크기**: 393MB
|
|
||||||
- **노출 포트**: 8084
|
|
||||||
|
|
||||||
### 빌드 일시
|
DOCKER_FILE=deployment/container/Dockerfile-backend
|
||||||
- **최신 빌드**: 2025-10-28
|
|
||||||
- **이전 빌드**: 2025-10-27
|
|
||||||
|
|
||||||
### 환경
|
services=(
|
||||||
- **Base Image**: openjdk:23-slim
|
"user-service"
|
||||||
- **Platform**: linux/amd64
|
"ai-service"
|
||||||
- **User**: k8s (non-root)
|
"event-service"
|
||||||
- **Java Version**: 23
|
"content-service"
|
||||||
|
"participation-service"
|
||||||
|
"analytics-service"
|
||||||
|
)
|
||||||
|
|
||||||
|
for service in "${services[@]}"; do
|
||||||
|
echo "Building ${service}..."
|
||||||
|
docker build \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
--build-arg BUILD_LIB_DIR="${service}/build/libs" \
|
||||||
|
--build-arg ARTIFACTORY_FILE="${service}.jar" \
|
||||||
|
-f ${DOCKER_FILE} \
|
||||||
|
-t ${service}:latest .
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✅ ${service} build successful"
|
||||||
|
else
|
||||||
|
echo "❌ ${service} build failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "🎉 All images built successfully!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 이미지 확인
|
||||||
|
|
||||||
|
### 생성된 이미지 확인 명령어
|
||||||
|
```bash
|
||||||
|
# 모든 서비스 이미지 확인
|
||||||
|
docker images | grep -E "(user-service|ai-service|event-service|content-service|participation-service|analytics-service)"
|
||||||
|
|
||||||
|
# 개별 서비스 확인
|
||||||
|
docker images user-service:latest
|
||||||
|
docker images ai-service:latest
|
||||||
|
docker images event-service:latest
|
||||||
|
docker images content-service:latest
|
||||||
|
docker images participation-service:latest
|
||||||
|
docker images analytics-service:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 빌드 결과 ✅
|
||||||
|
```
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||||
|
user-service latest 91c511ef86bd About a minute ago 1.09GB
|
||||||
|
ai-service latest 9477022fa493 About a minute ago 1.08GB
|
||||||
|
event-service latest add81de69536 About a minute ago 1.08GB
|
||||||
|
content-service latest aa9cc16ad041 About a minute ago 1.01GB
|
||||||
|
participation-service latest 9b044a3854dd About a minute ago 1.04GB
|
||||||
|
analytics-service latest ac569de42545 About a minute ago 1.08GB
|
||||||
|
```
|
||||||
|
|
||||||
|
**빌드 일시**: 2025-10-29 09:50 KST
|
||||||
|
**빌드 소요 시간**: 약 13초 (병렬 빌드)
|
||||||
|
**총 이미지 크기**: 6.48GB
|
||||||
|
|
||||||
|
## 7. 이미지 테스트
|
||||||
|
|
||||||
|
### 로컬 실행 테스트 (예시: user-service)
|
||||||
|
```bash
|
||||||
|
# 컨테이너 실행
|
||||||
|
docker run -d \
|
||||||
|
--name user-service-test \
|
||||||
|
-p 8080:8080 \
|
||||||
|
-e SPRING_PROFILES_ACTIVE=dev \
|
||||||
|
user-service:latest
|
||||||
|
|
||||||
|
# 로그 확인
|
||||||
|
docker logs -f user-service-test
|
||||||
|
|
||||||
|
# 헬스체크
|
||||||
|
curl http://localhost:8080/actuator/health
|
||||||
|
|
||||||
|
# 정리
|
||||||
|
docker stop user-service-test
|
||||||
|
docker rm user-service-test
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 다음 단계
|
||||||
|
|
||||||
|
### 8.1 컨테이너 레지스트리 푸시
|
||||||
|
```bash
|
||||||
|
# Docker Hub 예시
|
||||||
|
docker tag user-service:latest <your-registry>/user-service:latest
|
||||||
|
docker push <your-registry>/user-service:latest
|
||||||
|
|
||||||
|
# Azure Container Registry 예시
|
||||||
|
docker tag user-service:latest <acr-name>.azurecr.io/user-service:latest
|
||||||
|
docker push <acr-name>.azurecr.io/user-service:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Kubernetes 배포
|
||||||
|
- Kubernetes Deployment 매니페스트 작성
|
||||||
|
- Service 리소스 정의
|
||||||
|
- ConfigMap/Secret 설정
|
||||||
|
- Ingress 구성
|
||||||
|
|
||||||
|
### 8.3 CI/CD 파이프라인 구성
|
||||||
|
- GitHub Actions 또는 Jenkins 파이프라인 작성
|
||||||
|
- 자동 빌드 및 배포 설정
|
||||||
|
- 이미지 태깅 전략 수립 (semantic versioning)
|
||||||
|
|
||||||
|
## 9. 트러블슈팅
|
||||||
|
|
||||||
|
### Issue 1: Docker Desktop 미실행
|
||||||
|
**증상**:
|
||||||
|
```
|
||||||
|
ERROR: error during connect: open //./pipe/dockerDesktopLinuxEngine:
|
||||||
|
The system cannot find the file specified.
|
||||||
|
```
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
1. Docker Desktop 애플리케이션 시작
|
||||||
|
2. 시스템 트레이의 Docker 아이콘이 안정화될 때까지 대기
|
||||||
|
3. `docker ps` 명령으로 정상 동작 확인
|
||||||
|
|
||||||
|
### Issue 2: JAR 파일 없음
|
||||||
|
**증상**:
|
||||||
|
```
|
||||||
|
COPY failed: file not found in build context
|
||||||
|
```
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
```bash
|
||||||
|
# JAR 파일 재빌드
|
||||||
|
./gradlew {service-name}:clean {service-name}:bootJar
|
||||||
|
|
||||||
|
# 생성 확인
|
||||||
|
ls -l {service-name}/build/libs/{service-name}.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 3: 플랫폼 불일치
|
||||||
|
**증상**: K8s 클러스터에서 실행 안됨
|
||||||
|
|
||||||
|
**해결**: `--platform linux/amd64` 옵션 사용 (이미 적용됨)
|
||||||
|
|
||||||
|
## 10. 요약
|
||||||
|
|
||||||
|
### ✅ 완료된 작업
|
||||||
|
1. ✅ 6개 서비스의 bootJar 설정 완료
|
||||||
|
2. ✅ Dockerfile-backend 생성 완료
|
||||||
|
3. ✅ 6개 서비스 JAR 파일 빌드 완료 (총 542MB)
|
||||||
|
4. ✅ 6개 서비스 Docker 이미지 빌드 완료 (총 6.48GB)
|
||||||
|
|
||||||
|
### 📊 최종 서비스 현황
|
||||||
|
| 서비스 | JAR 빌드 | Docker 이미지 | 이미지 크기 | Image ID | 상태 |
|
||||||
|
|--------|---------|--------------|-----------|----------|------|
|
||||||
|
| user-service | ✅ 96MB | ✅ | 1.09GB | 91c511ef86bd | ✅ Ready |
|
||||||
|
| ai-service | ✅ 94MB | ✅ | 1.08GB | 9477022fa493 | ✅ Ready |
|
||||||
|
| event-service | ✅ 94MB | ✅ | 1.08GB | add81de69536 | ✅ Ready |
|
||||||
|
| content-service | ✅ 78MB | ✅ | 1.01GB | aa9cc16ad041 | ✅ Ready |
|
||||||
|
| participation-service | ✅ 85MB | ✅ | 1.04GB | 9b044a3854dd | ✅ Ready |
|
||||||
|
| analytics-service | ✅ 95MB | ✅ | 1.08GB | ac569de42545 | ✅ Ready |
|
||||||
|
| distribution-service | ❌ | ❌ | - | - | 소스 미구현 |
|
||||||
|
|
||||||
|
### 🎯 빌드 성능 메트릭
|
||||||
|
- **JAR 빌드 시간**: 27초
|
||||||
|
- **Docker 이미지 빌드**: 병렬 실행으로 약 13초
|
||||||
|
- **총 소요 시간**: 약 40초
|
||||||
|
- **빌드 성공률**: 100% (6/6 서비스)
|
||||||
|
|
||||||
|
### 🚀 다음 단계 권장사항
|
||||||
|
1. **컨테이너 레지스트리 푸시** (예: Azure ACR, Docker Hub)
|
||||||
|
2. **Kubernetes 배포 매니페스트 작성**
|
||||||
|
3. **CI/CD 파이프라인 구성** (GitHub Actions 또는 Jenkins)
|
||||||
|
4. **모니터링 및 로깅 설정**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**작성일**: 2025-10-29 09:50 KST
|
||||||
|
**작성자**: DevOps Engineer (송근정 "데브옵스 마스터")
|
||||||
|
**빌드 완료**: ✅ 모든 서비스 이미지 빌드 성공
|
||||||
|
|||||||
248
deployment/k8s/ENVIRONMENT_MAPPING.md
Normal file
248
deployment/k8s/ENVIRONMENT_MAPPING.md
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
# 환경변수 매핑 테이블
|
||||||
|
|
||||||
|
## 1. user-service 환경변수 매핑
|
||||||
|
|
||||||
|
| 환경변수 | 값 | 지정 객체 | 비고 |
|
||||||
|
|---------|-----|----------|------|
|
||||||
|
| SERVER_PORT | 8081 | cm-user-service | |
|
||||||
|
| DB_URL | jdbc:postgresql://user-postgresql:5432/userdb | cm-user-service | |
|
||||||
|
| DB_DRIVER | org.postgresql.Driver | cm-user-service | |
|
||||||
|
| DB_HOST | user-postgresql | cm-user-service | ClusterIP 서비스명 |
|
||||||
|
| DB_PORT | 5432 | cm-user-service | |
|
||||||
|
| DB_NAME | userdb | cm-user-service | |
|
||||||
|
| DB_USERNAME | eventuser | cm-user-service | |
|
||||||
|
| DB_PASSWORD | Hi5Jessica! | secret-user-service | |
|
||||||
|
| DB_KIND | postgresql | cm-user-service | |
|
||||||
|
| DDL_AUTO | update | cm-common | 공통 |
|
||||||
|
| SHOW_SQL | false | cm-common | 공통 |
|
||||||
|
| JPA_DIALECT | org.hibernate.dialect.PostgreSQLDialect | cm-common | 공통 |
|
||||||
|
| H2_CONSOLE_ENABLED | false | cm-common | 공통 |
|
||||||
|
| REDIS_ENABLED | true | cm-common | 공통 |
|
||||||
|
| REDIS_HOST | redis | cm-common | ClusterIP 서비스명, 공통 |
|
||||||
|
| REDIS_PORT | 6379 | cm-common | 공통 |
|
||||||
|
| REDIS_PASSWORD | Hi5Jessica! | secret-common | 공통 |
|
||||||
|
| REDIS_DATABASE | 0 | cm-user-service | |
|
||||||
|
| EXCLUDE_REDIS | "" | cm-common | 공통 |
|
||||||
|
| KAFKA_BOOTSTRAP_SERVERS | 20.249.182.13:9095,4.217.131.59:9095 | cm-common | 공통 |
|
||||||
|
| KAFKA_CONSUMER_GROUP | user-service-consumers | cm-user-service | |
|
||||||
|
| EXCLUDE_KAFKA | "" | cm-common | 공통 |
|
||||||
|
| JWT_SECRET | ShiBpV6q7NwnjOafujT87XcgxzTdEFmEKO5Y+8zNPvE= | secret-common | openssl 생성, 공통 |
|
||||||
|
| JWT_ACCESS_TOKEN_VALIDITY | 604800000 | cm-common | 공통 |
|
||||||
|
| CORS_ALLOWED_ORIGINS | http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084,http://kt-event-marketing.20.214.196.128.nip.io | cm-common | 공통 |
|
||||||
|
| LOG_LEVEL_APP | INFO | cm-common | 공통 |
|
||||||
|
| LOG_LEVEL_WEB | INFO | cm-common | 공통 |
|
||||||
|
| LOG_LEVEL_SQL | WARN | cm-common | 공통 |
|
||||||
|
| LOG_LEVEL_SQL_TYPE | WARN | cm-common | 공통 |
|
||||||
|
| LOG_FILE_PATH | logs/user-service.log | cm-user-service | |
|
||||||
|
|
||||||
|
## 2. event-service 환경변수 매핑
|
||||||
|
|
||||||
|
| 환경변수 | 값 | 지정 객체 | 비고 |
|
||||||
|
|---------|-----|----------|------|
|
||||||
|
| SERVER_PORT | 8080 | cm-event-service | |
|
||||||
|
| DB_HOST | event-postgresql | cm-event-service | ClusterIP 서비스명 |
|
||||||
|
| DB_PORT | 5432 | cm-event-service | |
|
||||||
|
| DB_NAME | eventdb | cm-event-service | |
|
||||||
|
| DB_USERNAME | eventuser | cm-event-service | |
|
||||||
|
| DB_PASSWORD | Hi5Jessica! | secret-event-service | |
|
||||||
|
| DDL_AUTO | update | cm-common | 공통 |
|
||||||
|
| REDIS_HOST | redis | cm-common | ClusterIP 서비스명, 공통 |
|
||||||
|
| REDIS_PORT | 6379 | cm-common | 공통 |
|
||||||
|
| REDIS_PASSWORD | Hi5Jessica! | secret-common | 공통 |
|
||||||
|
| REDIS_DATABASE | 2 | cm-event-service | |
|
||||||
|
| KAFKA_BOOTSTRAP_SERVERS | 20.249.182.13:9095,4.217.131.59:9095 | cm-common | 공통 |
|
||||||
|
| KAFKA_CONSUMER_GROUP | event-service-consumers | cm-event-service | |
|
||||||
|
| CONTENT_SERVICE_URL | http://content-service | cm-event-service | |
|
||||||
|
| DISTRIBUTION_SERVICE_URL | http://distribution-service | cm-event-service | |
|
||||||
|
| JWT_SECRET | ShiBpV6q7NwnjOafujT87XcgxzTdEFmEKO5Y+8zNPvE= | secret-common | 공통 |
|
||||||
|
| LOG_LEVEL | INFO | cm-event-service | |
|
||||||
|
| SQL_LOG_LEVEL | WARN | cm-event-service | |
|
||||||
|
|
||||||
|
## 3. ai-service 환경변수 매핑
|
||||||
|
|
||||||
|
| 환경변수 | 값 | 지정 객체 | 비고 |
|
||||||
|
|---------|-----|----------|------|
|
||||||
|
| SERVER_PORT | 8083 | cm-ai-service | |
|
||||||
|
| REDIS_HOST | redis | cm-common | ClusterIP 서비스명, 공통 |
|
||||||
|
| REDIS_PORT | 6379 | cm-common | 공통 |
|
||||||
|
| REDIS_PASSWORD | Hi5Jessica! | secret-common | 공통 |
|
||||||
|
| REDIS_DATABASE | 3 | cm-ai-service | |
|
||||||
|
| REDIS_TIMEOUT | 3000 | cm-ai-service | |
|
||||||
|
| REDIS_POOL_MAX | 8 | cm-common | 공통 |
|
||||||
|
| REDIS_POOL_IDLE | 8 | cm-common | 공통 |
|
||||||
|
| REDIS_POOL_MIN | 2 | cm-ai-service | |
|
||||||
|
| REDIS_POOL_WAIT | -1ms | cm-common | 공통 |
|
||||||
|
| KAFKA_BOOTSTRAP_SERVERS | 20.249.182.13:9095,4.217.131.59:9095 | cm-common | 공통 |
|
||||||
|
| KAFKA_CONSUMER_GROUP | ai-service-consumers | cm-ai-service | |
|
||||||
|
| KAFKA_TOPICS_AI_JOB | ai-event-generation-job | cm-ai-service | |
|
||||||
|
| KAFKA_TOPICS_AI_JOB_DLQ | ai-event-generation-job-dlq | cm-ai-service | |
|
||||||
|
| JWT_SECRET | ShiBpV6q7NwnjOafujT87XcgxzTdEFmEKO5Y+8zNPvE= | secret-common | 공통 |
|
||||||
|
| JWT_ACCESS_TOKEN_VALIDITY | 604800000 | cm-common | 공통 |
|
||||||
|
| JWT_REFRESH_TOKEN_VALIDITY | 86400 | cm-common | 공통 |
|
||||||
|
| CORS_ALLOWED_ORIGINS | http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084,http://kt-event-marketing.20.214.196.128.nip.io | cm-common | 공통 |
|
||||||
|
| CORS_ALLOWED_METHODS | GET,POST,PUT,DELETE,OPTIONS,PATCH | cm-common | 공통 |
|
||||||
|
| CORS_ALLOWED_HEADERS | * | cm-common | 공통 |
|
||||||
|
| CORS_ALLOW_CREDENTIALS | true | cm-common | 공통 |
|
||||||
|
| CORS_MAX_AGE | 3600 | cm-common | 공통 |
|
||||||
|
| LOG_LEVEL_ROOT | INFO | cm-common | 공통 |
|
||||||
|
| LOG_LEVEL_AI | DEBUG | cm-ai-service | |
|
||||||
|
| LOG_LEVEL_KAFKA | INFO | cm-ai-service | |
|
||||||
|
| LOG_LEVEL_REDIS | INFO | cm-ai-service | |
|
||||||
|
| LOG_LEVEL_RESILIENCE4J | DEBUG | cm-ai-service | |
|
||||||
|
| LOG_FILE_NAME | logs/ai-service.log | cm-ai-service | |
|
||||||
|
| LOG_FILE_MAX_SIZE | 10MB | cm-common | 공통 |
|
||||||
|
| LOG_FILE_MAX_HISTORY | 7 | cm-common | 공통 |
|
||||||
|
| LOG_FILE_TOTAL_CAP | 100MB | cm-common | 공통 |
|
||||||
|
| AI_PROVIDER | CLAUDE | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_API_URL | https://api.anthropic.com/v1/messages | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_API_KEY | sk-ant-api03-... | secret-ai-service | |
|
||||||
|
| AI_CLAUDE_ANTHROPIC_VERSION | 2023-06-01 | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_MODEL | claude-sonnet-4-5-20250929 | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_MAX_TOKENS | 4096 | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_TEMPERATURE | 0.7 | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_TIMEOUT | 300000 | cm-ai-service | |
|
||||||
|
| CACHE_TTL_RECOMMENDATION | 86400 | cm-ai-service | |
|
||||||
|
| CACHE_TTL_JOB_STATUS | 86400 | cm-ai-service | |
|
||||||
|
| CACHE_TTL_TREND | 3600 | cm-ai-service | |
|
||||||
|
| CACHE_TTL_FALLBACK | 604800 | cm-ai-service | |
|
||||||
|
|
||||||
|
## 4. content-service 환경변수 매핑
|
||||||
|
|
||||||
|
| 환경변수 | 값 | 지정 객체 | 비고 |
|
||||||
|
|---------|-----|----------|------|
|
||||||
|
| SERVER_PORT | 8084 | cm-content-service | application.yml 기본값 |
|
||||||
|
| REDIS_ENABLED | true | cm-common | 공통 |
|
||||||
|
| REDIS_HOST | redis | cm-common | ClusterIP 서비스명, 공통 |
|
||||||
|
| REDIS_PORT | 6379 | cm-common | 공통 |
|
||||||
|
| REDIS_PASSWORD | Hi5Jessica! | secret-common | 공통 |
|
||||||
|
| REDIS_TIMEOUT | 2000ms | cm-common | 공통 |
|
||||||
|
| REDIS_POOL_MAX | 8 | cm-common | 공통 |
|
||||||
|
| REDIS_POOL_IDLE | 8 | cm-common | 공통 |
|
||||||
|
| REDIS_POOL_MIN | 0 | cm-common | 공통 |
|
||||||
|
| REDIS_POOL_WAIT | -1ms | cm-common | 공통 |
|
||||||
|
| REDIS_DATABASE | 1 | cm-content-service | |
|
||||||
|
| JWT_SECRET | ShiBpV6q7NwnjOafujT87XcgxzTdEFmEKO5Y+8zNPvE= | secret-common | 공통 |
|
||||||
|
| JWT_ACCESS_TOKEN_VALIDITY | 604800000 | cm-common | 공통 (실제 3600000) |
|
||||||
|
| JWT_REFRESH_TOKEN_VALIDITY | 86400 | cm-common | 공통 (실제 604800000) |
|
||||||
|
| AZURE_STORAGE_CONNECTION_STRING | DefaultEndpointsProtocol=https;AccountName=... | secret-content-service | |
|
||||||
|
| AZURE_CONTAINER_NAME | content-images | cm-content-service | |
|
||||||
|
| REPLICATE_API_URL | https://api.replicate.com | cm-content-service | |
|
||||||
|
| REPLICATE_API_TOKEN | (값 없음) | secret-content-service | |
|
||||||
|
| REPLICATE_MODEL_VERSION | stability-ai/sdxl:... | cm-content-service | |
|
||||||
|
| CORS_ALLOWED_ORIGINS | http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084,http://kt-event-marketing.20.214.196.128.nip.io | cm-common | 공통 |
|
||||||
|
| LOG_LEVEL_APP | INFO | cm-common | 공통 (실제 DEBUG) |
|
||||||
|
| LOG_LEVEL_WEB | INFO | cm-common | 공통 |
|
||||||
|
| LOG_LEVEL_ROOT | INFO | cm-common | 공통 |
|
||||||
|
| LOG_FILE_PATH | logs/content-service.log | cm-content-service | |
|
||||||
|
| LOG_FILE_MAX_SIZE | 10MB | cm-common | 공통 |
|
||||||
|
| LOG_FILE_MAX_HISTORY | 7 | cm-common | 공통 |
|
||||||
|
| LOG_FILE_TOTAL_CAP | 100MB | cm-common | 공통 |
|
||||||
|
|
||||||
|
## 5. participation-service 환경변수 매핑
|
||||||
|
|
||||||
|
| 환경변수 | 값 | 지정 객체 | 비고 |
|
||||||
|
|---------|-----|----------|------|
|
||||||
|
| SERVER_PORT | 8084 | cm-participation-service | |
|
||||||
|
| DB_HOST | participation-postgresql | cm-participation-service | ClusterIP 서비스명 |
|
||||||
|
| DB_PORT | 5432 | cm-participation-service | |
|
||||||
|
| DB_NAME | participationdb | cm-participation-service | |
|
||||||
|
| DB_USERNAME | eventuser | cm-participation-service | |
|
||||||
|
| DB_PASSWORD | Hi5Jessica! | secret-participation-service | |
|
||||||
|
| DDL_AUTO | update | cm-common | 공통 |
|
||||||
|
| SHOW_SQL | false | cm-participation-service | |
|
||||||
|
| REDIS_HOST | redis | cm-common | ClusterIP 서비스명, 공통 |
|
||||||
|
| REDIS_PORT | 6379 | cm-common | 공통 |
|
||||||
|
| REDIS_PASSWORD | Hi5Jessica! | secret-common | 공통 |
|
||||||
|
| REDIS_DATABASE | 4 | cm-participation-service | |
|
||||||
|
| KAFKA_BOOTSTRAP_SERVERS | 20.249.182.13:9095,4.217.131.59:9095 | cm-common | 공통 |
|
||||||
|
| KAFKA_CONSUMER_GROUP | participation-service-consumers | cm-participation-service | |
|
||||||
|
| JWT_SECRET | ShiBpV6q7NwnjOafujT87XcgxzTdEFmEKO5Y+8zNPvE= | secret-common | 공통 |
|
||||||
|
| JWT_EXPIRATION | 86400000 | JWT_ACCESS_TOKEN_VALIDITY 대체 | |
|
||||||
|
| LOG_LEVEL | INFO | cm-participation-service | |
|
||||||
|
| LOG_FILE | logs/participation-service.log | cm-participation-service | |
|
||||||
|
|
||||||
|
## 6. analytics-service 환경변수 매핑
|
||||||
|
|
||||||
|
| 환경변수 | 값 | 지정 객체 | 비고 |
|
||||||
|
|---------|-----|----------|------|
|
||||||
|
| SERVER_PORT | 8086 | cm-analytics-service | |
|
||||||
|
| DB_KIND | postgresql | cm-common | 공통 |
|
||||||
|
| DB_HOST | analytic-postgresql | cm-analytics-service | ClusterIP 서비스명 |
|
||||||
|
| DB_PORT | 5432 | cm-analytics-service | |
|
||||||
|
| DB_NAME | analytics_db | cm-analytics-service | 실행프로파일은 analyticdb |
|
||||||
|
| DB_USERNAME | analytics_user | cm-analytics-service | 실행프로파일은 eventuser |
|
||||||
|
| DB_PASSWORD | Hi5Jessica! | secret-analytics-service | |
|
||||||
|
| DDL_AUTO | update | cm-analytics-service | 실행프로파일은 create |
|
||||||
|
| SHOW_SQL | false | cm-analytics-service | 실행프로파일은 true |
|
||||||
|
| REDIS_HOST | redis | cm-common | ClusterIP 서비스명, 공통 |
|
||||||
|
| REDIS_PORT | 6379 | cm-common | 공통 |
|
||||||
|
| REDIS_PASSWORD | Hi5Jessica! | secret-common | 공통 |
|
||||||
|
| REDIS_DATABASE | 5 | cm-analytics-service | |
|
||||||
|
| KAFKA_ENABLED | true | cm-analytics-service | |
|
||||||
|
| KAFKA_BOOTSTRAP_SERVERS | 20.249.182.13:9095,4.217.131.59:9095 | cm-common | 공통 |
|
||||||
|
| KAFKA_CONSUMER_GROUP_ID | analytics-service | cm-analytics-service | 실행프로파일은 analytics-service-consumers |
|
||||||
|
| SAMPLE_DATA_ENABLED | true | cm-analytics-service | |
|
||||||
|
| JWT_SECRET | ShiBpV6q7NwnjOafujT87XcgxzTdEFmEKO5Y+8zNPvE= | secret-common | 공통 |
|
||||||
|
| JWT_ACCESS_TOKEN_VALIDITY | 604800000 | cm-common | 실행프로파일은 1800 |
|
||||||
|
| JWT_REFRESH_TOKEN_VALIDITY | 86400 | cm-common | 실행프로파일은 86400 |
|
||||||
|
| CORS_ALLOWED_ORIGINS | http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084,http://kt-event-marketing.20.214.196.128.nip.io | cm-common | 공통 |
|
||||||
|
| LOG_FILE | logs/analytics-service.log | cm-analytics-service | |
|
||||||
|
| LOG_LEVEL_APP | INFO | cm-common | 실행프로파일은 DEBUG |
|
||||||
|
| LOG_LEVEL_WEB | INFO | cm-common | 공통 |
|
||||||
|
| LOG_LEVEL_SQL | WARN | cm-common | 실행프로파일은 DEBUG |
|
||||||
|
| LOG_LEVEL_SQL_TYPE | WARN | cm-common | 실행프로파일은 TRACE |
|
||||||
|
|
||||||
|
## 주요 체크 사항
|
||||||
|
|
||||||
|
### 1. ClusterIP 서비스명 사용 확인
|
||||||
|
- ✅ Redis HOST: `redis` (ClusterIP 서비스명)
|
||||||
|
- ✅ user-service DB HOST: `user-postgresql`
|
||||||
|
- ✅ event-service DB HOST: `event-postgresql`
|
||||||
|
- ✅ participation-service DB HOST: `participation-postgresql`
|
||||||
|
- ✅ analytics-service DB HOST: `analytic-postgresql`
|
||||||
|
|
||||||
|
### 2. Secret에 stringData 사용 확인
|
||||||
|
- ✅ secret-common.yaml: stringData 사용
|
||||||
|
- ✅ secret-user-service.yaml: stringData 사용
|
||||||
|
- ✅ secret-event-service.yaml: stringData 사용
|
||||||
|
- ✅ secret-ai-service.yaml: stringData 사용
|
||||||
|
- ✅ secret-content-service.yaml: stringData 사용
|
||||||
|
- ✅ secret-participation-service.yaml: stringData 사용
|
||||||
|
- ✅ secret-analytics-service.yaml: stringData 사용
|
||||||
|
|
||||||
|
### 3. JWT_SECRET openssl 생성 확인
|
||||||
|
- ✅ openssl rand -base64 32로 생성: `ShiBpV6q7NwnjOafujT87XcgxzTdEFmEKO5Y+8zNPvE=`
|
||||||
|
|
||||||
|
### 4. 매니페스트 내 환경변수 미사용 확인
|
||||||
|
- ✅ Ingress host: kt-event-marketing-api.20.214.196.128.nip.io (실제 값 사용)
|
||||||
|
- ✅ 모든 매니페스트에 실제 값만 지정
|
||||||
|
|
||||||
|
### 5. Image Pull Secret 설정 확인
|
||||||
|
- ✅ secret-imagepull.yaml: USERNAME과 PASSWORD 실제 값 지정
|
||||||
|
- ✅ Deployment에 imagePullSecrets 설정
|
||||||
|
|
||||||
|
### 6. Image명 형식 확인
|
||||||
|
- ✅ 형식: acrdigitalgarage01.azurecr.io/kt-event-marketing/{서비스명}:latest
|
||||||
|
|
||||||
|
### 7. Ingress 설정 확인
|
||||||
|
- ✅ Ingress External IP: 20.214.196.128
|
||||||
|
- ✅ Service port: 80
|
||||||
|
- ✅ Ingress annotation에 rewrite-target 설정 없음
|
||||||
|
|
||||||
|
### 8. envFrom 사용 확인
|
||||||
|
- ✅ 모든 Deployment에서 env 대신 envFrom 사용
|
||||||
|
|
||||||
|
### 9. REDIS_DATABASE 서비스별 분리 확인
|
||||||
|
- ✅ user-service: 0
|
||||||
|
- ✅ content-service: 1
|
||||||
|
- ✅ event-service: 2
|
||||||
|
- ✅ ai-service: 3
|
||||||
|
- ✅ participation-service: 4
|
||||||
|
- ✅ analytics-service: 5
|
||||||
|
|
||||||
|
### 10. 보안 환경변수 Secret 분리 확인
|
||||||
|
- ✅ JWT_SECRET: secret-common
|
||||||
|
- ✅ REDIS_PASSWORD: secret-common
|
||||||
|
- ✅ DB_PASSWORD: 각 서비스별 secret
|
||||||
|
- ✅ AI_CLAUDE_API_KEY: secret-ai-service
|
||||||
|
- ✅ AZURE_STORAGE_CONNECTION_STRING: secret-content-service
|
||||||
56
deployment/k8s/ai-service/cm-ai-service.yaml
Normal file
56
deployment/k8s/ai-service/cm-ai-service.yaml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm-ai-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
data:
|
||||||
|
# Server Configuration
|
||||||
|
SERVER_PORT: "8083"
|
||||||
|
|
||||||
|
# Redis Configuration (service-specific)
|
||||||
|
REDIS_DATABASE: "3"
|
||||||
|
REDIS_TIMEOUT: "3000"
|
||||||
|
REDIS_POOL_MIN: "2"
|
||||||
|
|
||||||
|
# Kafka Configuration (service-specific)
|
||||||
|
KAFKA_CONSUMER_GROUP: "ai-service-consumers"
|
||||||
|
|
||||||
|
# Kafka Topics Configuration
|
||||||
|
KAFKA_TOPICS_AI_JOB: "ai-event-generation-job"
|
||||||
|
KAFKA_TOPICS_AI_JOB_DLQ: "ai-event-generation-job-dlq"
|
||||||
|
|
||||||
|
# AI Provider Configuration
|
||||||
|
AI_PROVIDER: "CLAUDE"
|
||||||
|
AI_CLAUDE_API_URL: "https://api.anthropic.com/v1/messages"
|
||||||
|
AI_CLAUDE_ANTHROPIC_VERSION: "2023-06-01"
|
||||||
|
AI_CLAUDE_MODEL: "claude-sonnet-4-5-20250929"
|
||||||
|
AI_CLAUDE_MAX_TOKENS: "4096"
|
||||||
|
AI_CLAUDE_TEMPERATURE: "0.7"
|
||||||
|
AI_CLAUDE_TIMEOUT: "300000"
|
||||||
|
|
||||||
|
# Circuit Breaker Configuration
|
||||||
|
RESILIENCE4J_CIRCUITBREAKER_FAILURE_RATE_THRESHOLD: "50"
|
||||||
|
RESILIENCE4J_CIRCUITBREAKER_SLOW_CALL_RATE_THRESHOLD: "50"
|
||||||
|
RESILIENCE4J_CIRCUITBREAKER_SLOW_CALL_DURATION_THRESHOLD: "60s"
|
||||||
|
RESILIENCE4J_CIRCUITBREAKER_PERMITTED_CALLS_HALF_OPEN: "3"
|
||||||
|
RESILIENCE4J_CIRCUITBREAKER_SLIDING_WINDOW_SIZE: "10"
|
||||||
|
RESILIENCE4J_CIRCUITBREAKER_MINIMUM_CALLS: "5"
|
||||||
|
RESILIENCE4J_CIRCUITBREAKER_WAIT_DURATION_OPEN: "60s"
|
||||||
|
RESILIENCE4J_TIMELIMITER_TIMEOUT_DURATION: "300s"
|
||||||
|
|
||||||
|
# Redis Cache TTL Configuration (seconds)
|
||||||
|
CACHE_TTL_RECOMMENDATION: "86400"
|
||||||
|
CACHE_TTL_JOB_STATUS: "86400"
|
||||||
|
CACHE_TTL_TREND: "3600"
|
||||||
|
CACHE_TTL_FALLBACK: "604800"
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
LOG_LEVEL_ROOT: "INFO"
|
||||||
|
LOG_LEVEL_AI: "DEBUG"
|
||||||
|
LOG_LEVEL_KAFKA: "INFO"
|
||||||
|
LOG_LEVEL_REDIS: "INFO"
|
||||||
|
LOG_LEVEL_RESILIENCE4J: "DEBUG"
|
||||||
|
LOG_FILE_NAME: "logs/ai-service.log"
|
||||||
|
LOG_FILE_MAX_SIZE: "10MB"
|
||||||
|
LOG_FILE_MAX_HISTORY: "7"
|
||||||
|
LOG_FILE_TOTAL_CAP: "100MB"
|
||||||
63
deployment/k8s/ai-service/deployment.yaml
Normal file
63
deployment/k8s/ai-service/deployment.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: ai-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: ai-service
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: ai-service
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: ai-service
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: kt-event-marketing
|
||||||
|
containers:
|
||||||
|
- name: ai-service
|
||||||
|
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8083
|
||||||
|
name: http
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-common
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-ai-service
|
||||||
|
- secretRef:
|
||||||
|
name: secret-common
|
||||||
|
- secretRef:
|
||||||
|
name: secret-ai-service
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "256m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "1024m"
|
||||||
|
memory: "1024Mi"
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8083
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8083
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/liveness
|
||||||
|
port: 8083
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
9
deployment/k8s/ai-service/secret-ai-service.yaml
Normal file
9
deployment/k8s/ai-service/secret-ai-service.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secret-ai-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
# Claude API Key
|
||||||
|
AI_CLAUDE_API_KEY: "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"
|
||||||
16
deployment/k8s/ai-service/service.yaml
Normal file
16
deployment/k8s/ai-service/service.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: ai-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: ai-service
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8083
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: ai-service
|
||||||
38
deployment/k8s/analytics-service/cm-analytics-service.yaml
Normal file
38
deployment/k8s/analytics-service/cm-analytics-service.yaml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm-analytics-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
data:
|
||||||
|
# Server Configuration
|
||||||
|
SERVER_PORT: "8086"
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
DB_HOST: "analytic-postgresql"
|
||||||
|
DB_PORT: "5432"
|
||||||
|
DB_NAME: "analytics_db"
|
||||||
|
DB_USERNAME: "eventuser"
|
||||||
|
|
||||||
|
# Redis Configuration (service-specific)
|
||||||
|
REDIS_DATABASE: "5"
|
||||||
|
|
||||||
|
# Kafka Configuration (service-specific)
|
||||||
|
KAFKA_ENABLED: "true"
|
||||||
|
KAFKA_CONSUMER_GROUP_ID: "analytics-service"
|
||||||
|
|
||||||
|
# Sample Data Configuration (MVP only)
|
||||||
|
SAMPLE_DATA_ENABLED: "true"
|
||||||
|
|
||||||
|
# Batch Scheduler Configuration
|
||||||
|
BATCH_REFRESH_INTERVAL: "300000" # 5분 (밀리초)
|
||||||
|
BATCH_INITIAL_DELAY: "30000" # 30초 (밀리초)
|
||||||
|
BATCH_ENABLED: "true"
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
LOG_LEVEL_APP: "INFO"
|
||||||
|
LOG_LEVEL_WEB: "INFO"
|
||||||
|
LOG_LEVEL_SQL: "WARN"
|
||||||
|
LOG_LEVEL_SQL_TYPE: "WARN"
|
||||||
|
SHOW_SQL: "false"
|
||||||
|
DDL_AUTO: "update"
|
||||||
|
LOG_FILE: "logs/analytics-service.log"
|
||||||
63
deployment/k8s/analytics-service/deployment.yaml
Normal file
63
deployment/k8s/analytics-service/deployment.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: analytics-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: analytics-service
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: analytics-service
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: analytics-service
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: kt-event-marketing
|
||||||
|
containers:
|
||||||
|
- name: analytics-service
|
||||||
|
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8086
|
||||||
|
name: http
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-common
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-analytics-service
|
||||||
|
- secretRef:
|
||||||
|
name: secret-common
|
||||||
|
- secretRef:
|
||||||
|
name: secret-analytics-service
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "256m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "1024m"
|
||||||
|
memory: "1024Mi"
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/liveness
|
||||||
|
port: 8086
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/liveness
|
||||||
|
port: 8086
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8086
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secret-analytics-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
DB_PASSWORD: "Hi5Jessica!"
|
||||||
16
deployment/k8s/analytics-service/service.yaml
Normal file
16
deployment/k8s/analytics-service/service.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: analytics-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: analytics-service
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8086
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: analytics-service
|
||||||
47
deployment/k8s/common/cm-common.yaml
Normal file
47
deployment/k8s/common/cm-common.yaml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm-common
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
data:
|
||||||
|
# Redis Configuration
|
||||||
|
REDIS_ENABLED: "true"
|
||||||
|
REDIS_HOST: "redis"
|
||||||
|
REDIS_PORT: "6379"
|
||||||
|
REDIS_TIMEOUT: "2000ms"
|
||||||
|
REDIS_POOL_MAX: "8"
|
||||||
|
REDIS_POOL_IDLE: "8"
|
||||||
|
REDIS_POOL_MIN: "0"
|
||||||
|
REDIS_POOL_WAIT: "-1ms"
|
||||||
|
|
||||||
|
# Kafka Configuration
|
||||||
|
KAFKA_BOOTSTRAP_SERVERS: "20.249.182.13:9095,4.217.131.59:9095"
|
||||||
|
EXCLUDE_KAFKA: ""
|
||||||
|
EXCLUDE_REDIS: ""
|
||||||
|
|
||||||
|
# CORS Configuration
|
||||||
|
CORS_ALLOWED_ORIGINS: "http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084,http://kt-event-marketing.20.214.196.128.nip.io"
|
||||||
|
CORS_ALLOWED_METHODS: "GET,POST,PUT,DELETE,OPTIONS,PATCH"
|
||||||
|
CORS_ALLOWED_HEADERS: "*"
|
||||||
|
CORS_ALLOW_CREDENTIALS: "true"
|
||||||
|
CORS_MAX_AGE: "3600"
|
||||||
|
|
||||||
|
# JWT Configuration
|
||||||
|
JWT_ACCESS_TOKEN_VALIDITY: "604800000"
|
||||||
|
JWT_REFRESH_TOKEN_VALIDITY: "86400000"
|
||||||
|
|
||||||
|
# JPA Configuration
|
||||||
|
DDL_AUTO: "update"
|
||||||
|
SHOW_SQL: "false"
|
||||||
|
JPA_DIALECT: "org.hibernate.dialect.PostgreSQLDialect"
|
||||||
|
H2_CONSOLE_ENABLED: "false"
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
LOG_LEVEL_APP: "INFO"
|
||||||
|
LOG_LEVEL_WEB: "INFO"
|
||||||
|
LOG_LEVEL_SQL: "WARN"
|
||||||
|
LOG_LEVEL_SQL_TYPE: "WARN"
|
||||||
|
LOG_LEVEL_ROOT: "INFO"
|
||||||
|
LOG_FILE_MAX_SIZE: "10MB"
|
||||||
|
LOG_FILE_MAX_HISTORY: "7"
|
||||||
|
LOG_FILE_TOTAL_CAP: "100MB"
|
||||||
117
deployment/k8s/common/ingress.yaml
Normal file
117
deployment/k8s/common/ingress.yaml
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: kt-event-marketing
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||||
|
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: kt-event-marketing-api.20.214.196.128.nip.io
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
# User Service
|
||||||
|
- path: /api/v1/users
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: user-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
# Content Service
|
||||||
|
- path: /api/v1/content
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: content-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
# Event Service
|
||||||
|
- path: /api/v1/events
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: event-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
- path: /api/v1/jobs
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: event-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
- path: /api/v1/redis-test
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: event-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
# AI Service
|
||||||
|
- path: /api/v1/ai-service
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: ai-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
# Participation Service
|
||||||
|
- path: /api/v1/participations
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: participation-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
- path: /api/v1/winners
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: participation-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
- path: /debug
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: participation-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
# Analytics Service - Event Analytics
|
||||||
|
- path: /api/v1/events/([0-9]+)/analytics
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: analytics-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
# Analytics Service - User Analytics
|
||||||
|
- path: /api/v1/users/([0-9]+)/analytics
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: analytics-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
# Distribution Service
|
||||||
|
- path: /distribution
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: distribution-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
12
deployment/k8s/common/secret-common.yaml
Normal file
12
deployment/k8s/common/secret-common.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secret-common
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
# Redis Password
|
||||||
|
REDIS_PASSWORD: "Hi5Jessica!"
|
||||||
|
|
||||||
|
# JWT Secret
|
||||||
|
JWT_SECRET: "QL0czzXckz18kHnxpaTDoWFkq+3qKO7VQXeNvf2bOoU="
|
||||||
17
deployment/k8s/common/secret-imagepull.yaml
Normal file
17
deployment/k8s/common/secret-imagepull.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: kt-event-marketing
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
type: kubernetes.io/dockerconfigjson
|
||||||
|
stringData:
|
||||||
|
.dockerconfigjson: |
|
||||||
|
{
|
||||||
|
"auths": {
|
||||||
|
"acrdigitalgarage01.azurecr.io": {
|
||||||
|
"username": "acrdigitalgarage01",
|
||||||
|
"password": "+OY+rmOagorjWvQe/tTk6oqvnZI8SmNbY/Y2o5EDcY+ACRDCDbYk",
|
||||||
|
"auth": "YWNyZGlnaXRhbGdhcmFnZTAxOitPWStybU9hZ29yald2UWUvdFRrNm9xdm5aSThTbU5iWS9ZMm81RURjWStBQ1JEQ0RiWWs="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
deployment/k8s/content-service/cm-content-service.yaml
Normal file
25
deployment/k8s/content-service/cm-content-service.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm-content-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
data:
|
||||||
|
# Server Configuration
|
||||||
|
SERVER_PORT: "8084"
|
||||||
|
|
||||||
|
# Redis Configuration (service-specific)
|
||||||
|
REDIS_DATABASE: "1"
|
||||||
|
|
||||||
|
# Replicate API Configuration (Stable Diffusion)
|
||||||
|
REPLICATE_API_URL: "https://api.replicate.com"
|
||||||
|
REPLICATE_MODEL_VERSION: "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b"
|
||||||
|
|
||||||
|
# HuggingFace API Configuration
|
||||||
|
HUGGINGFACE_API_URL: "https://api-inference.huggingface.co"
|
||||||
|
HUGGINGFACE_MODEL: "runwayml/stable-diffusion-v1-5"
|
||||||
|
|
||||||
|
# Azure Blob Storage Configuration
|
||||||
|
AZURE_CONTAINER_NAME: "content-images"
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
LOG_FILE_PATH: "logs/content-service.log"
|
||||||
63
deployment/k8s/content-service/deployment.yaml
Normal file
63
deployment/k8s/content-service/deployment.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: content-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: content-service
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: content-service
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: content-service
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: kt-event-marketing
|
||||||
|
containers:
|
||||||
|
- name: content-service
|
||||||
|
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8084
|
||||||
|
name: http
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-common
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-content-service
|
||||||
|
- secretRef:
|
||||||
|
name: secret-common
|
||||||
|
- secretRef:
|
||||||
|
name: secret-content-service
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "256m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "1024m"
|
||||||
|
memory: "1024Mi"
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/v1/content/actuator/health
|
||||||
|
port: 8084
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/v1/content/actuator/health/readiness
|
||||||
|
port: 8084
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/v1/content/actuator/health/liveness
|
||||||
|
port: 8084
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
15
deployment/k8s/content-service/secret-content-service.yaml
Normal file
15
deployment/k8s/content-service/secret-content-service.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secret-content-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
# Azure Blob Storage Connection String
|
||||||
|
AZURE_STORAGE_CONNECTION_STRING: "DefaultEndpointsProtocol=https;AccountName=blobkteventstorage;AccountKey=tcBN7mAfojbl0uGsOpU7RNuKNhHnzmwDiWjN31liSMVSrWaEK+HHnYKZrjBXXAC6ZPsuxUDlsf8x+AStd++QYg==;EndpointSuffix=core.windows.net"
|
||||||
|
|
||||||
|
# Replicate API Token
|
||||||
|
REPLICATE_API_TOKEN: ""
|
||||||
|
|
||||||
|
# HuggingFace API Token
|
||||||
|
HUGGINGFACE_API_TOKEN: ""
|
||||||
16
deployment/k8s/content-service/service.yaml
Normal file
16
deployment/k8s/content-service/service.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: content-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: content-service
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8084
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: content-service
|
||||||
738
deployment/k8s/deploy-k8s-guide.md
Normal file
738
deployment/k8s/deploy-k8s-guide.md
Normal file
@ -0,0 +1,738 @@
|
|||||||
|
# Kubernetes 배포 가이드
|
||||||
|
|
||||||
|
## 1. 시스템 개요
|
||||||
|
|
||||||
|
- **시스템명**: kt-event-marketing
|
||||||
|
- **네임스페이스**: kt-event-marketing
|
||||||
|
- **ACR**: acrdigitalgarage01
|
||||||
|
- **AKS 클러스터**: aks-digitalgarage-01
|
||||||
|
- **Ingress External IP**: 20.214.196.128
|
||||||
|
- **파드 수**: 1 (각 서비스)
|
||||||
|
- **리소스 할당**: CPU 256m/1024m, Memory 256Mi/1024Mi
|
||||||
|
|
||||||
|
## 2. 서비스 구성
|
||||||
|
|
||||||
|
| 서비스 | 포트 | 데이터베이스 | Redis DB | API Path |
|
||||||
|
|--------|------|------------|----------|----------|
|
||||||
|
| user-service | 8081 | user-postgresql | 0 | /api/v1/users |
|
||||||
|
| content-service | 8084 | N/A | 1 | /api/v1/content |
|
||||||
|
| event-service | 8080 | event-postgresql | 2 | /api/v1/events, /api/v1/jobs |
|
||||||
|
| ai-service | 8083 | N/A | 3 | /api/v1/ai-service |
|
||||||
|
| participation-service | 8084 | participation-postgresql | 4 | /api/v1/participations, /api/v1/winners |
|
||||||
|
| analytics-service | 8086 | analytic-postgresql | 5 | /api/v1/events/.../analytics, /api/v1/users/.../analytics |
|
||||||
|
|
||||||
|
## 3. 사전 확인
|
||||||
|
|
||||||
|
### 3.1 Azure 로그인 확인
|
||||||
|
```bash
|
||||||
|
az account show
|
||||||
|
```
|
||||||
|
|
||||||
|
**확인 사항**: 올바른 Azure 구독에 로그인되어 있는지 확인
|
||||||
|
|
||||||
|
### 3.2 AKS Credential 확인
|
||||||
|
```bash
|
||||||
|
kubectl cluster-info
|
||||||
|
```
|
||||||
|
|
||||||
|
**확인 사항**: Kubernetes 클러스터에 정상적으로 연결되어 있는지 확인
|
||||||
|
|
||||||
|
### 3.3 Namespace 존재 확인
|
||||||
|
```bash
|
||||||
|
kubectl get ns kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
**확인 사항**: kt-event-marketing 네임스페이스가 존재하는지 확인. 없으면 생성:
|
||||||
|
```bash
|
||||||
|
kubectl create namespace kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 필수 서비스 확인
|
||||||
|
|
||||||
|
#### Redis 서비스 확인
|
||||||
|
```bash
|
||||||
|
kubectl get svc | grep redis
|
||||||
|
```
|
||||||
|
|
||||||
|
**확인 결과**:
|
||||||
|
```
|
||||||
|
redis ClusterIP 10.0.69.101 <none> 6379/TCP,26379/TCP
|
||||||
|
redis-external LoadBalancer 10.0.68.243 20.214.210.71 6379:30397/TCP,26379:32563/TCP
|
||||||
|
redis-headless ClusterIP None <none> 6379/TCP,26379/TCP
|
||||||
|
```
|
||||||
|
|
||||||
|
**사용할 서비스**: `redis` (ClusterIP)
|
||||||
|
|
||||||
|
#### 데이터베이스 서비스 확인
|
||||||
|
```bash
|
||||||
|
kubectl get svc | grep postgresql
|
||||||
|
```
|
||||||
|
|
||||||
|
**확인 결과**:
|
||||||
|
```
|
||||||
|
user-postgresql ClusterIP 10.0.189.87 <none> 5432/TCP
|
||||||
|
user-postgresql-external LoadBalancer 10.0.186.11 20.249.125.115 5432:30414/TCP
|
||||||
|
event-postgresql ClusterIP 10.0.245.96 <none> 5432/TCP
|
||||||
|
event-postgresql-external LoadBalancer 10.0.200.134 20.249.177.232 5432:31222/TCP
|
||||||
|
```
|
||||||
|
|
||||||
|
**사용할 서비스**:
|
||||||
|
- user-service: `user-postgresql` (ClusterIP)
|
||||||
|
- event-service: `event-postgresql` (ClusterIP)
|
||||||
|
|
||||||
|
#### Ingress Controller 확인
|
||||||
|
```bash
|
||||||
|
kubectl get svc ingress-nginx-controller -n ingress-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
**확인 결과**:
|
||||||
|
```
|
||||||
|
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
|
||||||
|
ingress-nginx-controller LoadBalancer 10.0.76.134 20.214.196.128 80:32094/TCP,443:30210/TCP
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ingress Host**: `kt-event-marketing-api.20.214.196.128.nip.io`
|
||||||
|
|
||||||
|
## 4. 매니페스트 적용
|
||||||
|
|
||||||
|
### 4.1 공통 리소스 배포
|
||||||
|
```bash
|
||||||
|
kubectl apply -f deployment/k8s/common/
|
||||||
|
```
|
||||||
|
|
||||||
|
**생성되는 리소스**:
|
||||||
|
- Secret: `kt-event-marketing` (ImagePullSecret)
|
||||||
|
- ConfigMap: `cm-common`
|
||||||
|
- Secret: `secret-common`
|
||||||
|
- Ingress: `kt-event-marketing`
|
||||||
|
|
||||||
|
### 4.2 user-service 배포
|
||||||
|
```bash
|
||||||
|
kubectl apply -f deployment/k8s/user-service/
|
||||||
|
```
|
||||||
|
|
||||||
|
**생성되는 리소스**:
|
||||||
|
- ConfigMap: `cm-user-service`
|
||||||
|
- Secret: `secret-user-service`
|
||||||
|
- Service: `user-service`
|
||||||
|
- Deployment: `user-service`
|
||||||
|
|
||||||
|
### 4.3 content-service 배포
|
||||||
|
```bash
|
||||||
|
kubectl apply -f deployment/k8s/content-service/
|
||||||
|
```
|
||||||
|
|
||||||
|
**생성되는 리소스**:
|
||||||
|
- ConfigMap: `cm-content-service`
|
||||||
|
- Secret: `secret-content-service`
|
||||||
|
- Service: `content-service`
|
||||||
|
- Deployment: `content-service`
|
||||||
|
|
||||||
|
### 4.4 event-service 배포
|
||||||
|
```bash
|
||||||
|
kubectl apply -f deployment/k8s/event-service/
|
||||||
|
```
|
||||||
|
|
||||||
|
**생성되는 리소스**:
|
||||||
|
- ConfigMap: `cm-event-service`
|
||||||
|
- Secret: `secret-event-service`
|
||||||
|
- Service: `event-service`
|
||||||
|
- Deployment: `event-service`
|
||||||
|
|
||||||
|
### 4.5 ai-service 배포
|
||||||
|
```bash
|
||||||
|
kubectl apply -f deployment/k8s/ai-service/
|
||||||
|
```
|
||||||
|
|
||||||
|
**생성되는 리소스**:
|
||||||
|
- ConfigMap: `cm-ai-service`
|
||||||
|
- Secret: `secret-ai-service`
|
||||||
|
- Service: `ai-service`
|
||||||
|
- Deployment: `ai-service`
|
||||||
|
|
||||||
|
### 4.6 participation-service 배포
|
||||||
|
```bash
|
||||||
|
kubectl apply -f deployment/k8s/participation-service/
|
||||||
|
```
|
||||||
|
|
||||||
|
**생성되는 리소스**:
|
||||||
|
- ConfigMap: `cm-participation-service`
|
||||||
|
- Secret: `secret-participation-service`
|
||||||
|
- Service: `participation-service`
|
||||||
|
- Deployment: `participation-service`
|
||||||
|
|
||||||
|
### 4.7 analytics-service 배포
|
||||||
|
```bash
|
||||||
|
kubectl apply -f deployment/k8s/analytics-service/
|
||||||
|
```
|
||||||
|
|
||||||
|
**생성되는 리소스**:
|
||||||
|
- ConfigMap: `cm-analytics-service`
|
||||||
|
- Secret: `secret-analytics-service`
|
||||||
|
- Service: `analytics-service`
|
||||||
|
- Deployment: `analytics-service`
|
||||||
|
|
||||||
|
### 4.8 전체 한번에 배포 (권장)
|
||||||
|
```bash
|
||||||
|
# 공통 리소스 먼저 배포
|
||||||
|
kubectl apply -f deployment/k8s/common/
|
||||||
|
|
||||||
|
# 모든 서비스 배포
|
||||||
|
kubectl apply -f deployment/k8s/user-service/
|
||||||
|
kubectl apply -f deployment/k8s/content-service/
|
||||||
|
kubectl apply -f deployment/k8s/event-service/
|
||||||
|
kubectl apply -f deployment/k8s/ai-service/
|
||||||
|
kubectl apply -f deployment/k8s/participation-service/
|
||||||
|
kubectl apply -f deployment/k8s/analytics-service/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 배포 확인
|
||||||
|
|
||||||
|
### 5.1 ConfigMap 및 Secret 확인
|
||||||
|
```bash
|
||||||
|
kubectl get configmap -n kt-event-marketing
|
||||||
|
kubectl get secret -n kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
**예상 출력**:
|
||||||
|
```
|
||||||
|
NAME DATA AGE
|
||||||
|
cm-common 41 1m
|
||||||
|
cm-user-service 18 1m
|
||||||
|
cm-content-service 10 1m
|
||||||
|
cm-event-service 11 1m
|
||||||
|
cm-ai-service 39 1m
|
||||||
|
|
||||||
|
NAME TYPE DATA AGE
|
||||||
|
kt-event-marketing kubernetes.io/dockerconfigjson 1 1m
|
||||||
|
secret-common Opaque 2 1m
|
||||||
|
secret-user-service Opaque 2 1m
|
||||||
|
secret-content-service Opaque 3 1m
|
||||||
|
secret-event-service Opaque 1 1m
|
||||||
|
secret-ai-service Opaque 1 1m
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Service 확인
|
||||||
|
```bash
|
||||||
|
kubectl get svc -n kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
**예상 출력**:
|
||||||
|
```
|
||||||
|
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||||
|
user-service ClusterIP 10.0.x.x <none> 80/TCP 1m
|
||||||
|
content-service ClusterIP 10.0.x.x <none> 80/TCP 1m
|
||||||
|
event-service ClusterIP 10.0.x.x <none> 80/TCP 1m
|
||||||
|
ai-service ClusterIP 10.0.x.x <none> 80/TCP 1m
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Deployment 확인
|
||||||
|
```bash
|
||||||
|
kubectl get deployment -n kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
**예상 출력**:
|
||||||
|
```
|
||||||
|
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||||
|
user-service 1/1 1 1 1m
|
||||||
|
content-service 1/1 1 1 1m
|
||||||
|
event-service 1/1 1 1 1m
|
||||||
|
ai-service 1/1 1 1 1m
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Pod 확인
|
||||||
|
```bash
|
||||||
|
kubectl get pods -n kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
**예상 출력**:
|
||||||
|
```
|
||||||
|
NAME READY STATUS RESTARTS AGE
|
||||||
|
user-service-xxxxxxxxxx-xxxxx 1/1 Running 0 1m
|
||||||
|
content-service-xxxxxxxxxx-xxxxx 1/1 Running 0 1m
|
||||||
|
event-service-xxxxxxxxxx-xxxxx 1/1 Running 0 1m
|
||||||
|
ai-service-xxxxxxxxxx-xxxxx 1/1 Running 0 1m
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.5 Ingress 확인
|
||||||
|
```bash
|
||||||
|
kubectl get ingress -n kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
**예상 출력**:
|
||||||
|
```
|
||||||
|
NAME CLASS HOSTS ADDRESS PORTS AGE
|
||||||
|
kt-event-marketing nginx kt-event-marketing-api.20.214.196.128.nip.io 20.214.196.128 80 1m
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.6 Pod 로그 확인
|
||||||
|
```bash
|
||||||
|
# user-service 로그
|
||||||
|
kubectl logs -f deployment/user-service -n kt-event-marketing
|
||||||
|
|
||||||
|
# content-service 로그
|
||||||
|
kubectl logs -f deployment/content-service -n kt-event-marketing
|
||||||
|
|
||||||
|
# event-service 로그
|
||||||
|
kubectl logs -f deployment/event-service -n kt-event-marketing
|
||||||
|
|
||||||
|
# ai-service 로그
|
||||||
|
kubectl logs -f deployment/ai-service -n kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.7 Pod 상세 정보 확인
|
||||||
|
```bash
|
||||||
|
kubectl describe pod -l app=user-service -n kt-event-marketing
|
||||||
|
kubectl describe pod -l app=content-service -n kt-event-marketing
|
||||||
|
kubectl describe pod -l app=event-service -n kt-event-marketing
|
||||||
|
kubectl describe pod -l app=ai-service -n kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. API 테스트
|
||||||
|
|
||||||
|
### 6.1 Health Check
|
||||||
|
```bash
|
||||||
|
# user-service
|
||||||
|
curl http://kt-event-marketing-api.20.214.196.128.nip.io/api/v1/users/profile
|
||||||
|
|
||||||
|
# content-service
|
||||||
|
curl http://kt-event-marketing-api.20.214.196.128.nip.io/api/v1/content/images/jobs/test
|
||||||
|
|
||||||
|
# event-service
|
||||||
|
curl http://kt-event-marketing-api.20.214.196.128.nip.io/api/v1/events
|
||||||
|
|
||||||
|
# ai-service
|
||||||
|
curl http://kt-event-marketing-api.20.214.196.128.nip.io/api/v1/ai-service/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Actuator Health Check
|
||||||
|
```bash
|
||||||
|
# ClusterIP를 통한 내부 접근 테스트
|
||||||
|
kubectl run curl-test --image=curlimages/curl -i --rm --restart=Never -n kt-event-marketing -- \
|
||||||
|
curl http://user-service/actuator/health
|
||||||
|
|
||||||
|
kubectl run curl-test --image=curlimages/curl -i --rm --restart=Never -n kt-event-marketing -- \
|
||||||
|
curl http://content-service/actuator/health
|
||||||
|
|
||||||
|
kubectl run curl-test --image=curlimages/curl -i --rm --restart=Never -n kt-event-marketing -- \
|
||||||
|
curl http://event-service/actuator/health
|
||||||
|
|
||||||
|
kubectl run curl-test --image=curlimages/curl -i --rm --restart=Never -n kt-event-marketing -- \
|
||||||
|
curl http://ai-service/actuator/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 체크리스트 검증 결과
|
||||||
|
|
||||||
|
### 7.1 객체 이름 네이밍 룰 준수 여부
|
||||||
|
✅ **통과**
|
||||||
|
- 공통 ConfigMap: `cm-common`
|
||||||
|
- 공통 Secret: `secret-common`
|
||||||
|
- 서비스별 ConfigMap: `cm-{서비스명}` (예: cm-user-service)
|
||||||
|
- 서비스별 Secret: `secret-{서비스명}` (예: secret-user-service)
|
||||||
|
- Ingress: `kt-event-marketing`
|
||||||
|
- Service: `{서비스명}` (예: user-service)
|
||||||
|
- Deployment: `{서비스명}` (예: user-service)
|
||||||
|
|
||||||
|
### 7.2 Redis Host명을 ClusterIP 타입의 Service 객체로 사용
|
||||||
|
✅ **통과**
|
||||||
|
- **사용 서비스**: `redis` (ClusterIP 타입)
|
||||||
|
- **확인 명령**: `kubectl get svc | grep redis`
|
||||||
|
- **설정 위치**: `deployment/k8s/common/cm-common.yaml`의 `REDIS_HOST: "redis"`
|
||||||
|
|
||||||
|
### 7.3 Database Host명을 ClusterIP 타입의 Service 객체로 사용
|
||||||
|
✅ **통과**
|
||||||
|
|
||||||
|
**user-service**:
|
||||||
|
- **사용 서비스**: `user-postgresql` (ClusterIP)
|
||||||
|
- **확인 명령**: `kubectl get svc | grep user-postgresql`
|
||||||
|
- **설정 위치**: `deployment/k8s/user-service/cm-user-service.yaml`의 `DB_HOST: "user-postgresql"`
|
||||||
|
|
||||||
|
**event-service**:
|
||||||
|
- **사용 서비스**: `event-postgresql` (ClusterIP)
|
||||||
|
- **확인 명령**: `kubectl get svc | grep event-postgresql`
|
||||||
|
- **설정 위치**: `deployment/k8s/event-service/cm-event-service.yaml`의 `DB_HOST: "event-postgresql"`
|
||||||
|
|
||||||
|
### 7.4 Secret 매니페스트에서 'stringData' 사용
|
||||||
|
✅ **통과**
|
||||||
|
- 모든 Secret 매니페스트에서 `stringData` 사용 확인
|
||||||
|
- `data` 필드 대신 `stringData` 사용으로 Base64 인코딩 불필요
|
||||||
|
|
||||||
|
**확인 파일**:
|
||||||
|
- `deployment/k8s/common/secret-common.yaml`
|
||||||
|
- `deployment/k8s/common/secret-imagepull.yaml`
|
||||||
|
- `deployment/k8s/user-service/secret-user-service.yaml`
|
||||||
|
- `deployment/k8s/content-service/secret-content-service.yaml`
|
||||||
|
- `deployment/k8s/event-service/secret-event-service.yaml`
|
||||||
|
- `deployment/k8s/ai-service/secret-ai-service.yaml`
|
||||||
|
|
||||||
|
### 7.5 JWT_SECRET을 openssl 명령으로 생성
|
||||||
|
✅ **통과**
|
||||||
|
- **생성 명령**: `openssl rand -base64 32`
|
||||||
|
- **생성된 값**: `QL0czzXckz18kHnxpaTDoWFkq+3qKO7VQXeNvf2bOoU=`
|
||||||
|
- **설정 위치**: `deployment/k8s/common/secret-common.yaml`의 `JWT_SECRET`
|
||||||
|
|
||||||
|
### 7.6 매니페스트 파일에 환경변수 미사용, 실제 값 지정
|
||||||
|
✅ **통과**
|
||||||
|
- 모든 매니페스트에서 `${변수명}` 형태가 아닌 실제 값 사용
|
||||||
|
- Ingress host: `kt-event-marketing-api.20.214.196.128.nip.io` (실제 IP 사용)
|
||||||
|
|
||||||
|
### 7.7 Image Pull Secret에 실제 USERNAME과 PASSWORD 지정
|
||||||
|
✅ **통과**
|
||||||
|
- **USERNAME**: `acrdigitalgarage01`
|
||||||
|
- **PASSWORD**: `+OY+rmOagorjWvQe/tTk6oqvnZI8SmNbY/Y2o5EDcY+ACRDCDbYk`
|
||||||
|
- **확인 명령**:
|
||||||
|
```bash
|
||||||
|
az acr credential show -n acrdigitalgarage01 --query "username" -o tsv
|
||||||
|
az acr credential show -n acrdigitalgarage01 --query "passwords[0].value" -o tsv
|
||||||
|
```
|
||||||
|
- **설정 위치**: `deployment/k8s/common/secret-imagepull.yaml`
|
||||||
|
|
||||||
|
### 7.8 Image명 형식 확인
|
||||||
|
✅ **통과**
|
||||||
|
- **형식**: `{ACR명}.azurecr.io/{시스템명}/{서비스명}:latest`
|
||||||
|
- **예시**:
|
||||||
|
- `acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service:latest`
|
||||||
|
- `acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service:latest`
|
||||||
|
- `acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service:latest`
|
||||||
|
- `acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service:latest`
|
||||||
|
|
||||||
|
### 7.9 Ingress Controller External IP 확인 및 반영
|
||||||
|
✅ **통과**
|
||||||
|
- **확인 명령**: `kubectl get svc ingress-nginx-controller -n ingress-nginx`
|
||||||
|
- **EXTERNAL-IP**: `20.214.196.128`
|
||||||
|
- **Ingress host**: `kt-event-marketing-api.20.214.196.128.nip.io`
|
||||||
|
- **설정 위치**: `deployment/k8s/common/ingress.yaml`
|
||||||
|
|
||||||
|
### 7.10 Ingress와 Service의 포트 일치 확인
|
||||||
|
✅ **통과**
|
||||||
|
- Ingress의 `backend.service.port.number`: **80**
|
||||||
|
- Service의 `port`: **80**
|
||||||
|
- 모든 서비스에서 일치 확인
|
||||||
|
|
||||||
|
### 7.11 Ingress path가 Controller @RequestMapping과 일치
|
||||||
|
✅ **통과**
|
||||||
|
|
||||||
|
| 서비스 | Controller Path | Ingress Path |
|
||||||
|
|--------|----------------|--------------|
|
||||||
|
| user-service | @RequestMapping("/api/v1/users") | /api/v1/users |
|
||||||
|
| content-service | @RequestMapping("/api/v1/content") | /api/v1/content |
|
||||||
|
| event-service | @RequestMapping("/api/v1/events") | /api/v1/events |
|
||||||
|
| ai-service | /api/v1/ai-service/health | /api/v1/ai-service |
|
||||||
|
|
||||||
|
### 7.12 보안 환경변수를 Secret으로 지정
|
||||||
|
✅ **통과**
|
||||||
|
|
||||||
|
**공통 Secret** (secret-common):
|
||||||
|
- REDIS_PASSWORD
|
||||||
|
- JWT_SECRET
|
||||||
|
|
||||||
|
**서비스별 Secret**:
|
||||||
|
- **user-service**: DB_PASSWORD, DB_URL
|
||||||
|
- **content-service**: AZURE_STORAGE_CONNECTION_STRING, REPLICATE_API_TOKEN, HUGGINGFACE_API_TOKEN
|
||||||
|
- **event-service**: DB_PASSWORD
|
||||||
|
- **ai-service**: AI_CLAUDE_API_KEY
|
||||||
|
|
||||||
|
### 7.13 REDIS_DATABASE가 각 서비스마다 다르게 지정
|
||||||
|
✅ **통과**
|
||||||
|
|
||||||
|
| 서비스 | Redis Database | 설정 위치 |
|
||||||
|
|--------|---------------|-----------|
|
||||||
|
| user-service | 0 | cm-user-service |
|
||||||
|
| content-service | 1 | cm-content-service |
|
||||||
|
| event-service | 2 | cm-event-service |
|
||||||
|
| ai-service | 3 | cm-ai-service |
|
||||||
|
|
||||||
|
### 7.14 ConfigMap과 Secret을 envFrom으로 사용
|
||||||
|
✅ **통과**
|
||||||
|
- 모든 Deployment에서 `env` 대신 `envFrom` 사용
|
||||||
|
- `configMapRef`와 `secretRef`로 ConfigMap과 Secret 참조
|
||||||
|
|
||||||
|
## 8. 실행 프로파일 환경변수 매핑 테이블
|
||||||
|
|
||||||
|
### 8.1 user-service 환경변수 매핑
|
||||||
|
|
||||||
|
| 환경변수명 | 값 | 지정 객체 | 비고 |
|
||||||
|
|-----------|---|----------|------|
|
||||||
|
| SERVER_PORT | 8081 | cm-user-service | |
|
||||||
|
| DB_URL | jdbc:postgresql://user-postgresql:5432/userdb | cm-user-service | |
|
||||||
|
| DB_DRIVER | org.postgresql.Driver | cm-user-service | |
|
||||||
|
| DB_HOST | user-postgresql | cm-user-service | ClusterIP Service |
|
||||||
|
| DB_PORT | 5432 | cm-user-service | |
|
||||||
|
| DB_NAME | userdb | cm-user-service | |
|
||||||
|
| DB_USERNAME | eventuser | cm-user-service | |
|
||||||
|
| DB_PASSWORD | Hi5Jessica! | secret-user-service | Secret |
|
||||||
|
| DB_KIND | postgresql | cm-user-service | |
|
||||||
|
| DDL_AUTO | update | cm-common | |
|
||||||
|
| SHOW_SQL | false | cm-common | |
|
||||||
|
| JPA_DIALECT | org.hibernate.dialect.PostgreSQLDialect | cm-common | |
|
||||||
|
| H2_CONSOLE_ENABLED | false | cm-common | |
|
||||||
|
| REDIS_ENABLED | true | cm-common | |
|
||||||
|
| REDIS_HOST | redis | cm-common | ClusterIP Service |
|
||||||
|
| REDIS_PORT | 6379 | cm-common | |
|
||||||
|
| REDIS_PASSWORD | Hi5Jessica! | secret-common | Secret |
|
||||||
|
| REDIS_DATABASE | 0 | cm-user-service | |
|
||||||
|
| EXCLUDE_REDIS | "" | cm-common | |
|
||||||
|
| KAFKA_BOOTSTRAP_SERVERS | 20.249.182.13:9095,4.217.131.59:9095 | cm-common | |
|
||||||
|
| KAFKA_CONSUMER_GROUP | user-service-consumers | cm-user-service | |
|
||||||
|
| EXCLUDE_KAFKA | "" | cm-common | |
|
||||||
|
| JWT_SECRET | QL0czzXckz18kHnxpaTDoWFkq+3qKO7VQXeNvf2bOoU= | secret-common | Secret (openssl) |
|
||||||
|
| JWT_ACCESS_TOKEN_VALIDITY | 604800000 | cm-common | |
|
||||||
|
| CORS_ALLOWED_ORIGINS | http://localhost:8081,...,http://kt-event-marketing.20.214.196.128.nip.io | cm-common | |
|
||||||
|
| LOG_LEVEL_APP | INFO | cm-common | |
|
||||||
|
| LOG_LEVEL_WEB | INFO | cm-common | |
|
||||||
|
| LOG_LEVEL_SQL | WARN | cm-common | |
|
||||||
|
| LOG_LEVEL_SQL_TYPE | WARN | cm-common | |
|
||||||
|
| LOG_FILE_PATH | logs/user-service.log | cm-user-service | |
|
||||||
|
| DB_POOL_MAX | 20 | cm-user-service | |
|
||||||
|
| DB_POOL_MIN | 5 | cm-user-service | |
|
||||||
|
| DB_CONN_TIMEOUT | 30000 | cm-user-service | |
|
||||||
|
| DB_IDLE_TIMEOUT | 600000 | cm-user-service | |
|
||||||
|
| DB_MAX_LIFETIME | 1800000 | cm-user-service | |
|
||||||
|
| DB_LEAK_THRESHOLD | 60000 | cm-user-service | |
|
||||||
|
|
||||||
|
### 8.2 content-service 환경변수 매핑
|
||||||
|
|
||||||
|
| 환경변수명 | 값 | 지정 객체 | 비고 |
|
||||||
|
|-----------|---|----------|------|
|
||||||
|
| SERVER_PORT | 8084 | cm-content-service | |
|
||||||
|
| REDIS_ENABLED | true | cm-common | |
|
||||||
|
| REDIS_HOST | redis | cm-common | ClusterIP Service |
|
||||||
|
| REDIS_PORT | 6379 | cm-common | |
|
||||||
|
| REDIS_PASSWORD | Hi5Jessica! | secret-common | Secret |
|
||||||
|
| REDIS_DATABASE | 1 | cm-content-service | |
|
||||||
|
| JWT_SECRET | QL0czzXckz18kHnxpaTDoWFkq+3qKO7VQXeNvf2bOoU= | secret-common | Secret |
|
||||||
|
| JWT_ACCESS_TOKEN_VALIDITY | 604800000 | cm-common | |
|
||||||
|
| JWT_REFRESH_TOKEN_VALIDITY | 86400000 | cm-common | |
|
||||||
|
| AZURE_STORAGE_CONNECTION_STRING | DefaultEndpointsProtocol=https;... | secret-content-service | Secret |
|
||||||
|
| AZURE_CONTAINER_NAME | content-images | cm-content-service | |
|
||||||
|
| REPLICATE_API_URL | https://api.replicate.com | cm-content-service | |
|
||||||
|
| REPLICATE_API_TOKEN | "" | secret-content-service | Secret (empty) |
|
||||||
|
| REPLICATE_MODEL_VERSION | stability-ai/sdxl:... | cm-content-service | |
|
||||||
|
| HUGGINGFACE_API_URL | https://api-inference.huggingface.co | cm-content-service | |
|
||||||
|
| HUGGINGFACE_API_TOKEN | "" | secret-content-service | Secret (empty) |
|
||||||
|
| HUGGINGFACE_MODEL | runwayml/stable-diffusion-v1-5 | cm-content-service | |
|
||||||
|
| CORS_ALLOWED_ORIGINS | http://localhost:8081,...,http://kt-event-marketing.20.214.196.128.nip.io | cm-common | |
|
||||||
|
| LOG_LEVEL_APP | INFO | cm-common | |
|
||||||
|
| LOG_LEVEL_WEB | INFO | cm-common | |
|
||||||
|
| LOG_LEVEL_ROOT | INFO | cm-common | |
|
||||||
|
| LOG_FILE_PATH | logs/content-service.log | cm-content-service | |
|
||||||
|
| LOG_FILE_MAX_SIZE | 10MB | cm-common | |
|
||||||
|
| LOG_FILE_MAX_HISTORY | 7 | cm-common | |
|
||||||
|
| LOG_FILE_TOTAL_CAP | 100MB | cm-common | |
|
||||||
|
|
||||||
|
### 8.3 event-service 환경변수 매핑
|
||||||
|
|
||||||
|
| 환경변수명 | 값 | 지정 객체 | 비고 |
|
||||||
|
|-----------|---|----------|------|
|
||||||
|
| SERVER_PORT | 8080 | cm-event-service | |
|
||||||
|
| DB_HOST | event-postgresql | cm-event-service | ClusterIP Service |
|
||||||
|
| DB_PORT | 5432 | cm-event-service | |
|
||||||
|
| DB_NAME | eventdb | cm-event-service | |
|
||||||
|
| DB_USERNAME | eventuser | cm-event-service | |
|
||||||
|
| DB_PASSWORD | Hi5Jessica! | secret-event-service | Secret |
|
||||||
|
| DDL_AUTO | update | cm-common | |
|
||||||
|
| REDIS_HOST | redis | cm-common | ClusterIP Service |
|
||||||
|
| REDIS_PORT | 6379 | cm-common | |
|
||||||
|
| REDIS_PASSWORD | Hi5Jessica! | secret-common | Secret |
|
||||||
|
| REDIS_DATABASE | 2 | cm-event-service | |
|
||||||
|
| KAFKA_BOOTSTRAP_SERVERS | 20.249.182.13:9095,4.217.131.59:9095 | cm-common | |
|
||||||
|
| KAFKA_CONSUMER_GROUP | event-service-consumers | cm-event-service | |
|
||||||
|
| CONTENT_SERVICE_URL | http://content-service | cm-event-service | |
|
||||||
|
| DISTRIBUTION_SERVICE_URL | http://distribution-service | cm-event-service | |
|
||||||
|
| JWT_SECRET | QL0czzXckz18kHnxpaTDoWFkq+3qKO7VQXeNvf2bOoU= | secret-common | Secret |
|
||||||
|
| LOG_LEVEL | INFO | cm-event-service | |
|
||||||
|
| SQL_LOG_LEVEL | WARN | cm-event-service | |
|
||||||
|
| LOG_FILE | logs/event-service.log | cm-event-service | |
|
||||||
|
|
||||||
|
### 8.4 ai-service 환경변수 매핑
|
||||||
|
|
||||||
|
| 환경변수명 | 값 | 지정 객체 | 비고 |
|
||||||
|
|-----------|---|----------|------|
|
||||||
|
| SERVER_PORT | 8083 | cm-ai-service | |
|
||||||
|
| REDIS_HOST | redis | cm-common | ClusterIP Service |
|
||||||
|
| REDIS_PORT | 6379 | cm-common | |
|
||||||
|
| REDIS_PASSWORD | Hi5Jessica! | secret-common | Secret |
|
||||||
|
| REDIS_DATABASE | 3 | cm-ai-service | |
|
||||||
|
| REDIS_TIMEOUT | 3000 | cm-ai-service | |
|
||||||
|
| REDIS_POOL_MIN | 2 | cm-ai-service | |
|
||||||
|
| KAFKA_BOOTSTRAP_SERVERS | 20.249.182.13:9095,4.217.131.59:9095 | cm-common | |
|
||||||
|
| KAFKA_CONSUMER_GROUP | ai-service-consumers | cm-ai-service | |
|
||||||
|
| KAFKA_TOPICS_AI_JOB | ai-event-generation-job | cm-ai-service | |
|
||||||
|
| KAFKA_TOPICS_AI_JOB_DLQ | ai-event-generation-job-dlq | cm-ai-service | |
|
||||||
|
| JWT_SECRET | QL0czzXckz18kHnxpaTDoWFkq+3qKO7VQXeNvf2bOoU= | secret-common | Secret |
|
||||||
|
| JWT_ACCESS_TOKEN_VALIDITY | 604800000 | cm-common | |
|
||||||
|
| JWT_REFRESH_TOKEN_VALIDITY | 86400000 | cm-common | |
|
||||||
|
| CORS_ALLOWED_ORIGINS | http://localhost:8081,...,http://kt-event-marketing.20.214.196.128.nip.io | cm-common | |
|
||||||
|
| CORS_ALLOWED_METHODS | GET,POST,PUT,DELETE,OPTIONS,PATCH | cm-common | |
|
||||||
|
| CORS_ALLOWED_HEADERS | "*" | cm-common | |
|
||||||
|
| CORS_ALLOW_CREDENTIALS | true | cm-common | |
|
||||||
|
| CORS_MAX_AGE | 3600 | cm-common | |
|
||||||
|
| AI_PROVIDER | CLAUDE | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_API_URL | https://api.anthropic.com/v1/messages | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_API_KEY | sk-ant-api03-mLtyNZ... | secret-ai-service | Secret |
|
||||||
|
| AI_CLAUDE_ANTHROPIC_VERSION | 2023-06-01 | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_MODEL | claude-sonnet-4-5-20250929 | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_MAX_TOKENS | 4096 | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_TEMPERATURE | 0.7 | cm-ai-service | |
|
||||||
|
| AI_CLAUDE_TIMEOUT | 300000 | cm-ai-service | |
|
||||||
|
| RESILIENCE4J_CIRCUITBREAKER_FAILURE_RATE_THRESHOLD | 50 | cm-ai-service | |
|
||||||
|
| RESILIENCE4J_CIRCUITBREAKER_SLOW_CALL_RATE_THRESHOLD | 50 | cm-ai-service | |
|
||||||
|
| RESILIENCE4J_CIRCUITBREAKER_SLOW_CALL_DURATION_THRESHOLD | 60s | cm-ai-service | |
|
||||||
|
| RESILIENCE4J_CIRCUITBREAKER_PERMITTED_CALLS_HALF_OPEN | 3 | cm-ai-service | |
|
||||||
|
| RESILIENCE4J_CIRCUITBREAKER_SLIDING_WINDOW_SIZE | 10 | cm-ai-service | |
|
||||||
|
| RESILIENCE4J_CIRCUITBREAKER_MINIMUM_CALLS | 5 | cm-ai-service | |
|
||||||
|
| RESILIENCE4J_CIRCUITBREAKER_WAIT_DURATION_OPEN | 60s | cm-ai-service | |
|
||||||
|
| RESILIENCE4J_TIMELIMITER_TIMEOUT_DURATION | 300s | cm-ai-service | |
|
||||||
|
| CACHE_TTL_RECOMMENDATION | 86400 | cm-ai-service | |
|
||||||
|
| CACHE_TTL_JOB_STATUS | 86400 | cm-ai-service | |
|
||||||
|
| CACHE_TTL_TREND | 3600 | cm-ai-service | |
|
||||||
|
| CACHE_TTL_FALLBACK | 604800 | cm-ai-service | |
|
||||||
|
| LOG_LEVEL_ROOT | INFO | cm-ai-service | |
|
||||||
|
| LOG_LEVEL_AI | DEBUG | cm-ai-service | |
|
||||||
|
| LOG_LEVEL_KAFKA | INFO | cm-ai-service | |
|
||||||
|
| LOG_LEVEL_REDIS | INFO | cm-ai-service | |
|
||||||
|
| LOG_LEVEL_RESILIENCE4J | DEBUG | cm-ai-service | |
|
||||||
|
| LOG_FILE_NAME | logs/ai-service.log | cm-ai-service | |
|
||||||
|
| LOG_FILE_MAX_SIZE | 10MB | cm-ai-service | |
|
||||||
|
| LOG_FILE_MAX_HISTORY | 7 | cm-ai-service | |
|
||||||
|
| LOG_FILE_TOTAL_CAP | 100MB | cm-ai-service | |
|
||||||
|
|
||||||
|
## 9. 트러블슈팅
|
||||||
|
|
||||||
|
### 9.1 Pod가 ImagePullBackOff 상태인 경우
|
||||||
|
```bash
|
||||||
|
# ImagePullSecret 확인
|
||||||
|
kubectl get secret kt-event-marketing -n kt-event-marketing -o yaml
|
||||||
|
|
||||||
|
# Pod 이벤트 확인
|
||||||
|
kubectl describe pod -l app=user-service -n kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
**해결 방법**:
|
||||||
|
1. ACR 자격 증명 재확인
|
||||||
|
2. ImagePullSecret 재생성
|
||||||
|
3. Deployment 재배포
|
||||||
|
|
||||||
|
### 9.2 Pod가 CrashLoopBackOff 상태인 경우
|
||||||
|
```bash
|
||||||
|
# Pod 로그 확인
|
||||||
|
kubectl logs -l app=user-service -n kt-event-marketing --tail=100
|
||||||
|
|
||||||
|
# 이전 컨테이너 로그 확인
|
||||||
|
kubectl logs -l app=user-service -n kt-event-marketing --previous
|
||||||
|
```
|
||||||
|
|
||||||
|
**일반적인 원인**:
|
||||||
|
- 데이터베이스 연결 실패
|
||||||
|
- Redis 연결 실패
|
||||||
|
- 환경변수 설정 오류
|
||||||
|
- 애플리케이션 시작 오류
|
||||||
|
|
||||||
|
### 9.3 Ingress를 통한 접근이 안되는 경우
|
||||||
|
```bash
|
||||||
|
# Ingress 상태 확인
|
||||||
|
kubectl describe ingress kt-event-marketing -n kt-event-marketing
|
||||||
|
|
||||||
|
# Ingress Controller 로그 확인
|
||||||
|
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
**확인 사항**:
|
||||||
|
1. Ingress host가 올바른지 확인
|
||||||
|
2. Service가 정상적으로 동작하는지 확인
|
||||||
|
3. Service selector와 Pod label이 일치하는지 확인
|
||||||
|
|
||||||
|
### 9.4 데이터베이스 연결 실패
|
||||||
|
```bash
|
||||||
|
# PostgreSQL 서비스 확인
|
||||||
|
kubectl get svc | grep postgresql
|
||||||
|
|
||||||
|
# Pod 내부에서 데이터베이스 연결 테스트
|
||||||
|
kubectl exec -it deployment/user-service -n kt-event-marketing -- /bin/sh
|
||||||
|
# 컨테이너 내부에서
|
||||||
|
nc -zv user-postgresql 5432
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.5 Redis 연결 실패
|
||||||
|
```bash
|
||||||
|
# Redis 서비스 확인
|
||||||
|
kubectl get svc | grep redis
|
||||||
|
|
||||||
|
# Pod 내부에서 Redis 연결 테스트
|
||||||
|
kubectl exec -it deployment/user-service -n kt-event-marketing -- /bin/sh
|
||||||
|
# 컨테이너 내부에서
|
||||||
|
nc -zv redis 6379
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. 삭제 및 재배포
|
||||||
|
|
||||||
|
### 10.1 특정 서비스 삭제
|
||||||
|
```bash
|
||||||
|
# user-service 삭제
|
||||||
|
kubectl delete -f deployment/k8s/user-service/
|
||||||
|
|
||||||
|
# content-service 삭제
|
||||||
|
kubectl delete -f deployment/k8s/content-service/
|
||||||
|
|
||||||
|
# event-service 삭제
|
||||||
|
kubectl delete -f deployment/k8s/event-service/
|
||||||
|
|
||||||
|
# ai-service 삭제
|
||||||
|
kubectl delete -f deployment/k8s/ai-service/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 전체 삭제
|
||||||
|
```bash
|
||||||
|
# 모든 서비스 삭제
|
||||||
|
kubectl delete -f deployment/k8s/user-service/
|
||||||
|
kubectl delete -f deployment/k8s/content-service/
|
||||||
|
kubectl delete -f deployment/k8s/event-service/
|
||||||
|
kubectl delete -f deployment/k8s/ai-service/
|
||||||
|
|
||||||
|
# 공통 리소스 삭제
|
||||||
|
kubectl delete -f deployment/k8s/common/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 Namespace 전체 삭제 (주의!)
|
||||||
|
```bash
|
||||||
|
# ⚠️ 주의: Namespace를 삭제하면 모든 리소스가 삭제됩니다
|
||||||
|
kubectl delete namespace kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. 참고 사항
|
||||||
|
|
||||||
|
### 11.1 리소스 제한
|
||||||
|
- **CPU 요청**: 256m (0.25 코어)
|
||||||
|
- **CPU 제한**: 1024m (1 코어)
|
||||||
|
- **메모리 요청**: 256Mi
|
||||||
|
- **메모리 제한**: 1024Mi
|
||||||
|
|
||||||
|
필요에 따라 `deployment.yaml`의 `resources` 섹션을 수정하여 조정할 수 있습니다.
|
||||||
|
|
||||||
|
### 11.2 Probe 설정
|
||||||
|
- **StartupProbe**: 초기 시작 확인 (최대 300초)
|
||||||
|
- **ReadinessProbe**: 트래픽 수신 준비 확인
|
||||||
|
- **LivenessProbe**: 컨테이너 생존 확인
|
||||||
|
|
||||||
|
### 11.3 Auto Scaling (추후 적용)
|
||||||
|
HPA (Horizontal Pod Autoscaler) 설정 예시:
|
||||||
|
```bash
|
||||||
|
kubectl autoscale deployment user-service \
|
||||||
|
--cpu-percent=70 \
|
||||||
|
--min=1 \
|
||||||
|
--max=5 \
|
||||||
|
-n kt-event-marketing
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.4 보안 강화
|
||||||
|
- Secret 암호화: Sealed Secrets 또는 External Secrets Operator 사용 권장
|
||||||
|
- Network Policy: Pod 간 통신 제어
|
||||||
|
- RBAC: 적절한 권한 설정
|
||||||
|
|
||||||
|
## 12. 문의 및 지원
|
||||||
|
|
||||||
|
배포 중 문제가 발생하면 다음 정보를 포함하여 문의하세요:
|
||||||
|
1. Pod 상태: `kubectl get pods -n kt-event-marketing`
|
||||||
|
2. Pod 로그: `kubectl logs <pod-name> -n kt-event-marketing`
|
||||||
|
3. Pod 이벤트: `kubectl describe pod <pod-name> -n kt-event-marketing`
|
||||||
|
4. Ingress 상태: `kubectl describe ingress kt-event-marketing -n kt-event-marketing`
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm-distribution-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
data:
|
||||||
|
# Server Configuration
|
||||||
|
SERVER_PORT: "8085"
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
DB_HOST: "distribution-postgresql"
|
||||||
|
DB_PORT: "5432"
|
||||||
|
DB_NAME: "distributiondb"
|
||||||
|
DB_USERNAME: "eventuser"
|
||||||
|
|
||||||
|
# Kafka Configuration
|
||||||
|
KAFKA_ENABLED: "true"
|
||||||
|
KAFKA_CONSUMER_GROUP: "distribution-service"
|
||||||
|
|
||||||
|
# External Channel APIs
|
||||||
|
URIDONGNETV_API_URL: "http://localhost:9001/api/uridongnetv"
|
||||||
|
RINGOBIZ_API_URL: "http://localhost:9002/api/ringobiz"
|
||||||
|
GINITV_API_URL: "http://localhost:9003/api/ginitv"
|
||||||
|
INSTAGRAM_API_URL: "http://localhost:9004/api/instagram"
|
||||||
|
NAVER_API_URL: "http://localhost:9005/api/naver"
|
||||||
|
KAKAO_API_URL: "http://localhost:9006/api/kakao"
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
LOG_FILE: "logs/distribution-service.log"
|
||||||
63
deployment/k8s/distribution-service/deployment.yaml
Normal file
63
deployment/k8s/distribution-service/deployment.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: distribution-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: distribution-service
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: distribution-service
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: distribution-service
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: kt-event-marketing
|
||||||
|
containers:
|
||||||
|
- name: distribution-service
|
||||||
|
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8085
|
||||||
|
name: http
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-common
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-distribution-service
|
||||||
|
- secretRef:
|
||||||
|
name: secret-common
|
||||||
|
- secretRef:
|
||||||
|
name: secret-distribution-service
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "256m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "1024m"
|
||||||
|
memory: "1024Mi"
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8085
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8085
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/liveness
|
||||||
|
port: 8085
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secret-distribution-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
DB_PASSWORD: "Hi5Jessica!"
|
||||||
16
deployment/k8s/distribution-service/service.yaml
Normal file
16
deployment/k8s/distribution-service/service.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: distribution-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: distribution-service
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: distribution-service
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8085
|
||||||
|
protocol: TCP
|
||||||
29
deployment/k8s/event-service/cm-event-service.yaml
Normal file
29
deployment/k8s/event-service/cm-event-service.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm-event-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
data:
|
||||||
|
# Server Configuration
|
||||||
|
SERVER_PORT: "8080"
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
DB_HOST: "event-postgresql"
|
||||||
|
DB_PORT: "5432"
|
||||||
|
DB_NAME: "eventdb"
|
||||||
|
DB_USERNAME: "eventuser"
|
||||||
|
|
||||||
|
# Redis Configuration (service-specific)
|
||||||
|
REDIS_DATABASE: "2"
|
||||||
|
|
||||||
|
# Kafka Configuration (service-specific)
|
||||||
|
KAFKA_CONSUMER_GROUP: "event-service-consumers"
|
||||||
|
|
||||||
|
# Service URLs
|
||||||
|
CONTENT_SERVICE_URL: "http://content-service"
|
||||||
|
DISTRIBUTION_SERVICE_URL: "http://distribution-service"
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
LOG_LEVEL: "INFO"
|
||||||
|
SQL_LOG_LEVEL: "WARN"
|
||||||
|
LOG_FILE: "logs/event-service.log"
|
||||||
63
deployment/k8s/event-service/deployment.yaml
Normal file
63
deployment/k8s/event-service/deployment.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: event-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: event-service
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: event-service
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: event-service
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: kt-event-marketing
|
||||||
|
containers:
|
||||||
|
- name: event-service
|
||||||
|
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-common
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-event-service
|
||||||
|
- secretRef:
|
||||||
|
name: secret-common
|
||||||
|
- secretRef:
|
||||||
|
name: secret-event-service
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "256m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "1024m"
|
||||||
|
memory: "1024Mi"
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/liveness
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
9
deployment/k8s/event-service/secret-event-service.yaml
Normal file
9
deployment/k8s/event-service/secret-event-service.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secret-event-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
# Database Password
|
||||||
|
DB_PASSWORD: "Hi5Jessica!"
|
||||||
16
deployment/k8s/event-service/service.yaml
Normal file
16
deployment/k8s/event-service/service.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: event-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: event-service
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: event-service
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm-participation-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
data:
|
||||||
|
# Server Configuration
|
||||||
|
SERVER_PORT: "8084"
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
DB_HOST: "participation-postgresql"
|
||||||
|
DB_PORT: "5432"
|
||||||
|
DB_NAME: "participationdb"
|
||||||
|
DB_USERNAME: "eventuser"
|
||||||
|
|
||||||
|
# Redis Configuration (service-specific)
|
||||||
|
REDIS_DATABASE: "4"
|
||||||
|
|
||||||
|
# Kafka Configuration (service-specific)
|
||||||
|
KAFKA_CONSUMER_GROUP: "participation-service-consumers"
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
LOG_LEVEL: "INFO"
|
||||||
|
SHOW_SQL: "false"
|
||||||
|
LOG_FILE: "logs/participation-service.log"
|
||||||
63
deployment/k8s/participation-service/deployment.yaml
Normal file
63
deployment/k8s/participation-service/deployment.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: participation-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: participation-service
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: participation-service
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: participation-service
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: kt-event-marketing
|
||||||
|
containers:
|
||||||
|
- name: participation-service
|
||||||
|
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8084
|
||||||
|
name: http
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-common
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-participation-service
|
||||||
|
- secretRef:
|
||||||
|
name: secret-common
|
||||||
|
- secretRef:
|
||||||
|
name: secret-participation-service
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "256m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "1024m"
|
||||||
|
memory: "1024Mi"
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/liveness
|
||||||
|
port: 8084
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/liveness
|
||||||
|
port: 8084
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8084
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secret-participation-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
DB_PASSWORD: "Hi5Jessica!"
|
||||||
16
deployment/k8s/participation-service/service.yaml
Normal file
16
deployment/k8s/participation-service/service.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: participation-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: participation-service
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8084
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: participation-service
|
||||||
32
deployment/k8s/user-service/cm-user-service.yaml
Normal file
32
deployment/k8s/user-service/cm-user-service.yaml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm-user-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
data:
|
||||||
|
# Server Configuration
|
||||||
|
SERVER_PORT: "8081"
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
DB_URL: "jdbc:postgresql://user-postgresql:5432/userdb"
|
||||||
|
DB_HOST: "user-postgresql"
|
||||||
|
DB_PORT: "5432"
|
||||||
|
DB_NAME: "userdb"
|
||||||
|
DB_USERNAME: "eventuser"
|
||||||
|
DB_DRIVER: "org.postgresql.Driver"
|
||||||
|
DB_KIND: "postgresql"
|
||||||
|
DB_POOL_MAX: "20"
|
||||||
|
DB_POOL_MIN: "5"
|
||||||
|
DB_CONN_TIMEOUT: "30000"
|
||||||
|
DB_IDLE_TIMEOUT: "600000"
|
||||||
|
DB_MAX_LIFETIME: "1800000"
|
||||||
|
DB_LEAK_THRESHOLD: "60000"
|
||||||
|
|
||||||
|
# Redis Configuration (service-specific)
|
||||||
|
REDIS_DATABASE: "0"
|
||||||
|
|
||||||
|
# Kafka Configuration (service-specific)
|
||||||
|
KAFKA_CONSUMER_GROUP: "user-service-consumers"
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
LOG_FILE_PATH: "logs/user-service.log"
|
||||||
63
deployment/k8s/user-service/deployment.yaml
Normal file
63
deployment/k8s/user-service/deployment.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: user-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: user-service
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: user-service
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: user-service
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: kt-event-marketing
|
||||||
|
containers:
|
||||||
|
- name: user-service
|
||||||
|
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8081
|
||||||
|
name: http
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-common
|
||||||
|
- configMapRef:
|
||||||
|
name: cm-user-service
|
||||||
|
- secretRef:
|
||||||
|
name: secret-common
|
||||||
|
- secretRef:
|
||||||
|
name: secret-user-service
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "256m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "1024m"
|
||||||
|
memory: "1024Mi"
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 8081
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/readiness
|
||||||
|
port: 8081
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health/liveness
|
||||||
|
port: 8081
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
9
deployment/k8s/user-service/secret-user-service.yaml
Normal file
9
deployment/k8s/user-service/secret-user-service.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: secret-user-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
# Database Password
|
||||||
|
DB_PASSWORD: "Hi5Jessica!"
|
||||||
16
deployment/k8s/user-service/service.yaml
Normal file
16
deployment/k8s/user-service/service.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: user-service
|
||||||
|
namespace: kt-event-marketing
|
||||||
|
labels:
|
||||||
|
app: user-service
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8081
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: user-service
|
||||||
@ -1,3 +1,7 @@
|
|||||||
|
bootJar {
|
||||||
|
archiveFileName = 'distribution-service.jar'
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Kafka for event publishing
|
// Kafka for event publishing
|
||||||
implementation 'org.springframework.kafka:spring-kafka'
|
implementation 'org.springframework.kafka:spring-kafka'
|
||||||
|
|||||||
@ -4,9 +4,9 @@ spring:
|
|||||||
|
|
||||||
# Database Configuration (PostgreSQL)
|
# Database Configuration (PostgreSQL)
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:eventdb}
|
url: jdbc:postgresql://${DB_HOST:20.249.177.232}:${DB_PORT:5432}/${DB_NAME:eventdb}
|
||||||
username: ${DB_USERNAME:eventuser}
|
username: ${DB_USERNAME:eventuser}
|
||||||
password: ${DB_PASSWORD:eventpass}
|
password: ${DB_PASSWORD:Hi5Jessica!}
|
||||||
driver-class-name: org.postgresql.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
hikari:
|
hikari:
|
||||||
maximum-pool-size: 5
|
maximum-pool-size: 5
|
||||||
@ -33,9 +33,10 @@ spring:
|
|||||||
# Redis Configuration
|
# Redis Configuration
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: ${REDIS_HOST:localhost}
|
host: ${REDIS_HOST:20.214.210.71}
|
||||||
port: ${REDIS_PORT:6379}
|
port: ${REDIS_PORT:6379}
|
||||||
password: ${REDIS_PASSWORD:}
|
password: ${REDIS_PASSWORD:Hi5Jessica!}
|
||||||
|
database: ${REDIS_DATABASE:0}
|
||||||
timeout: 60000ms
|
timeout: 60000ms
|
||||||
connect-timeout: 60000ms
|
connect-timeout: 60000ms
|
||||||
lettuce:
|
lettuce:
|
||||||
@ -48,7 +49,7 @@ spring:
|
|||||||
|
|
||||||
# Kafka Configuration
|
# Kafka Configuration
|
||||||
kafka:
|
kafka:
|
||||||
bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
|
bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:20.249.182.13:9095,4.217.131.59:9095}
|
||||||
producer:
|
producer:
|
||||||
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||||
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
|
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
|
||||||
@ -134,11 +135,11 @@ feign:
|
|||||||
|
|
||||||
# Content Service Client
|
# Content Service Client
|
||||||
content-service:
|
content-service:
|
||||||
url: ${CONTENT_SERVICE_URL:http://localhost:8082}
|
url: ${CONTENT_SERVICE_URL:http://localhost:8084}
|
||||||
|
|
||||||
# Distribution Service Client
|
# Distribution Service Client
|
||||||
distribution-service:
|
distribution-service:
|
||||||
url: ${DISTRIBUTION_SERVICE_URL:http://localhost:8084}
|
url: ${DISTRIBUTION_SERVICE_URL:http://localhost:8085}
|
||||||
|
|
||||||
# Application Configuration
|
# Application Configuration
|
||||||
app:
|
app:
|
||||||
|
|||||||
@ -18,6 +18,10 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bootJar {
|
||||||
|
archiveFileName = 'participation-service.jar'
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Common 모듈
|
// Common 모듈
|
||||||
implementation project(':common')
|
implementation project(':common')
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
bootJar {
|
||||||
|
archiveFileName = 'user-service.jar'
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// BCrypt for password hashing
|
// BCrypt for password hashing
|
||||||
implementation 'org.springframework.security:spring-security-crypto'
|
implementation 'org.springframework.security:spring-security-crypto'
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import org.springframework.http.ResponseEntity;
|
|||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Controller
|
* User Controller
|
||||||
*
|
*
|
||||||
@ -90,7 +92,7 @@ public class UserController {
|
|||||||
@GetMapping("/profile")
|
@GetMapping("/profile")
|
||||||
@Operation(summary = "프로필 조회", description = "사용자 프로필 조회 API")
|
@Operation(summary = "프로필 조회", description = "사용자 프로필 조회 API")
|
||||||
public ResponseEntity<ProfileResponse> getProfile(@AuthenticationPrincipal UserPrincipal principal) {
|
public ResponseEntity<ProfileResponse> getProfile(@AuthenticationPrincipal UserPrincipal principal) {
|
||||||
Long userId = principal.getUserId();
|
UUID userId = principal.getUserId();
|
||||||
log.info("프로필 조회 요청: userId={}", userId);
|
log.info("프로필 조회 요청: userId={}", userId);
|
||||||
ProfileResponse response = userService.getProfile(userId);
|
ProfileResponse response = userService.getProfile(userId);
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
@ -106,7 +108,7 @@ public class UserController {
|
|||||||
public ResponseEntity<ProfileResponse> updateProfile(
|
public ResponseEntity<ProfileResponse> updateProfile(
|
||||||
@AuthenticationPrincipal UserPrincipal principal,
|
@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@Valid @RequestBody UpdateProfileRequest request) {
|
@Valid @RequestBody UpdateProfileRequest request) {
|
||||||
Long userId = principal.getUserId();
|
UUID userId = principal.getUserId();
|
||||||
log.info("프로필 수정 요청: userId={}", userId);
|
log.info("프로필 수정 요청: userId={}", userId);
|
||||||
ProfileResponse response = userService.updateProfile(userId, request);
|
ProfileResponse response = userService.updateProfile(userId, request);
|
||||||
log.info("프로필 수정 성공: userId={}", userId);
|
log.info("프로필 수정 성공: userId={}", userId);
|
||||||
@ -123,7 +125,7 @@ public class UserController {
|
|||||||
public ResponseEntity<Void> changePassword(
|
public ResponseEntity<Void> changePassword(
|
||||||
@AuthenticationPrincipal UserPrincipal principal,
|
@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@Valid @RequestBody ChangePasswordRequest request) {
|
@Valid @RequestBody ChangePasswordRequest request) {
|
||||||
Long userId = principal.getUserId();
|
UUID userId = principal.getUserId();
|
||||||
log.info("비밀번호 변경 요청: userId={}", userId);
|
log.info("비밀번호 변경 요청: userId={}", userId);
|
||||||
userService.changePassword(userId, request);
|
userService.changePassword(userId, request);
|
||||||
log.info("비밀번호 변경 성공: userId={}", userId);
|
log.info("비밀번호 변경 성공: userId={}", userId);
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import lombok.Builder;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 로그인 응답 DTO
|
* 로그인 응답 DTO
|
||||||
*
|
*
|
||||||
@ -27,7 +29,7 @@ public class LoginResponse {
|
|||||||
/**
|
/**
|
||||||
* 사용자 ID
|
* 사용자 ID
|
||||||
*/
|
*/
|
||||||
private Long userId;
|
private UUID userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사용자 이름
|
* 사용자 이름
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import lombok.Getter;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 프로필 응답 DTO
|
* 프로필 응답 DTO
|
||||||
@ -24,7 +25,7 @@ public class ProfileResponse {
|
|||||||
/**
|
/**
|
||||||
* 사용자 ID
|
* 사용자 ID
|
||||||
*/
|
*/
|
||||||
private Long userId;
|
private UUID userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사용자 이름
|
* 사용자 이름
|
||||||
@ -49,7 +50,7 @@ public class ProfileResponse {
|
|||||||
/**
|
/**
|
||||||
* 매장 ID
|
* 매장 ID
|
||||||
*/
|
*/
|
||||||
private Long storeId;
|
private UUID storeId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장명
|
* 매장명
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import lombok.Builder;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 회원가입 응답 DTO
|
* 회원가입 응답 DTO
|
||||||
*
|
*
|
||||||
@ -27,7 +29,7 @@ public class RegisterResponse {
|
|||||||
/**
|
/**
|
||||||
* 사용자 ID
|
* 사용자 ID
|
||||||
*/
|
*/
|
||||||
private Long userId;
|
private UUID userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사용자 이름
|
* 사용자 이름
|
||||||
@ -37,7 +39,7 @@ public class RegisterResponse {
|
|||||||
/**
|
/**
|
||||||
* 매장 ID
|
* 매장 ID
|
||||||
*/
|
*/
|
||||||
private Long storeId;
|
private UUID storeId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장명
|
* 매장명
|
||||||
|
|||||||
@ -3,6 +3,9 @@ package com.kt.event.user.entity;
|
|||||||
import com.kt.event.common.entity.BaseTimeEntity;
|
import com.kt.event.common.entity.BaseTimeEntity;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장 엔티티
|
* 매장 엔티티
|
||||||
@ -24,9 +27,10 @@ public class Store extends BaseTimeEntity {
|
|||||||
* 매장 ID (Primary Key)
|
* 매장 ID (Primary Key)
|
||||||
*/
|
*/
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(generator = "uuid2")
|
||||||
@Column(name = "store_id")
|
@GenericGenerator(name = "uuid2", strategy = "uuid2")
|
||||||
private Long id;
|
@Column(name = "store_id", columnDefinition = "uuid")
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장명
|
* 매장명
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package com.kt.event.user.entity;
|
|||||||
import com.kt.event.common.entity.BaseTimeEntity;
|
import com.kt.event.common.entity.BaseTimeEntity;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사용자 엔티티
|
* 사용자 엔티티
|
||||||
@ -29,9 +31,10 @@ public class User extends BaseTimeEntity {
|
|||||||
* 사용자 ID (Primary Key)
|
* 사용자 ID (Primary Key)
|
||||||
*/
|
*/
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(generator = "uuid2")
|
||||||
@Column(name = "user_id")
|
@GenericGenerator(name = "uuid2", strategy = "uuid2")
|
||||||
private Long id;
|
@Column(name = "user_id", columnDefinition = "uuid")
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사용자 이름
|
* 사용자 이름
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장 Repository
|
* 매장 Repository
|
||||||
@ -15,7 +16,7 @@ import java.util.Optional;
|
|||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
@Repository
|
@Repository
|
||||||
public interface StoreRepository extends JpaRepository<Store, Long> {
|
public interface StoreRepository extends JpaRepository<Store, UUID> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사용자 ID로 매장 조회
|
* 사용자 ID로 매장 조회
|
||||||
@ -23,5 +24,5 @@ public interface StoreRepository extends JpaRepository<Store, Long> {
|
|||||||
* @param userId 사용자 ID
|
* @param userId 사용자 ID
|
||||||
* @return 매장 Optional
|
* @return 매장 Optional
|
||||||
*/
|
*/
|
||||||
Optional<Store> findByUserId(Long userId);
|
Optional<Store> findByUserId(UUID userId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import org.springframework.stereotype.Repository;
|
|||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사용자 Repository
|
* 사용자 Repository
|
||||||
@ -19,7 +20,7 @@ import java.util.Optional;
|
|||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
@Repository
|
@Repository
|
||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
public interface UserRepository extends JpaRepository<User, UUID> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 이메일로 사용자 조회
|
* 이메일로 사용자 조회
|
||||||
@ -61,5 +62,5 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
|||||||
*/
|
*/
|
||||||
@Modifying
|
@Modifying
|
||||||
@Query("UPDATE User u SET u.lastLoginAt = :lastLoginAt WHERE u.id = :userId")
|
@Query("UPDATE User u SET u.lastLoginAt = :lastLoginAt WHERE u.id = :userId")
|
||||||
void updateLastLoginAt(@Param("userId") Long userId, @Param("lastLoginAt") LocalDateTime lastLoginAt);
|
void updateLastLoginAt(@Param("userId") UUID userId, @Param("lastLoginAt") LocalDateTime lastLoginAt);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import com.kt.event.user.dto.request.RegisterRequest;
|
|||||||
import com.kt.event.user.dto.response.ProfileResponse;
|
import com.kt.event.user.dto.response.ProfileResponse;
|
||||||
import com.kt.event.user.dto.response.RegisterResponse;
|
import com.kt.event.user.dto.response.RegisterResponse;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Service Interface
|
* User Service Interface
|
||||||
*
|
*
|
||||||
@ -30,7 +32,7 @@ public interface UserService {
|
|||||||
* @param userId 사용자 ID
|
* @param userId 사용자 ID
|
||||||
* @return 프로필 응답
|
* @return 프로필 응답
|
||||||
*/
|
*/
|
||||||
ProfileResponse getProfile(Long userId);
|
ProfileResponse getProfile(UUID userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 프로필 수정
|
* 프로필 수정
|
||||||
@ -39,7 +41,7 @@ public interface UserService {
|
|||||||
* @param request 프로필 수정 요청
|
* @param request 프로필 수정 요청
|
||||||
* @return 프로필 응답
|
* @return 프로필 응답
|
||||||
*/
|
*/
|
||||||
ProfileResponse updateProfile(Long userId, UpdateProfileRequest request);
|
ProfileResponse updateProfile(UUID userId, UpdateProfileRequest request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 비밀번호 변경
|
* 비밀번호 변경
|
||||||
@ -47,12 +49,12 @@ public interface UserService {
|
|||||||
* @param userId 사용자 ID
|
* @param userId 사용자 ID
|
||||||
* @param request 비밀번호 변경 요청
|
* @param request 비밀번호 변경 요청
|
||||||
*/
|
*/
|
||||||
void changePassword(Long userId, ChangePasswordRequest request);
|
void changePassword(UUID userId, ChangePasswordRequest request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 최종 로그인 시각 업데이트 (비동기)
|
* 최종 로그인 시각 업데이트 (비동기)
|
||||||
*
|
*
|
||||||
* @param userId 사용자 ID
|
* @param userId 사용자 ID
|
||||||
*/
|
*/
|
||||||
void updateLastLoginAt(Long userId);
|
void updateLastLoginAt(UUID userId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,7 +76,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
|||||||
|
|
||||||
// 3. 매장 정보 조회
|
// 3. 매장 정보 조회
|
||||||
Store store = storeRepository.findByUserId(user.getId()).orElse(null);
|
Store store = storeRepository.findByUserId(user.getId()).orElse(null);
|
||||||
Long storeId = store != null ? store.getId() : null;
|
UUID storeId = store != null ? store.getId() : null;
|
||||||
|
|
||||||
// 4. JWT 토큰 생성
|
// 4. JWT 토큰 생성
|
||||||
String token = jwtTokenProvider.createAccessToken(
|
String token = jwtTokenProvider.createAccessToken(
|
||||||
@ -144,7 +145,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
|||||||
* @param userId 사용자 ID
|
* @param userId 사용자 ID
|
||||||
* @param role 역할
|
* @param role 역할
|
||||||
*/
|
*/
|
||||||
private void saveSession(String token, Long userId, String role) {
|
private void saveSession(String token, UUID userId, String role) {
|
||||||
if (redisTemplate != null) {
|
if (redisTemplate != null) {
|
||||||
String key = "user:session:" + token;
|
String key = "user:session:" + token;
|
||||||
String value = userId + ":" + role;
|
String value = userId + ":" + role;
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,7 +129,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
* UFR-USER-030: 프로필 관리
|
* UFR-USER-030: 프로필 관리
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ProfileResponse getProfile(Long userId) {
|
public ProfileResponse getProfile(UUID userId) {
|
||||||
User user = userRepository.findById(userId)
|
User user = userRepository.findById(userId)
|
||||||
.orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_FOUND.getErrorCode()));
|
.orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_FOUND.getErrorCode()));
|
||||||
|
|
||||||
@ -158,7 +159,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public ProfileResponse updateProfile(Long userId, UpdateProfileRequest request) {
|
public ProfileResponse updateProfile(UUID userId, UpdateProfileRequest request) {
|
||||||
User user = userRepository.findById(userId)
|
User user = userRepository.findById(userId)
|
||||||
.orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_FOUND.getErrorCode()));
|
.orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_FOUND.getErrorCode()));
|
||||||
|
|
||||||
@ -186,7 +187,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void changePassword(Long userId, ChangePasswordRequest request) {
|
public void changePassword(UUID userId, ChangePasswordRequest request) {
|
||||||
User user = userRepository.findById(userId)
|
User user = userRepository.findById(userId)
|
||||||
.orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_FOUND.getErrorCode()));
|
.orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_FOUND.getErrorCode()));
|
||||||
|
|
||||||
@ -213,7 +214,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
@Override
|
@Override
|
||||||
@Async
|
@Async
|
||||||
@Transactional
|
@Transactional
|
||||||
public void updateLastLoginAt(Long userId) {
|
public void updateLastLoginAt(UUID userId) {
|
||||||
userRepository.updateLastLoginAt(userId, LocalDateTime.now());
|
userRepository.updateLastLoginAt(userId, LocalDateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +225,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
* @param userId 사용자 ID
|
* @param userId 사용자 ID
|
||||||
* @param role 역할
|
* @param role 역할
|
||||||
*/
|
*/
|
||||||
private void saveSession(String token, Long userId, String role) {
|
private void saveSession(String token, UUID userId, String role) {
|
||||||
if (redisTemplate != null) {
|
if (redisTemplate != null) {
|
||||||
String key = "user:session:" + token;
|
String key = "user:session:" + token;
|
||||||
String value = userId + ":" + role;
|
String value = userId + ":" + role;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user