From e6f2c3a810022d01f7b5588b2a9a33bbcecf0470 Mon Sep 17 00:00:00 2001 From: OhSeongRak Date: Wed, 18 Jun 2025 13:48:10 +0900 Subject: [PATCH] refactor: all --- deployment/container/nginx.conf | 126 +++++++++++++++++--- deployment/deploy.yaml.template | 3 +- deployment/deploy_env_vars | 47 ++++++-- deployment/manifest/deployment.yaml | 39 +++++- deployment/manifest/frontend-configmap.yaml | 4 +- public/runtime-env.js | 97 +++++++++++---- 6 files changed, 266 insertions(+), 50 deletions(-) diff --git a/deployment/container/nginx.conf b/deployment/container/nginx.conf index 685b08c..8bbf9a5 100644 --- a/deployment/container/nginx.conf +++ b/deployment/container/nginx.conf @@ -4,7 +4,15 @@ server { root /usr/share/nginx/html; index index.html index.htm; - # Gzip compression + # 에러 페이지 설정 + error_page 404 /index.html; + error_page 500 502 503 504 /50x.html; + + # 로깅 설정 + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log warn; + + # Gzip compression 최적화 gzip on; gzip_vary on; gzip_min_length 1024; @@ -17,30 +25,118 @@ server { text/javascript application/javascript application/xml+rss - application/json; + application/json + application/xml + image/svg+xml; - # Handle client routing (Vue Router) - location / { - try_files $uri $uri/ /index.html; - } + # Brotli compression (더 좋은 압축률) + # brotli on; + # brotli_comp_level 6; + # brotli_types text/xml image/svg+xml application/x-font-ttf image/vnd.microsoft.icon application/x-font-opentype application/json font/eot application/vnd.ms-fontobject application/javascript font/otf application/xml application/xhtml+xml text/javascript application/x-javascript text/plain application/x-font-truetype application/xml+rss image/x-icon font/opentype text/css image/x-win-bitmap; - # Security headers + # 보안 헤더 강화 add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "no-referrer-when-downgrade" always; - add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' http://smarketing.20.249.184.228.nip.io https://smarketing.20.249.184.228.nip.io" always; + add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { - expires 1y; - add_header Cache-Control "public, immutable"; + # CORS 설정 (필요시) + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always; + + # SPA 라우팅 처리 (Vue Router) + location / { + try_files $uri $uri/ /index.html; + + # HTML 파일은 캐시하지 않음 (런타임 설정 반영 위해) + location ~* \.html$ { + expires -1; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + } } - # Health check endpoint + # 런타임 환경 설정 파일 - 캐시하지 않음 + location /runtime-env.js { + expires -1; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + } + + # 정적 자산 캐시 최적화 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + + # 정적 파일에 대한 로깅 최소화 + access_log off; + } + + # manifest.json과 service worker 캐시 설정 + location ~* \.(json|webmanifest)$ { + expires 1d; + add_header Cache-Control "public"; + } + + location /sw.js { + expires -1; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } + + # 파비콘 캐시 + location /favicon.ico { + expires 1M; + access_log off; + log_not_found off; + } + + # 헬스체크 엔드포인트 location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } -} \ No newline at end of file + + # API 프록시 (필요시 백엔드로 직접 프록시) + # location /api/ { + # proxy_pass http://smarketing.20.249.184.228.nip.io/api/; + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + # proxy_connect_timeout 30s; + # proxy_send_timeout 30s; + # proxy_read_timeout 30s; + # } + + # 숨겨진 파일 접근 차단 + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + # 불필요한 파일 접근 차단 + location ~* \.(log|txt|md|yml|yaml|conf)$ { + deny all; + access_log off; + log_not_found off; + } + + # 압축된 자산 우선 제공 + location ~* \.(js|css)$ { + gzip_static on; + expires 1y; + add_header Cache-Control "public, immutable"; + } +} + +# 추가 서버 블록 (HTTPS 리다이렉트, 필요시) +# server { +# listen 8080; +# server_name smarketing.20.249.184.228.nip.io; +# return 301 https://$server_name$request_uri; +# } \ No newline at end of file diff --git a/deployment/deploy.yaml.template b/deployment/deploy.yaml.template index 5379137..63a8d68 100644 --- a/deployment/deploy.yaml.template +++ b/deployment/deploy.yaml.template @@ -140,7 +140,7 @@ metadata: labels: app: smarketing-frontend spec: - type: LoadBalancer + type: ClusterIP ports: - port: 80 targetPort: ${export_port} @@ -157,6 +157,7 @@ metadata: namespace: ${namespace} annotations: nginx.ingress.kubernetes.io/rewrite-target: / + nginx.ingress.kubernetes.io/ssl-redirect: "false" spec: rules: - host: ${ingress_host} diff --git a/deployment/deploy_env_vars b/deployment/deploy_env_vars index aa706ef..e449517 100644 --- a/deployment/deploy_env_vars +++ b/deployment/deploy_env_vars @@ -14,14 +14,14 @@ export_port=18080 # Gateway/Ingress 설정 (⭐ smarketing-backend와 동일한 IP 사용) ingress_host=smarketing.20.249.184.228.nip.io -# 리소스 설정 -resources_requests_cpu=256m -resources_requests_memory=256Mi -resources_limits_cpu=1024m -resources_limits_memory=1024Mi +# 리소스 설정 (프론트엔드에 맞게 조정) +resources_requests_cpu=128m # 프론트엔드는 CPU 사용량이 적음 +resources_requests_memory=128Mi # 메모리도 적게 사용 +resources_limits_cpu=512m # 제한도 낮게 설정 +resources_limits_memory=512Mi # API URLs (⭐ smarketing-backend ingress를 통해 라우팅) -# 현재 설정된 백엔드 API들과 일치 +# 백엔드 서비스별 API 경로들 auth_url=http://smarketing.20.249.184.228.nip.io/api/auth member_url=http://smarketing.20.249.184.228.nip.io/api/member store_url=http://smarketing.20.249.184.228.nip.io/api/store @@ -30,6 +30,39 @@ sales_url=http://smarketing.20.249.184.228.nip.io/api/sales content_url=http://smarketing.20.249.184.228.nip.io/api/content recommend_url=http://smarketing.20.249.184.228.nip.io/api/recommend +# Frontend 이미지 경로 설정 +smarketing_frontend_image_path=${registry}/${image_org}/smarketing-frontend:latest + # GitHub 설정 github_org=won-ktds -teamid=smarketing \ No newline at end of file +teamid=smarketing + +# 환경 플래그 +environment=production +debug_mode=false + +# SSL/TLS 설정 (필요시) +ssl_enabled=false +ssl_redirect=false + +# 로깅 레벨 +log_level=info + +# 헬스체크 설정 +health_check_path=/health +health_check_interval=30s +health_check_timeout=5s +health_check_retries=3 + +# 보안 설정 +security_headers_enabled=true +cors_enabled=true +allowed_origins=* + +# 캐시 설정 +static_cache_enabled=true +static_cache_duration=1y + +# 압축 설정 +gzip_enabled=true +gzip_compression_level=6 \ No newline at end of file diff --git a/deployment/manifest/deployment.yaml b/deployment/manifest/deployment.yaml index 272d253..6d19513 100644 --- a/deployment/manifest/deployment.yaml +++ b/deployment/manifest/deployment.yaml @@ -5,6 +5,7 @@ metadata: namespace: ${namespace} labels: app: smarketing-frontend + version: v1 spec: replicas: ${replicas} selector: @@ -14,6 +15,7 @@ spec: metadata: labels: app: smarketing-frontend + version: v1 spec: imagePullSecrets: - name: acr-secret @@ -23,6 +25,7 @@ spec: imagePullPolicy: Always ports: - containerPort: ${export_port} + name: http resources: requests: cpu: ${resources_requests_cpu} @@ -34,19 +37,53 @@ spec: - name: runtime-config mountPath: /usr/share/nginx/html/runtime-env.js subPath: runtime-env.js + readOnly: true + env: + - name: NGINX_PORT + value: "${export_port}" livenessProbe: httpGet: path: /health port: ${export_port} + scheme: HTTP initialDelaySeconds: 30 periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 readinessProbe: httpGet: path: /health port: ${export_port} + scheme: HTTP initialDelaySeconds: 5 periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + successThreshold: 1 + # 시작 프로브 추가 (컨테이너 시작 시간이 오래 걸릴 수 있음) + startupProbe: + httpGet: + path: /health + port: ${export_port} + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 30 + successThreshold: 1 volumes: - name: runtime-config configMap: - name: smarketing-frontend-config \ No newline at end of file + name: smarketing-frontend-config + defaultMode: 0644 + # 배포 전략 설정 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + # 재시작 정책 + restartPolicy: Always + # DNS 정책 + dnsPolicy: ClusterFirst \ No newline at end of file diff --git a/deployment/manifest/frontend-configmap.yaml b/deployment/manifest/frontend-configmap.yaml index d111f6e..e3e164d 100644 --- a/deployment/manifest/frontend-configmap.yaml +++ b/deployment/manifest/frontend-configmap.yaml @@ -8,14 +8,14 @@ data: console.log('=== RUNTIME-ENV.JS 로드됨 (배포 환경) ==='); window.__runtime_config__ = { - // 백엔드 API 구조에 맞게 URL 설정 + // 백엔드 API 구조에 맞게 URL 설정 - ingress host 사용 AUTH_URL: 'http://${ingress_host}/api/auth', MEMBER_URL: 'http://${ingress_host}/api/member', STORE_URL: 'http://${ingress_host}/api/store', MENU_URL: 'http://${ingress_host}/api/menu', SALES_URL: 'http://${ingress_host}/api/sales', CONTENT_URL: 'http://${ingress_host}/api/content', - RECOMMEND_URL: 'http://${ingress_host}/api/recommendations', + RECOMMEND_URL: 'http://${ingress_host}/api/recommend', // Gateway URL (운영 환경) GATEWAY_URL: 'http://${ingress_host}', diff --git a/public/runtime-env.js b/public/runtime-env.js index 0dff287..8673a31 100644 --- a/public/runtime-env.js +++ b/public/runtime-env.js @@ -1,18 +1,62 @@ -//* public/runtime-env.js - 백엔드 API 경로에 맞게 수정 +//* public/runtime-env.js - 배포 환경 우선 설정 console.log('=== RUNTIME-ENV.JS 로드됨 ==='); +// 배포 환경 감지 함수 +const isProduction = () => { + // 프로덕션 환경 감지 로직 + return window.location.hostname !== 'localhost' && + window.location.hostname !== '127.0.0.1' && + !window.location.hostname.includes('dev'); +}; + +// 기본 ingress host 설정 (deploy_env_vars에서 설정된 값) +const DEFAULT_INGRESS_HOST = 'smarketing.20.249.184.228.nip.io'; + +// 환경별 API URL 설정 +const getBaseUrl = () => { + if (isProduction()) { + // 프로덕션: ingress host 사용 + return `http://${DEFAULT_INGRESS_HOST}`; + } else { + // 개발환경: localhost 사용 + return ''; + } +}; + +const baseUrl = getBaseUrl(); + window.__runtime_config__ = { - // ⚠️ 수정: 백엔드 API 구조에 맞게 URL 설정 - AUTH_URL: 'http://localhost:8081/api/auth', - MEMBER_URL: 'http://localhost:8081/api/member', - STORE_URL: 'http://localhost:8082/api/store', - MENU_URL: 'http://localhost:8082/api/menu', - SALES_URL: 'http://localhost:8082/api/sales', // store 서비스 - CONTENT_URL: 'http://localhost:8083/api/content', - RECOMMEND_URL: 'http://localhost:8084/api/recommendations', // ⚠️ 수정: 올바른 경로 + // 프로덕션 환경에서는 ingress host 사용, 개발환경에서는 localhost + AUTH_URL: isProduction() ? + `${baseUrl}/api/auth` : + 'http://localhost:8081/api/auth', + + MEMBER_URL: isProduction() ? + `${baseUrl}/api/member` : + 'http://localhost:8081/api/member', + + STORE_URL: isProduction() ? + `${baseUrl}/api/store` : + 'http://localhost:8082/api/store', + + MENU_URL: isProduction() ? + `${baseUrl}/api/menu` : + 'http://localhost:8082/api/menu', + + SALES_URL: isProduction() ? + `${baseUrl}/api/sales` : + 'http://localhost:8082/api/sales', + + CONTENT_URL: isProduction() ? + `${baseUrl}/api/content` : + 'http://localhost:8083/api/content', + + RECOMMEND_URL: isProduction() ? + `${baseUrl}/api/recommend` : + 'http://localhost:8084/api/recommendations', - // Gateway URL (운영 환경용) - GATEWAY_URL: 'http://20.1.2.3', + // Gateway URL + GATEWAY_URL: isProduction() ? baseUrl : 'http://20.1.2.3', // 기능 플래그 FEATURES: { @@ -20,17 +64,17 @@ window.__runtime_config__ = { PUSH_NOTIFICATIONS: true, SOCIAL_LOGIN: false, MULTI_LANGUAGE: false, - API_HEALTH_CHECK: true, // ⚠️ 추가 + API_HEALTH_CHECK: true, }, // 환경 설정 - ENV: 'development', - DEBUG: true, + ENV: isProduction() ? 'production' : 'development', + DEBUG: !isProduction(), - // ⚠️ 추가: API 타임아웃 설정 + // API 타임아웃 설정 API_TIMEOUT: 30000, - // ⚠️ 추가: 재시도 설정 + // 재시도 설정 RETRY_ATTEMPTS: 3, RETRY_DELAY: 1000, @@ -38,7 +82,7 @@ window.__runtime_config__ = { VERSION: '1.0.0', BUILD_DATE: new Date().toISOString(), - // ⚠️ 추가: 백엔드 서비스 포트 정보 (디버깅용) + // 백엔드 서비스 포트 정보 (디버깅용) BACKEND_PORTS: { AUTH: 8081, STORE: 8082, @@ -47,7 +91,7 @@ window.__runtime_config__ = { } }; -// ⚠️ 추가: 설정 검증 함수 +// 설정 검증 함수 const validateConfig = () => { const config = window.__runtime_config__; const requiredUrls = ['AUTH_URL', 'STORE_URL', 'SALES_URL', 'RECOMMEND_URL']; @@ -63,8 +107,13 @@ const validateConfig = () => { return true; }; -// ⚠️ 추가: 개발 환경에서만 상세 로깅 +// 환경별 상세 로깅 if (window.__runtime_config__.DEBUG) { + console.log('=== 현재 환경 정보 ==='); + console.log('🌍 Environment:', window.__runtime_config__.ENV); + console.log('🏠 Hostname:', window.location.hostname); + console.log('🔧 Is Production:', isProduction()); + console.log('=== 백엔드 API URLs ==='); console.log('🔐 AUTH_URL:', window.__runtime_config__.AUTH_URL); console.log('🏪 STORE_URL:', window.__runtime_config__.STORE_URL); @@ -74,12 +123,9 @@ if (window.__runtime_config__.DEBUG) { console.log('=== 설정 상세 정보 ==='); console.log('전체 설정:', window.__runtime_config__); - - // 설정 검증 실행 - validateConfig(); } -// ⚠️ 추가: 전역 설정 접근 함수 +// 전역 설정 접근 함수 window.getApiConfig = () => window.__runtime_config__; window.getApiUrl = (serviceName) => { const config = window.__runtime_config__; @@ -87,4 +133,7 @@ window.getApiUrl = (serviceName) => { return config[urlKey] || null; }; -console.log('✅ [RUNTIME] 런타임 설정 로드 완료'); \ No newline at end of file +// 설정 검증 실행 +validateConfig(); + +console.log(`✅ [RUNTIME] 런타임 설정 로드 완료 (${window.__runtime_config__.ENV} 환경)`); \ No newline at end of file