Release: v1.0.0-beta.2 (#316) (#325) #120
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: CD (Deploy to GCP - Dev) | |
| on: | |
| push: | |
| branches: ['dev'] | |
| permissions: | |
| contents: read | |
| id-token: write | |
| env: | |
| DOCKER_IMAGE: asia-northeast3-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/folioo-docker/folioo-server | |
| concurrency: | |
| group: deploy-dev | |
| cancel-in-progress: true | |
| jobs: | |
| build-and-push: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v4 | |
| - name: Authenticate to Google Cloud | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| workload_identity_provider: ${{ secrets.WIF_PROVIDER }} | |
| service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }} | |
| - name: Set up Cloud SDK | |
| uses: google-github-actions/setup-gcloud@v2 | |
| with: | |
| project_id: ${{ secrets.GCP_PROJECT_ID }} | |
| - name: Authenticate to Artifact Registry | |
| run: gcloud auth configure-docker asia-northeast3-docker.pkg.dev | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build and Push to Artifact Registry | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| push: true | |
| tags: | | |
| ${{ env.DOCKER_IMAGE }}:dev | |
| ${{ env.DOCKER_IMAGE }}:${{ github.sha }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| deploy-dev: | |
| needs: build-and-push | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v4 | |
| - name: Set up Supabase CLI | |
| uses: supabase/setup-cli@v1 | |
| with: | |
| version: 2.75.0 | |
| - name: Apply Supabase migrations (dev) | |
| run: | | |
| if [ -z "${{ secrets.SUPABASE_DEV_DB_URL }}" ]; then | |
| echo "::error::Missing required secret SUPABASE_DEV_DB_URL" | |
| exit 1 | |
| fi | |
| DB_URL="${{ secrets.SUPABASE_DEV_DB_URL }}" | |
| if printf '%s' "$DB_URL" | grep -q ':6543'; then | |
| echo "::error::SUPABASE_DEV_DB_URL points to transaction pooler port 6543." | |
| echo "::error::Use a migration-safe connection string (direct DB or session-mode 5432)." | |
| exit 1 | |
| fi | |
| supabase db push --db-url "$DB_URL" | |
| - name: Authenticate to Google Cloud | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| workload_identity_provider: ${{ secrets.WIF_PROVIDER }} | |
| service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }} | |
| - name: Set up Cloud SDK | |
| uses: google-github-actions/setup-gcloud@v2 | |
| with: | |
| project_id: ${{ secrets.GCP_PROJECT_ID }} | |
| - name: Load Deploy Config from Terraform | |
| run: | | |
| gcloud storage cp gs://${{ secrets.TF_STATE_BUCKET }}/deploy-config.json /tmp/deploy-config.json | |
| echo "GCE_NAME=$(jq -r .dev_gce_name /tmp/deploy-config.json)" >> $GITHUB_ENV | |
| echo "GCE_ZONE=$(jq -r .dev_gce_zone /tmp/deploy-config.json)" >> $GITHUB_ENV | |
| - name: Refresh Environment Variables from Secret Manager | |
| run: | | |
| # 디렉토리 먼저 생성 (첫 배포 포함 모든 경우에 안전) | |
| gcloud compute ssh ${{ env.GCE_NAME }} \ | |
| --zone=${{ env.GCE_ZONE }} \ | |
| --project=${{ secrets.GCP_PROJECT_ID }} \ | |
| --tunnel-through-iap \ | |
| --quiet \ | |
| --command="sudo mkdir -p /home/folioo" | |
| gcloud secrets versions access latest \ | |
| --project='${{ secrets.GCP_PROJECT_ID }}' \ | |
| --secret='folioo-dev-config' \ | |
| | jq -r 'to_entries[] | "\(.key | ascii_upcase)=\(.value)"' \ | |
| > .env.dev | |
| gcloud compute scp .env.dev ${{ env.GCE_NAME }}:~/.env.dev \ | |
| --zone=${{ env.GCE_ZONE }} \ | |
| --project=${{ secrets.GCP_PROJECT_ID }} \ | |
| --tunnel-through-iap \ | |
| --quiet | |
| gcloud compute ssh ${{ env.GCE_NAME }} \ | |
| --zone=${{ env.GCE_ZONE }} \ | |
| --project=${{ secrets.GCP_PROJECT_ID }} \ | |
| --tunnel-through-iap \ | |
| --quiet \ | |
| --command=" | |
| sudo mv ~/.env.dev /home/folioo/.env.dev | |
| sudo chmod 600 /home/folioo/.env.dev | |
| " | |
| - name: Deploy to GCE via IAP (Blue-Green) | |
| run: | | |
| # B. compose 파일 전송 | |
| gcloud compute scp docker-compose.infra.yml docker-compose.dev.yml \ | |
| ${{ env.GCE_NAME }}:~ \ | |
| --zone=${{ env.GCE_ZONE }} \ | |
| --project=${{ secrets.GCP_PROJECT_ID }} \ | |
| --tunnel-through-iap \ | |
| --quiet | |
| # C. Blue-Green 배포 실행 | |
| gcloud compute ssh ${{ env.GCE_NAME }} \ | |
| --zone=${{ env.GCE_ZONE }} \ | |
| --project=${{ secrets.GCP_PROJECT_ID }} \ | |
| --tunnel-through-iap \ | |
| --quiet \ | |
| --command=" | |
| sudo mv ~/docker-compose.infra.yml /home/folioo/docker-compose.infra.yml | |
| sudo mv ~/docker-compose.dev.yml /home/folioo/docker-compose.dev.yml | |
| cd /home/folioo | |
| # jq 설치 확인 (없으면 설치) | |
| if ! command -v jq &>/dev/null; then | |
| sudo apt-get update -y -qq && sudo apt-get install -y -qq jq | |
| fi | |
| export DOCKER_IMAGE='${{ env.DOCKER_IMAGE }}' | |
| # GCE 메타데이터 서버로 Docker 인증 (gcloud 불필요) | |
| curl -sf 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token' \ | |
| -H 'Metadata-Flavor: Google' \ | |
| | python3 -c 'import sys, json; print(json.load(sys.stdin)[\"access_token\"])' \ | |
| | docker login -u oauth2accesstoken --password-stdin https://asia-northeast3-docker.pkg.dev | |
| DC='sudo -E docker compose --env-file .env.dev -f docker-compose.infra.yml -f docker-compose.dev.yml' | |
| sudo docker network create folioo-network || true | |
| # ── 현재 활성 슬롯 감지 ── | |
| ACTIVE_SLOT=\$(\$DC ps --format json 2>/dev/null | jq -sr '.[].Service' 2>/dev/null | grep -E 'dev-blue|dev-green' | head -1 || true) | |
| ACTIVE_SLOT=\${ACTIVE_SLOT:-none} | |
| if [[ \"\$ACTIVE_SLOT\" == 'dev-blue' ]]; then | |
| GREEN_SLOT='green' | |
| BLUE_SLOT='blue' | |
| else | |
| GREEN_SLOT='blue' | |
| BLUE_SLOT='green' | |
| fi | |
| echo \"Current: \$ACTIVE_SLOT / New slot: dev-\$GREEN_SLOT / Old: dev-\$BLUE_SLOT\" | |
| # ── 최초 배포: 베이스 인프라 시작 ── | |
| if [[ \"\$ACTIVE_SLOT\" == 'none' ]]; then | |
| echo 'First deploy: starting base infra' | |
| \$DC up -d traefik cloudflared | |
| sleep 10 | |
| fi | |
| \$DC --profile blue --profile green pull | |
| echo \"Starting green slot (dev-\$GREEN_SLOT)\" | |
| \$DC --profile \$GREEN_SLOT up -d --remove-orphans | |
| # ── Health Check ── | |
| GREEN_CONTAINER=\$(\$DC ps --format json 2>/dev/null | jq -sr \".[] | select(.Service == \\\"dev-\$GREEN_SLOT\\\") | .Name\") | |
| echo \"Health check starting... (container: \$GREEN_CONTAINER)\" | |
| for i in {1..36}; do | |
| STATUS=\$(sudo docker inspect --format='{{.State.Health.Status}}' \"\$GREEN_CONTAINER\" 2>/dev/null || echo 'not_found') | |
| if [[ \"\$STATUS\" == 'healthy' ]]; then | |
| echo \"✅ Green slot healthy! (\${i}th check)\" | |
| break | |
| fi | |
| if [[ \"\$STATUS\" == 'unhealthy' ]]; then | |
| echo '❌ Container unhealthy - rolling back' | |
| sudo docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' \"\$GREEN_CONTAINER\" 2>/dev/null || true | |
| sudo docker logs --tail 80 \"\$GREEN_CONTAINER\" 2>/dev/null || true | |
| sudo docker stop \"\$GREEN_CONTAINER\" 2>/dev/null && sudo docker rm -f \"\$GREEN_CONTAINER\" 2>/dev/null | |
| exit 1 | |
| fi | |
| if [ \$i -eq 36 ]; then | |
| echo '❌ Health check timeout (180s) - rolling back' | |
| sudo docker logs --tail 80 \"\$GREEN_CONTAINER\" 2>/dev/null || true | |
| sudo docker stop \"\$GREEN_CONTAINER\" 2>/dev/null && sudo docker rm -f \"\$GREEN_CONTAINER\" 2>/dev/null | |
| exit 1 | |
| fi | |
| echo \"Waiting... (\$i/36) status=\$STATUS\" | |
| sleep 5 | |
| done | |
| # ── 구 슬롯 종료 (인프라는 유지) ── | |
| if [[ \"\$ACTIVE_SLOT\" != 'none' ]]; then | |
| OLD_CONTAINER=\$(\$DC ps --format json 2>/dev/null | jq -sr \".[] | select(.Service == \\\"dev-\$BLUE_SLOT\\\") | .Name\") | |
| if [[ -n \"\$OLD_CONTAINER\" ]]; then | |
| echo \"Stopping old slot (\$OLD_CONTAINER)...\" | |
| sudo docker stop \"\$OLD_CONTAINER\" 2>/dev/null && sudo docker rm -f \"\$OLD_CONTAINER\" 2>/dev/null | |
| fi | |
| fi | |
| sudo docker image prune -f | |
| echo '✅ Dev deployment completed!' | |
| " |