-
Notifications
You must be signed in to change notification settings - Fork 0
233 lines (207 loc) · 10.1 KB
/
Copy pathcd-dev.yml
File metadata and controls
233 lines (207 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
name: CD Dev
on:
push:
branches:
- develop
workflow_dispatch:
permissions:
contents: read
concurrency:
group: cd-dev-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/zipzip-be
jobs:
build-and-push:
name: Build & push dev image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
# private 서브모듈(src/main/resources/config)이 체크아웃되지 않은 환경(CI/CD)에서도
# non-optional classpath import가 깨지지 않도록 placeholder 값만 채운다.
# 실제 dev DB 접속정보는 컨테이너 실행 시 SPRING_DATASOURCE_* 환경변수로 주입되어
# 이 값을 덮어쓰므로(Spring 프로퍼티 우선순위상 env var가 우선), public 이미지에 포함되어도 안전하다.
- name: Create build-time placeholder secret
run: |
mkdir -p src/main/resources/config
cat > src/main/resources/config/application-secret.yml <<'YAML'
spring:
application:
name: zipzip-server
datasource:
url: jdbc:postgresql://localhost:5432/placeholder
username: placeholder
password: placeholder
driver-class-name: org.postgresql.Driver
jwt:
issuer: zipzip-server-placeholder
access-secret: placeholder-access-secret-must-be-at-least-32-characters
refresh-secret: placeholder-refresh-secret-must-be-at-least-32-characters
access-token-expiration: 30m
refresh-token-expiration: 14d
apple:
team-id: placeholder-apple-team-id
client-id: placeholder-apple-client-id
key-id: placeholder-apple-key-id
private-key: placeholder-apple-private-key
idempotency:
auth-response-encryption-key: placeholder-idempotency-secret-must-be-at-least-32-characters
auth-response-ttl: 10m
storage:
endpoint: https://placeholder.compat.objectstorage.placeholder.oraclecloud.com
region: placeholder-region
bucket: placeholder-bucket
access-key: placeholder-access-key
secret-key: placeholder-secret-key
YAML
# JAR은 러너 네이티브(x86) 아키텍처로 빌드한다. 바이트코드는 아키텍처 무관이라
# 여기서 굳이 arm64를 QEMU로 에뮬레이션하며 컴파일할 필요가 없다(느림).
- name: Build JAR
run: |
chmod +x gradlew
./gradlew --no-daemon clean bootJar -x test
mkdir -p app
cp build/libs/*.jar app/app.jar
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# 인스턴스가 ARM64(Ampere A1)뿐이라 arm64 단일 아키텍처만 빌드한다.
# 이 단계는 이미 빌드된 JAR을 arm64 JRE 이미지에 얹기만 하므로(COPY/chown)
# QEMU 에뮬레이션이어도 빠르다 — 무거운 컴파일은 위에서 네이티브로 이미 끝남.
# 태그는 :dev, :dev-{sha} — prod(:latest, :{sha})와 절대 겹치지 않게 분리.
- name: Build and push dev image (linux/arm64)
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64
push: true
tags: |
${{ env.IMAGE_NAME }}:dev
${{ env.IMAGE_NAME }}:dev-${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
name: Deploy to zipzip-be (dev)
needs: build-and-push
runs-on: ubuntu-latest
env:
SSH_USER: ${{ secrets.SSH_USER }}
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_PRIVATE_KEY_DEV: ${{ secrets.SSH_PRIVATE_KEY_DEV }}
GIT_SHA: ${{ github.sha }}
DOCKERHUB_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/zipzip-be
DB_URL: ${{ secrets.SPRING_DATASOURCE_URL_DEV }}
DB_USERNAME: ${{ secrets.SPRING_DATASOURCE_USERNAME_DEV }}
DB_PASSWORD: ${{ secrets.SPRING_DATASOURCE_PASSWORD_DEV }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID_DEV }}
APPLE_CLIENT_ID: ${{ secrets.APPLE_CLIENT_ID_DEV }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID_DEV }}
APPLE_PRIVATE_KEY: ${{ secrets.APPLE_PRIVATE_KEY_DEV }}
JWT_ISSUER: ${{ secrets.JWT_ISSUER_DEV }}
JWT_ACCESS_SECRET: ${{ secrets.JWT_ACCESS_SECRET_DEV }}
JWT_REFRESH_SECRET: ${{ secrets.JWT_REFRESH_SECRET_DEV }}
JWT_ACCESS_TOKEN_EXPIRATION: ${{ secrets.JWT_ACCESS_TOKEN_EXPIRATION_DEV }}
JWT_REFRESH_TOKEN_EXPIRATION: ${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_DEV }}
IDEMPOTENCY_AUTH_RESPONSE_ENCRYPTION_KEY: ${{ secrets.IDEMPOTENCY_AUTH_RESPONSE_ENCRYPTION_KEY_DEV }}
STORAGE_ENDPOINT: ${{ secrets.STORAGE_ENDPOINT_DEV }}
STORAGE_REGION: ${{ secrets.STORAGE_REGION_DEV }}
STORAGE_BUCKET: ${{ secrets.STORAGE_BUCKET_DEV }}
STORAGE_ACCESS_KEY: ${{ secrets.STORAGE_ACCESS_KEY_DEV }}
STORAGE_SECRET_KEY: ${{ secrets.STORAGE_SECRET_KEY_DEV }}
steps:
- name: Validate deployment payload
shell: bash
run: |
missing=0
payload="$RUNNER_TEMP/zipzip-deployment.env"
: > "$payload"
chmod 600 "$payload"
require_non_empty() {
local name="$1"
local value="$2"
if [ -z "$value" ]; then
echo "::error::$name is required for dev deployment."
missing=1
fi
}
append_required_payload() {
local key="$1"
local value="$2"
if [ -z "$value" ]; then
echo "::error::$key is required for dev deployment."
missing=1
return
fi
if [[ "$value" == *$'\n'* ]]; then
echo "::error::$key must be stored as a single-line value for env_file delivery."
missing=1
return
fi
printf '%s=%s\n' "$key" "$value" >> "$payload"
}
require_non_empty SSH_USER "$SSH_USER"
require_non_empty SSH_HOST "$SSH_HOST"
require_non_empty SSH_PRIVATE_KEY_DEV "$SSH_PRIVATE_KEY_DEV"
if [[ "$DOCKERHUB_IMAGE" == /* ]]; then
echo "::error::DOCKERHUB_USERNAME is required for dev deployment."
missing=1
fi
append_required_payload GIT_SHA "$GIT_SHA"
append_required_payload DOCKERHUB_IMAGE "$DOCKERHUB_IMAGE"
append_required_payload SPRING_PROFILES_ACTIVE "dev"
append_required_payload SPRING_DATASOURCE_URL "$DB_URL"
append_required_payload SPRING_DATASOURCE_USERNAME "$DB_USERNAME"
append_required_payload SPRING_DATASOURCE_PASSWORD "$DB_PASSWORD"
append_required_payload APPLE_TEAM_ID "$APPLE_TEAM_ID"
append_required_payload APPLE_CLIENT_ID "$APPLE_CLIENT_ID"
append_required_payload APPLE_KEY_ID "$APPLE_KEY_ID"
append_required_payload APPLE_PRIVATE_KEY "$APPLE_PRIVATE_KEY"
append_required_payload JWT_ISSUER "$JWT_ISSUER"
append_required_payload JWT_ACCESS_SECRET "$JWT_ACCESS_SECRET"
append_required_payload JWT_REFRESH_SECRET "$JWT_REFRESH_SECRET"
append_required_payload JWT_ACCESS_TOKEN_EXPIRATION "$JWT_ACCESS_TOKEN_EXPIRATION"
append_required_payload JWT_REFRESH_TOKEN_EXPIRATION "$JWT_REFRESH_TOKEN_EXPIRATION"
append_required_payload IDEMPOTENCY_AUTH_RESPONSE_ENCRYPTION_KEY "$IDEMPOTENCY_AUTH_RESPONSE_ENCRYPTION_KEY"
append_required_payload STORAGE_ENDPOINT "$STORAGE_ENDPOINT"
append_required_payload STORAGE_REGION "$STORAGE_REGION"
append_required_payload STORAGE_BUCKET "$STORAGE_BUCKET"
append_required_payload STORAGE_ACCESS_KEY "$STORAGE_ACCESS_KEY"
append_required_payload STORAGE_SECRET_KEY "$STORAGE_SECRET_KEY"
exit "$missing"
# 시크릿은 run: 스크립트 문자열에 직접 넣지 않고 env:로만 전달한다(스크립트 인젝션 방지, cd.yml과 동일한 이유).
- name: Configure SSH
run: |
mkdir -p ~/.ssh
umask 077
printf '%s\n' "$SSH_PRIVATE_KEY_DEV" > ~/.ssh/deploy_key_dev
chmod 600 ~/.ssh/deploy_key_dev
ssh-keyscan -H "$SSH_HOST" >> ~/.ssh/known_hosts
# 이미지는 public이라 서버에서도 로그인 없이 pull 가능.
# 서버의 authorized_keys에 dev 전용 forced command(`~/zipzip-deploy-dev.sh`)가 걸려 있어서
# 실제로는 아래 커맨드 문자열('deploy')과 무관하게 그 스크립트만 실행된다.
# dev 런타임 환경변수는 GitHub Secrets(*_DEV)에 개별 등록해두고, stdin으로 흘려보내
# 서버 스크립트가 그걸 그대로 .env.dev로 기록한 뒤 배포하게 한다.
# .env.dev는 컨테이너 env_file이자 docker-compose.dev.yml의 ${DOCKERHUB_IMAGE} 치환용
# 파일을 겸하므로, 매 배포마다 통째로 덮어써도 없어지지 않도록 DOCKERHUB_IMAGE도 함께 보낸다.
# GIT_SHA도 함께 보내서 서버가 이 커밋 시점의 docker-compose.dev.yml, nginx/conf.d/dev-api.conf를
# raw.githubusercontent.com에서 직접 가져와 반영하게 한다(cd.yml과 동일한 이유 — stdin으로 파일
# 내용을 직접 보내면 배포키 유출 시 임의 compose 설정을 주입당할 위험이 있음).
# (prod와 SSH 키, 시크릿, 서버 스크립트, 컨테이너/서비스명 전부 별개)
- name: Deploy over SSH
run: |
ssh -i ~/.ssh/deploy_key_dev -o UserKnownHostsFile=~/.ssh/known_hosts \
"$SSH_USER@$SSH_HOST" 'deploy' < "$RUNNER_TEMP/zipzip-deployment.env"