1- name : CD - Deploy to Server via SSH
1+ name : CD - Deploy to GCE Instances
22
33on :
4- workflow_dispatch : # ✅ 수동 실행 지원
5- workflow_run :
6- workflows : ["CI - Upload Compose & Traefik Files"] # CI 워크플로우 이름
7- types :
8- - completed
4+ repository_dispatch :
5+ types : [deploy-backend]
6+ workflow_dispatch :
7+ inputs :
8+ image_tag :
9+ description : " Docker image tag to deploy (default: latest)"
10+ required : false
11+ default : " latest"
12+ target :
13+ description : " Target instance (all/app/ocr/alert)"
14+ required : false
15+ default : " all"
16+
17+ concurrency :
18+ group : cd-deploy
19+ cancel-in-progress : false # 배포 중 취소 방지
920
1021jobs :
1122 deploy :
12- if : ${{ github.event.workflow_run.conclusion == 'success' }}
23+ name : Deploy ${{ matrix.instance.name }}
1324 runs-on : ubuntu-latest
1425 environment : production
1526
27+ strategy :
28+ max-parallel : 1
29+ fail-fast : false
30+ matrix :
31+ instance :
32+ - { name: app, compose: docker-compose.app.yml }
33+ - { name: ocr, compose: docker-compose.ocr.yml }
34+ - { name: alert, compose: docker-compose.alert.yml }
35+
1636 steps :
17- - name : Checkout repository
18- uses : actions/checkout@v3
37+ - name : Check target filter
38+ id : check
39+ run : |
40+ TARGET="${{ github.event.inputs.target || 'all' }}"
41+ CURRENT="${{ matrix.instance.name }}"
42+ if [ "$TARGET" != "all" ] && [ "$TARGET" != "$CURRENT" ]; then
43+ echo "skip=true" >> $GITHUB_OUTPUT
44+ echo "⏭️ Skipping $CURRENT (target: $TARGET)"
45+ else
46+ echo "skip=false" >> $GITHUB_OUTPUT
47+ fi
1948
20- - name : Connect & Deploy via SSH
21- uses : appleboy/ssh-action@v1.0.3
49+ - uses : actions/checkout@v4
50+ if : steps.check.outputs.skip != 'true'
51+
52+ - name : Authenticate to Google Cloud
53+ if : steps.check.outputs.skip != 'true'
54+ uses : google-github-actions/auth@v2
2255 with :
23- host : ${{ secrets.SERVER_HOST }}
24- username : ${{ secrets.SERVER_USER }}
25- key : ${{ secrets.SERVER_PEM_KEY }}
26- script : |
27- export SECRET_KEY="${{ secrets.SECRET_KEY }}"
28- export DJANGO_SETTINGS_MODULE="${{ secrets.DJANGO_SETTINGS_MODULE }}"
29-
30- export MYSQL_USER="${{ secrets.MYSQL_USER }}"
31- export MYSQL_PASSWORD="${{ secrets.MYSQL_PASSWORD }}"
32- export MYSQL_DATABASE="${{ secrets.MYSQL_DATABASE }}"
33- export MYSQL_ROOT_PASSWORD="${{ secrets.MYSQL_ROOT_PASSWORD }}"
34-
35- export AWS_ACCESS_KEY="${{ secrets.AWS_ACCESS_KEY }}"
36- export AWS_SECRET_KEY="${{ secrets.AWS_SECRET_KEY }}"
37- export AWS_S3_BUCKET_NAME="${{ secrets.AWS_S3_BUCKET_NAME }}"
38- export AWS_S3_REGION="${{ secrets.AWS_S3_REGION }}"
39- export GOOGLE_APPLICATION_CREDENTIALS="${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}"
40-
41- export RABBITMQ_USER="${{ secrets.RABBITMQ_USER }}"
42- export RABBITMQ_PASSWORD="${{ secrets.RABBITMQ_PASSWORD }}"
43- export DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME }}"
44- export DOCKER_IMAGE_NAME="${{ secrets.DOCKER_IMAGE_NAME }}"
45- export DOCKER_CELERY_NAME="${{ secrets.DOCKER_CELERY_NAME }}"
46-
47- export TRAEFIK_DASHBOARD_AUTH="${{ secrets.TRAEFIK_DASHBOARD_AUTH }}"
56+ credentials_json : ${{ secrets.GCP_SA_KEY }}
57+
58+ - name : Set up Cloud SDK
59+ if : steps.check.outputs.skip != 'true'
60+ uses : google-github-actions/setup-gcloud@v2
61+
62+ - name : Get instance name
63+ if : steps.check.outputs.skip != 'true'
64+ id : instance
65+ run : |
66+ ROLE="${{ matrix.instance.name }}"
67+ case "$ROLE" in
68+ app) INSTANCE="${{ secrets.APP_INSTANCE }}" ;;
69+ ocr) INSTANCE="${{ secrets.OCR_INSTANCE }}" ;;
70+ alert) INSTANCE="${{ secrets.ALERT_INSTANCE }}" ;;
71+ esac
72+ echo "name=$INSTANCE" >> $GITHUB_OUTPUT
73+ echo "🎯 Deploying to $INSTANCE"
74+
75+ - name : Sync deploy configs
76+ if : steps.check.outputs.skip != 'true'
77+ run : |
78+ gcloud compute scp --recurse \
79+ ./compose ./config ./env ./scripts \
80+ ${{ steps.instance.outputs.name }}:~/depoly/ \
81+ --zone=${{ secrets.GCE_ZONE }} \
82+ --quiet
83+
84+ - name : Deploy via SSH
85+ if : steps.check.outputs.skip != 'true'
86+ run : |
87+ gcloud compute ssh ${{ steps.instance.outputs.name }} \
88+ --zone=${{ secrets.GCE_ZONE }} \
89+ --quiet \
90+ --command="
91+ cd ~/depoly &&
92+ gcloud auth configure-docker ${{ secrets.AR_REGION }}-docker.pkg.dev --quiet &&
93+ source env/hosts.env &&
94+ docker compose -f compose/${{ matrix.instance.compose }} pull &&
95+ docker compose -f compose/${{ matrix.instance.compose }} up -d &&
96+ echo '=== Container Status ===' &&
97+ docker compose -f compose/${{ matrix.instance.compose }} ps
98+ "
4899
49- cd /home/ubuntu/app
50-
51- docker compose \
52- -f docker-compose.backend.yml \
53- -f docker-compose.portainer.yml \
54- -f docker-compose.traefik.yml \
55- pull
56-
57- docker compose \
58- -f docker-compose.backend.yml \
59- -f docker-compose.portainer.yml \
60- -f docker-compose.traefik.yml \
61- down
62-
63- docker image prune -f
64- docker volume prune -f
65-
66- docker compose \
67- -f docker-compose.backend.yml \
68- -f docker-compose.portainer.yml \
69- -f docker-compose.traefik.yml \
70- up -d --build
100+ - name : Health check
101+ if : steps.check.outputs.skip != 'true'
102+ run : |
103+ echo "⏳ Waiting 15s for services to start..."
104+ sleep 15
105+ gcloud compute ssh ${{ steps.instance.outputs.name }} \
106+ --zone=${{ secrets.GCE_ZONE }} \
107+ --quiet \
108+ --command="docker compose -f ~/depoly/compose/${{ matrix.instance.compose }} ps --format 'table {{.Name}}\t{{.Status}}'"
0 commit comments