Skip to content

CI/CD Pipeline for Microservices (Ultra-Fast) #145

CI/CD Pipeline for Microservices (Ultra-Fast)

CI/CD Pipeline for Microservices (Ultra-Fast) #145

Workflow file for this run

name: CI/CD Pipeline for Microservices (Ultra-Fast)
on:
pull_request:
branches:
- main
workflow_dispatch:
permissions:
contents: read
security-events: write
env:
DOCKER_REGISTRY: softbank2025
IMAGE_TAG: ${{ github.sha }}
jobs:
build-jars:
name: Build All JARs
runs-on: ubuntu-latest
outputs:
services: ${{ steps.set-matrix.outputs.services }}
has-changes: ${{ steps.set-matrix.outputs.has-changes }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: 'gradle'
- name: Detect changed files
id: changes
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "서비스 전체 배포 (수동 실행)"
echo "server=true" >> $GITHUB_OUTPUT
echo "gateway=true" >> $GITHUB_OUTPUT
echo "fe=true" >> $GITHUB_OUTPUT
echo "deploy=true" >> $GITHUB_OUTPUT
echo "user=true" >> $GITHUB_OUTPUT
elif [ "${{ github.event_name }}" == "pull_request" ]; then
CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }})
echo "Changed files:"
echo "$CHANGED_FILES"
if echo "$CHANGED_FILES" | grep -qE "^server/|^build.gradle$|^settings.gradle$"; then
echo "server=true" >> $GITHUB_OUTPUT
else
echo "server=false" >> $GITHUB_OUTPUT
fi
if echo "$CHANGED_FILES" | grep -qE "^gateway/|^build.gradle$|^settings.gradle$"; then
echo "gateway=true" >> $GITHUB_OUTPUT
else
echo "gateway=false" >> $GITHUB_OUTPUT
fi
if echo "$CHANGED_FILES" | grep -qE "^fe/|^build.gradle$|^settings.gradle$"; then
echo "fe=true" >> $GITHUB_OUTPUT
else
echo "fe=false" >> $GITHUB_OUTPUT
fi
if echo "$CHANGED_FILES" | grep -qE "^deploy/|^build.gradle$|^settings.gradle$"; then
echo "deploy=true" >> $GITHUB_OUTPUT
else
echo "deploy=false" >> $GITHUB_OUTPUT
fi
if echo "$CHANGED_FILES" | grep -qE "^user/|^build.gradle$|^settings.gradle$"; then
echo "user=true" >> $GITHUB_OUTPUT
else
echo "user=false" >> $GITHUB_OUTPUT
fi
else
echo "메인 브랜치 푸시 - 전체 배포"
echo "server=true" >> $GITHUB_OUTPUT
echo "gateway=true" >> $GITHUB_OUTPUT
echo "fe=true" >> $GITHUB_OUTPUT
echo "deploy=true" >> $GITHUB_OUTPUT
echo "user=true" >> $GITHUB_OUTPUT
fi
- name: Set matrix services
id: set-matrix
run: |
CHANGED_SERVICES=""
if [ "${{ steps.changes.outputs.server }}" == "true" ]; then
CHANGED_SERVICES="${CHANGED_SERVICES}\"server\","
fi
if [ "${{ steps.changes.outputs.gateway }}" == "true" ]; then
CHANGED_SERVICES="${CHANGED_SERVICES}\"gateway\","
fi
if [ "${{ steps.changes.outputs.fe }}" == "true" ]; then
CHANGED_SERVICES="${CHANGED_SERVICES}\"fe\","
fi
if [ "${{ steps.changes.outputs.deploy }}" == "true" ]; then
CHANGED_SERVICES="${CHANGED_SERVICES}\"deploy\","
fi
if [ "${{ steps.changes.outputs.user }}" == "true" ]; then
CHANGED_SERVICES="${CHANGED_SERVICES}\"user\","
fi
CHANGED_SERVICES=$(echo $CHANGED_SERVICES | sed 's/,$//')
if [ -z "$CHANGED_SERVICES" ]; then
echo "services=[]" >> $GITHUB_OUTPUT
echo "has-changes=false" >> $GITHUB_OUTPUT
echo "⚠️ 변경된 서비스 없음"
else
echo "services=[$CHANGED_SERVICES]" >> $GITHUB_OUTPUT
echo "has-changes=true" >> $GITHUB_OUTPUT
echo "✅ 변경된 서비스: [$CHANGED_SERVICES]"
fi
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Build all services (parallel)
run: |
echo "🔨 Building all services in parallel..."
./gradlew build -x test --parallel --max-workers=5 --build-cache
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: |
server/build/libs/*.jar
gateway/build/libs/*.jar
fe/build/libs/*.jar
deploy/build/libs/*.jar
user/build/libs/*.jar
retention-days: 1
docker-build-scan-push:
name: Docker Build & Push
needs: build-jars
runs-on: ubuntu-latest
if: needs.build-jars.outputs.has-changes == 'true'
strategy:
matrix:
service: ${{ fromJson(needs.build-jars.outputs.services) }}
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-artifacts
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Create optimized Dockerfile
run: |
cat > ${{ matrix.service }}/Dockerfile.fast << 'EOF'
FROM eclipse-temurin:17-jre-focal
WORKDIR /app
COPY ${{ matrix.service }}/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
EOF
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./${{ matrix.service }}/Dockerfile.fast
push: true
tags: |
${{ env.DOCKER_REGISTRY }}/${{ matrix.service }}:${{ env.IMAGE_TAG }}
${{ env.DOCKER_REGISTRY }}/${{ matrix.service }}:latest
cache-from: |
type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ matrix.service }}:buildcache
type=gha
cache-to: |
type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ matrix.service }}:buildcache,mode=max
type=gha,mode=max
- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
continue-on-error: true
with:
image-ref: ${{ env.DOCKER_REGISTRY }}/${{ matrix.service }}:${{ env.IMAGE_TAG }}
format: 'sarif'
output: 'trivy-results-${{ matrix.service }}.sarif'
severity: 'CRITICAL,HIGH'
timeout: '5m0s'
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@v3
if: always()
continue-on-error: true
with:
sarif_file: 'trivy-results-${{ matrix.service }}.sarif'
category: 'trivy-${{ matrix.service }}'
deploy-to-ec2:
name: Deploy to EC2
needs: [build-jars, docker-build-scan-push]
runs-on: ubuntu-latest
if: (github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/heads/feat/')) && needs.build-jars.outputs.has-changes == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Prepare changed services list
id: services
run: |
SERVICES=$(echo '${{ needs.build-jars.outputs.services }}' | jq -r '.[]' | tr '\n' ' ')
echo "list=$SERVICES" >> $GITHUB_OUTPUT
echo "📦 Services to deploy: $SERVICES"
- name: Deploy to EC2 via AWS SSM
id: deploy
run: |
SERVICES="${{ steps.services.outputs.list }}"
cat > deploy.sh << 'DEPLOY_EOF'
#!/bin/bash
set -e
SERVICES="$CHANGED_SERVICES"
IMAGE_TAG="$GITHUB_SHA"
echo "📂 Navigating to project directory..."
cd /home/ubuntu/Raspberry
echo "🔧 Changing file ownership..."
sudo chown -R $USER:$USER .
echo "🔄 Pulling latest code..."
git fetch origin
git reset --hard $GITHUB_SHA
# 스왑 체크 (간소화)
if ! sudo swapon --show | grep -q "/swapfile" && [ ! -f /swapfile ]; then
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab
fi
echo "🐳 Docker login..."
echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
sudo chmod 666 /var/run/docker.sock
echo "🔧 Updating docker-compose.yml..."
cp docker-compose.yml docker-compose.yml.backup
for service in $SERVICES; do
sed -i "s|image: softbank2025/${service}:.*|image: softbank2025/${service}:${IMAGE_TAG}|g" docker-compose.yml
done
sed -i '/build:/,+2d' docker-compose.yml
echo "📥 Pulling images (parallel)..."
for service in $SERVICES; do
docker pull softbank2025/${service}:${IMAGE_TAG} &
done
wait
echo "🛑 Stopping and removing old services..."
docker-compose stop $SERVICES || true
docker-compose rm -f $SERVICES || true
echo "🚀 Creating new services..."
docker-compose up -d --no-deps $SERVICES
echo "📊 Ensuring monitoring services are running..."
docker-compose up -d prometheus grafana
echo "⏳ Waiting 15s..."
sleep 15
docker image prune -a -f || true
echo "✅ Deployment completed!"
docker-compose ps
DEPLOY_EOF
ENCODED_SCRIPT=$(cat deploy.sh | base64 -w 0)
COMMAND_ID=$(aws ssm send-command \
--instance-ids "${{ secrets.EC2_INSTANCE_ID }}" \
--document-name "AWS-RunShellScript" \
--parameters "commands=[\"echo $ENCODED_SCRIPT | base64 -d > /tmp/deploy.sh && chmod +x /tmp/deploy.sh && DOCKERHUB_PASSWORD='${{ secrets.DOCKERHUB_PASSWORD }}' DOCKERHUB_USERNAME='${{ secrets.DOCKERHUB_USERNAME }}' GITHUB_SHA='${{ github.sha }}' CHANGED_SERVICES='$SERVICES' bash /tmp/deploy.sh\"]" \
--timeout-seconds 300 \
--output text \
--query 'Command.CommandId')
echo "command_id=$COMMAND_ID" >> $GITHUB_OUTPUT
echo "✅ SSM Command ID: $COMMAND_ID"
- name: Wait for deployment
run: |
COMMAND_ID="${{ steps.deploy.outputs.command_id }}"
for i in {1..30}; do
STATUS=$(aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "${{ secrets.EC2_INSTANCE_ID }}" \
--query 'Status' \
--output text 2>/dev/null || echo "Pending")
echo "[$i/30] Status: $STATUS"
if [ "$STATUS" = "Success" ]; then
echo "✅ Deployment completed!"
aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "${{ secrets.EC2_INSTANCE_ID }}" \
--query 'StandardOutputContent' \
--output text
break
elif [ "$STATUS" = "Failed" ] || [ "$STATUS" = "TimedOut" ]; then
echo "❌ Deployment failed: $STATUS"
aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "${{ secrets.EC2_INSTANCE_ID }}" \
--query 'StandardErrorContent' \
--output text
exit 1
fi
sleep 5
done
- name: Quick health check
run: |
cat > verify.sh << 'VERIFY_EOF'
#!/bin/bash
cd /home/ubuntu/Raspberry
echo "🔍 Health check..."
if curl -sf http://localhost:8761 > /dev/null 2>&1; then
echo "✅ Eureka OK"
else
echo "❌ Eureka DOWN"
exit 1
fi
if curl -sf http://localhost:8080/actuator/health > /dev/null 2>&1; then
echo "✅ Gateway OK"
fi
docker-compose ps --format "table {{.Service}}\t{{.Status}}"
VERIFY_EOF
ENCODED_SCRIPT=$(cat verify.sh | base64 -w 0)
VERIFY_CMD=$(aws ssm send-command \
--instance-ids "${{ secrets.EC2_INSTANCE_ID }}" \
--document-name "AWS-RunShellScript" \
--parameters "commands=[\"echo $ENCODED_SCRIPT | base64 -d | bash\"]" \
--timeout-seconds 120 \
--output text \
--query 'Command.CommandId')
sleep 8
aws ssm get-command-invocation \
--command-id "$VERIFY_CMD" \
--instance-id "${{ secrets.EC2_INSTANCE_ID }}" \
--query 'StandardOutputContent' \
--output text
- name: Summary
if: always()
run: |
if [ "${{ job.status }}" == "success" ]; then
echo "✅ Deployment successful!"
echo "🔖 Image tag: ${{ github.sha }}"
echo "📦 Services: ${{ steps.services.outputs.list }}"
else
echo "❌ Deployment failed!"
exit 1
fi