Merge pull request #141 from intel-fit/fix/kakaopay-url #271
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD Pipeline | |
| on: | |
| push: | |
| branches: | |
| - dev | |
| env: | |
| DOCKER_IMAGE: intelfit-backend | |
| EC2_HOST: 43.200.40.140 | |
| EC2_USER: ubuntu | |
| jobs: | |
| test: | |
| if : false | |
| runs-on: ubuntu-latest | |
| services: | |
| mysql: | |
| image: mysql:8.0 | |
| env: | |
| MYSQL_ROOT_PASSWORD: Admin123! | |
| MYSQL_DATABASE: intelfit | |
| ports: | |
| - 3306:3306 | |
| options: >- | |
| --health-cmd="mysqladmin ping" | |
| --health-interval=10s | |
| --health-timeout=5s | |
| --health-retries=3 | |
| redis: | |
| image: redis:7.2-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd="redis-cli ping" | |
| --health-interval=10s | |
| --health-timeout=5s | |
| --health-retries=3 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| cache: gradle | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| - name: Clean and refresh dependencies | |
| run: ./gradlew clean --refresh-dependencies --no-daemon | |
| - name: Run tests | |
| run: ./gradlew test --no-daemon | |
| env: | |
| SPRING_PROFILES_ACTIVE: test | |
| SPRING_DATASOURCE_URL: jdbc:mysql://localhost:3306/intelfit?serverTimezone=Asia/Seoul&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true | |
| SPRING_DATASOURCE_USERNAME: root | |
| SPRING_DATASOURCE_PASSWORD: Admin123! | |
| SPRING_DATA_REDIS_HOST: localhost | |
| SPRING_DATA_REDIS_PORT: 6379 | |
| # JWT | |
| JWT_SECRET: intelfit-jwt-secret-key-for-production-use-minimum-32-characters-long-2024 | |
| # 🔥 이메일 SMTP | |
| SPRING_MAIL_HOST: smtp.naver.com | |
| SPRING_MAIL_PORT: 587 | |
| SPRING_MAIL_USERNAME: ${{ secrets.SPRING_MAIL_USERNAME }} | |
| SPRING_MAIL_PASSWORD: ${{ secrets.SPRING_MAIL_PASSWORD }} | |
| # 🔥 AI 서버 더미 | |
| AI_SERVER_URL: http://localhost:8000 | |
| AI_SERVER_API_KEY: dummy | |
| # OCR Mock | |
| AWS_S3_BUCKET: dummy | |
| AWS_S3_BASE_URL: http://localhost | |
| GEMINI_API_KEY: dummy | |
| # Stripe Dummy (Fully Mock) | |
| STRIPE_PUBLISHABLE_KEY: dummy | |
| STRIPE_SECRET_KEY: dummy | |
| STRIPE_PRICE_ID_MONTHLY: dummy | |
| STRIPE_PRICE_ID_ANNUAL: dummy | |
| STRIPE_WEBHOOK_SECRET: dummy | |
| STRIPE_SUCCESS_URL: http://localhost | |
| STRIPE_CANCEL_URL: http://localhost | |
| # KakaoPay Dummy (Fully Mock) | |
| KAKAOPAY_ADMIN_KEY: dummy | |
| KAKAOPAY_REST_API_KEY: dummy | |
| KAKAOPAY_CID: TC0ONETIME | |
| KAKAOPAY_PLAN_MONTHLY_AMOUNT: 5900 | |
| KAKAOPAY_PLAN_ANNUAL_AMOUNT: 59000 | |
| KAKAOPAY_PLAN_MONTHLY_ITEM_NAME: "test-monthly" | |
| KAKAOPAY_PLAN_ANNUAL_ITEM_NAME: "test-annual" | |
| KAKAOPAY_APPROVAL_URL: http://localhost | |
| KAKAOPAY_CANCEL_URL: http://localhost | |
| KAKAOPAY_FAIL_URL: http://localhost | |
| KAKAOPAY_PLAN_MONTHLY_TAX_FREE: 0 | |
| KAKAOPAY_PLAN_ANNUAL_TAX_FREE: 0 | |
| # 카카오 로그인 Dummy | |
| KAKAO_CLIENT_ID: dummy | |
| KAKAO_CLIENT_SECRET: dummy | |
| build-and-deploy: | |
| # needs: test // 필요시, 주석 제거하기 | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| fetch-depth: 0 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build Docker image | |
| run: | | |
| echo "🔧 Building Docker image..." | |
| docker build --no-cache --pull -t ${{ env.DOCKER_IMAGE }}:${{ github.sha }} . | |
| docker tag ${{ env.DOCKER_IMAGE }}:${{ github.sha }} ${{ env.DOCKER_IMAGE }}:latest | |
| echo "Docker image built successfully" | |
| - name: Save Docker image | |
| run: | | |
| docker save ${{ env.DOCKER_IMAGE }}:latest | gzip > image.tar.gz | |
| ls -lh image.tar.gz | |
| - name: Create Nginx config file | |
| run: | | |
| mkdir -p docker/nginx | |
| cat > docker/nginx/nginx.conf << 'EOF' | |
| server { | |
| listen 80; | |
| server_name _; | |
| access_log /var/log/nginx/access.log; | |
| error_log /var/log/nginx/error.log; | |
| client_max_body_size 10M; | |
| location ^~ /.well-known/acme-challenge/ { | |
| root /var/www/certbot; | |
| default_type "text/plain"; | |
| try_files $uri =404; | |
| } | |
| location /health { | |
| access_log off; | |
| return 200 "OK"; | |
| add_header Content-Type text/plain; | |
| } | |
| # AI 서버 프록시 (HTTP) | |
| location /ai/ { | |
| if ($request_method = 'OPTIONS') { | |
| add_header 'Access-Control-Allow-Origin' '*'; | |
| add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS'; | |
| add_header 'Access-Control-Allow-Headers' '*'; | |
| add_header 'Access-Control-Max-Age' 1728000; | |
| return 204; | |
| } | |
| proxy_pass http://43.200.40.140:8000/; | |
| proxy_http_version 1.1; | |
| 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_read_timeout 300s; | |
| proxy_connect_timeout 60s; | |
| proxy_send_timeout 300s; | |
| } | |
| location / { | |
| if ($request_method = 'OPTIONS') { | |
| add_header 'Access-Control-Allow-Origin' '*'; | |
| add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS'; | |
| add_header 'Access-Control-Allow-Headers' '*'; | |
| add_header 'Access-Control-Max-Age' 1728000; | |
| return 204; | |
| } | |
| proxy_pass http://spring-app:8080; | |
| proxy_http_version 1.1; | |
| proxy_set_header Upgrade $http_upgrade; | |
| proxy_set_header Connection 'upgrade'; | |
| 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_cache_bypass $http_upgrade; | |
| } | |
| } | |
| # HTTPS 서버 (도메인 접근용) | |
| server { | |
| listen 443 ssl; | |
| server_name intelfits.com www.intelfits.com; | |
| ssl_certificate /etc/letsencrypt/live/intelfits.com/fullchain.pem; | |
| ssl_certificate_key /etc/letsencrypt/live/intelfits.com/privkey.pem; | |
| ssl_protocols TLSv1.2 TLSv1.3; | |
| ssl_prefer_server_ciphers off; | |
| access_log /var/log/nginx/access.log; | |
| error_log /var/log/nginx/error.log; | |
| client_max_body_size 10M; | |
| location /health { | |
| access_log off; | |
| return 200 "OK"; | |
| add_header Content-Type text/plain; | |
| } | |
| # AI 서버 프록시 (HTTPS) | |
| location /ai/ { | |
| if ($request_method = 'OPTIONS') { | |
| add_header 'Access-Control-Allow-Origin' '*'; | |
| add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS'; | |
| add_header 'Access-Control-Allow-Headers' '*'; | |
| add_header 'Access-Control-Max-Age' 1728000; | |
| return 204; | |
| } | |
| proxy_pass http://43.200.40.140:8000/; | |
| proxy_http_version 1.1; | |
| 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_read_timeout 300s; | |
| proxy_connect_timeout 60s; | |
| proxy_send_timeout 300s; | |
| } | |
| location / { | |
| if ($request_method = 'OPTIONS') { | |
| add_header 'Access-Control-Allow-Origin' '*'; | |
| add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS'; | |
| add_header 'Access-Control-Allow-Headers' '*'; | |
| add_header 'Access-Control-Max-Age' 1728000; | |
| return 204; | |
| } | |
| proxy_pass http://spring-app:8080; | |
| proxy_http_version 1.1; | |
| proxy_set_header Upgrade $http_upgrade; | |
| proxy_set_header Connection 'upgrade'; | |
| 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_cache_bypass $http_upgrade; | |
| } | |
| } | |
| EOF | |
| - name: Create deploy.sh | |
| run: | | |
| cat > deploy.sh << 'EOF' | |
| #!/bin/bash | |
| set -e | |
| echo "IntelFit 백엔드 배포 시작..." | |
| docker compose down --remove-orphans || true | |
| docker image prune -f || true | |
| docker compose up -d | |
| echo "서비스 초기화 중..." | |
| sleep 20 | |
| echo "Spring Boot 헬스체크..." | |
| if docker exec spring-app curl -sf http://localhost:8080/actuator/health > /dev/null 2>&1; then | |
| echo "Spring Boot 정상 작동" | |
| else | |
| echo "Spring Boot 헬스체크 실패" | |
| docker logs --tail 50 spring-app | |
| exit 1 | |
| fi | |
| echo "🌐 Nginx 리버스 프록시 확인..." | |
| if curl -sf http://localhost/health > /dev/null 2>&1; then | |
| echo "Nginx 프록시 정상 작동" | |
| else | |
| echo "Nginx 프록시 응답 없음" | |
| docker logs --tail 50 nginx | |
| exit 1 | |
| fi | |
| echo "배포 완료!" | |
| docker compose ps | |
| EOF | |
| chmod +x deploy.sh | |
| - name: Create .env file | |
| run: | | |
| cat > .env << EOF | |
| KAKAO_CLIENT_ID=${{ secrets.KAKAO_CLIENT_ID }} | |
| KAKAO_CLIENT_SECRET=${{ secrets.KAKAO_CLIENT_SECRET }} | |
| EOF | |
| - name: Copy files to EC2 | |
| uses: appleboy/scp-action@v0.1.7 | |
| with: | |
| host: ${{ env.EC2_HOST }} | |
| username: ${{ env.EC2_USER }} | |
| key: ${{ secrets.EC2_PRIVATE_KEY }} | |
| source: "image.tar.gz,compose.yaml,deploy.sh,docker/nginx/nginx.conf,docker/mysql/exerciseCategoryDataBase.sql,.env" | |
| target: "/home/ubuntu/back-end" | |
| overwrite: true | |
| - name: Deploy to EC2 | |
| uses: appleboy/ssh-action@v1.0.3 | |
| with: | |
| host: ${{ env.EC2_HOST }} | |
| username: ${{ env.EC2_USER }} | |
| key: ${{ secrets.EC2_PRIVATE_KEY }} | |
| script: | | |
| cd /home/ubuntu/back-end | |
| echo "📦 Docker 이미지 로드..." | |
| docker load < image.tar.gz | |
| rm image.tar.gz | |
| ./deploy.sh |