-
Notifications
You must be signed in to change notification settings - Fork 5
359 lines (328 loc) · 17.3 KB
/
Copy pathdev-deploy.yml
File metadata and controls
359 lines (328 loc) · 17.3 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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
on:
push:
branches:
- env/dev
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
GCP_KEY_PATH: "/tmp/key.json"
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Run actionlint
shell: bash
run: |
set -ex
bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
./actionlint -color
- name: setup node
uses: actions/setup-node@v4
with:
node-version: "24.18.0"
- name: cache
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: nuxt setup
run: |
echo "${{ secrets.DEV_ENV }}" > .env
# ランキング取得のエッジキャッシュ用オリジン(公開URLなので Secret では
# なく Variable で管理する)。環境ごとにオリジンが異なるため
# ステージングは DEV_RANKING_BASE_URL を使う。未設定なら空行となり、
# フロントは process.env.RANKING_BASE_URL || process.env.API_URL の
# フォールバックで従来どおり関数直叩きになるため無害。
echo "RANKING_BASE_URL=${{ vars.DEV_RANKING_BASE_URL }}" >> .env
working-directory: ./web
- name: nuxt generate
run: |
yarn
yarn generate
working-directory: ./web
- name: copy files
run: |
rm -rf app/dist
cp -r web/dist app
working-directory: ./
- name: firebase setup
run: |
npm install -g firebase-tools
npm install
working-directory: ./app/functions
# deploy setup
- name: gcp_key file
run: |
set -ex
echo "${{ secrets.DEV_GCLOUD_SERVICE_KEY }}" | base64 -d > ${{ env.GCP_KEY_PATH }}
- uses: 'google-github-actions/auth@v2'
with:
credentials_json: '${{ secrets.DEV_GCLOUD_SERVICE_KEY }}'
- name: 'Use gcloud CLI'
run: 'gcloud auth list --filter=status:ACTIVE --format="value(account)"'
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v2'
with:
install_components: ''
project_id: 'd-shrine-dev'
- name: firebase deploy
run: |
firebase deploy --project d-shrine-dev --force
working-directory: ./app
- name: setup go
uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: go build & test (functions-go)
run: |
go build ./...
go vet ./...
working-directory: ./app/functions-go
# Firestoreエミュレータの実行にはJDK 21以上が必要(firebase-toolsの要件)。
# ubuntu-22.04ランナーのデフォルトJavaはこれを満たさないため明示的にセットアップする。
- name: setup java (for Firestore emulator)
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "21"
# sanpai/ranking/register/statusCacheBackfill等のFirestore統合テストは
# FIRESTORE_EMULATOR_HOST未設定時は自動スキップされる設計(README参照)。
# CIでも実際に検証されるよう、Firestoreエミュレータを起動した状態で
# go test を実行する(firebase emulators:exec が終了時に自動でエミュレータを
# 落とすため、後続ステップに影響しない)。
- name: go test (functions-go, with Firestore emulator)
run: |
firebase emulators:exec --project d-shrine-dev --only firestore \
'cd functions-go && go test ./...'
working-directory: ./app
# OGP画像生成(userOGPGo)のQC。サンプルOGPをPNG/WebPでレンダリングして
# アーティファクト化し、生成物を目視確認できるようにする(描画の破綻を
# デプロイ前に検知する目的も兼ねる)。
- name: generate OGP sample for QC
run: |
go test ./internal/ogpimage/ -run TestWriteQCArtifacts -count=1 -v
working-directory: ./app/functions-go
env:
OGP_QC_OUT: ${{ github.workspace }}/ogp-qc
- name: upload OGP sample artifact
uses: actions/upload-artifact@v4
with:
name: ogp-sample
path: ${{ github.workspace }}/ogp-qc/
if-no-files-found: error
# ここから Go版 Cloud Run functions のデプロイ。
# Gen2(Cloud Run)関数のデプロイは内部でCloud Buildのコンテナビルドを伴い
# 1本あたり数分かかる。関数間に依存が無いため、以前のように1本ずつ直列で
# deployすると全体で15〜30分規模になっていた。そこで前提処理(サービス有効化・
# Pub/Subトピック作成・認証情報取得・ロケーション解決)を先に済ませてから、
# 全関数のdeployをバックグラウンドで同時に起動して一括waitする
# (体感で最も遅い1本分の時間に短縮)。
# HTTP関数とスケジュール関数(Pub/Subトリガー)の両方に必要なGCPサービスを一括有効化。
- name: enable required GCP services
run: |
gcloud services enable \
cloudfunctions.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
pubsub.googleapis.com \
cloudscheduler.googleapis.com \
eventarc.googleapis.com \
--project=d-shrine-dev
# gen2(Cloud Run)関数のイメージ格納先であるArtifact Registryリポジトリ
# (デフォルト名 gcf-artifacts)を事前に作成しておく(冪等)。
# gcloudは通常このリポジトリを遅延自動解決するが、複数のdeployを並列実行すると
# その解決が競合し、一部が
# `gcloud crashed (AttributeError): 'NoneType' object has no attribute 'dockerRepository'`
# でクラッシュする(並列時のみ・ランダムな一部で発生する非決定的な競合)。
# 事前作成し、各deployで --docker-repository を明示指定することで自動解決の競合を無くす。
- name: ensure Artifact Registry repo for gen2 functions
run: |
gcloud artifacts repositories describe gcf-artifacts \
--location=us-central1 --project=d-shrine-dev \
|| gcloud artifacts repositories create gcf-artifacts \
--location=us-central1 --repository-format=docker --project=d-shrine-dev
# Cloud Functions(Gen2)の --trigger-topic は Gen1と異なりPub/Subトピックを
# 自動作成しない(存在しないトピックを指定すると
# `Validation failed for trigger ...: Resource not found` で即失敗する)。
# そのため各スケジュール関数用トピックを事前に作成しておく
# (既に存在すれば describe が成功するのでスキップする、冪等な実装)。
- name: create Pub/Sub topics for scheduled Go functions
run: |
for TOPIC in ranking-update-go ranking-cache-go status-cache-backfill-go scheduled-ogp-delete-go; do
gcloud pubsub topics describe "$TOPIC" --project=d-shrine-dev \
|| gcloud pubsub topics create "$TOPIC" --project=d-shrine-dev
done
# GitHub OAuth Appのclient_id/client_secretは、Node版が使う
# `firebase functions:config:get` (既にGCP側に設定済みの値)からそのまま取得し、
# 新規のGitHub Secretsは増やさない。ログに値が出ないよう ::add-mask:: で
# マスクしてから $GITHUB_ENV に渡す(sanpaiGoで使用)。
- name: fetch GitHub OAuth credentials for sanpaiGo
run: |
set -eo pipefail
CONFIG_JSON=$(firebase functions:config:get github --project d-shrine-dev)
CLIENT_ID=$(echo "$CONFIG_JSON" | jq -r '.client_id')
CLIENT_SECRET=$(echo "$CONFIG_JSON" | jq -r '.client_secret')
echo "::add-mask::$CLIENT_ID"
echo "::add-mask::$CLIENT_SECRET"
echo "SANPAI_GH_CLIENT_ID=$CLIENT_ID" >> "$GITHUB_ENV"
echo "SANPAI_GH_CLIENT_SECRET=$CLIENT_SECRET" >> "$GITHUB_ENV"
working-directory: ./app
# FUNC_BASE_URLはNode版が functions.config().func.base_url から読む値と同じもので、
# sanpaiGoのGitHub資格情報と同様に既存のFirebase Functions Configから取得する
# (ogpRewriteGoで使用)。
- name: fetch base_url for ogpRewriteGo
run: |
set -eo pipefail
CONFIG_JSON=$(firebase functions:config:get func --project d-shrine-dev)
BASE_URL=$(echo "$CONFIG_JSON" | jq -r '.base_url')
echo "OGP_FUNC_BASE_URL=$BASE_URL" >> "$GITHUB_ENV"
working-directory: ./app
# Cloud SchedulerジョブのlocationはプロジェクトのApp Engineアプリと
# 同一でなければならない(App Engineが存在するプロジェクトでは、それ以外の
# リージョンを指定すると `Location must equal <app-engine-location>` エラーになる)。
# `gcloud app describe` が返す locationId はレガシーなApp Engine表記
# (例: "us-central")の場合があり、Cloud Scheduler/Cloud Functions等の
# 通常のCloudリージョン表記(例: "us-central1")とは異なることがあるため、
# 既知の対応表で変換する。
- name: resolve App Engine location for Cloud Scheduler
run: |
set -eo pipefail
RAW_LOCATION=$(gcloud app describe --project=d-shrine-dev --format='value(locationId)')
case "$RAW_LOCATION" in
us-central) LOCATION=us-central1 ;;
europe-west) LOCATION=europe-west1 ;;
*) LOCATION="$RAW_LOCATION" ;;
esac
echo "resolved App Engine location: $RAW_LOCATION -> $LOCATION"
echo "APP_ENGINE_LOCATION=$LOCATION" >> "$GITHUB_ENV"
# 全Go関数のデプロイをバックグラウンドで同時起動し、まとめてwaitする。
# 依存関係(トピック/認証情報/ロケーション)は上の先行ステップで解決済み。
# timeout: HTTP関数は30s、スケジュール関数は全ユーザースキャン等で長くなり得るため
# 安全側に300sを明示。ログは関数ごとにファイルへ退避し、最後に順番に出力する
# (並行実行でログが混ざるのを防ぐ)。1本でも失敗したらステップを失敗させる。
- name: deploy all Go Cloud Run functions in parallel
working-directory: ./app/functions-go
run: |
set -uo pipefail
# 事前作成済みのAR docker リポジトリを明示指定し、並列deploy時の自動解決競合を防ぐ。
DOCKER_REPO="projects/d-shrine-dev/locations/us-central1/repositories/gcf-artifacts"
declare -a PIDS NAMES
deploy() {
local name="$1"; shift
local entry="$1"; shift
echo "start deploy: $name"
# 直前のデプロイrunのGCP側オペレーションが未解放だと 409
# (unable to queue the operation)で即失敗するため、90秒おいて
# 1回だけリトライする(マージが近接して連続デプロイになった場合の対策)。
( for attempt in 1 2; do
if gcloud functions deploy "$name" \
--project=d-shrine-dev \
--gen2 \
--runtime=go125 \
--region=us-central1 \
--docker-repository="$DOCKER_REPO" \
--source=. \
--entry-point="$entry" \
"$@"; then
exit 0
fi
if [ "$attempt" -eq 1 ]; then
echo "deploy $name failed (attempt 1); retrying in 90s..."
sleep 90
fi
done
exit 1 ) > "/tmp/deploy-$name.log" 2>&1 &
PIDS+=("$!")
NAMES+=("$name")
}
# HTTPトリガー関数
deploy statusGo StatusGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=30s
deploy sanpaiGo SanpaiGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=30s \
--set-env-vars="GITHUB_CLIENT_ID=$SANPAI_GH_CLIENT_ID,GITHUB_CLIENT_SECRET=$SANPAI_GH_CLIENT_SECRET,SANPAI_NEXT_TIME_SECONDS=60"
deploy registerGo RegisterGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=30s
deploy rankingGo RankingGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=30s
deploy sanpaiHistoryGo SanpaiHistoryGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=30s
deploy profileStatsGo ProfileStatsGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=30s
deploy badgeGo BadgeGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=30s
# GitHub APIのレート制限緩和のため sanpaiGo と同じOAuth資格情報を使う
deploy githubStatsGo GithubStatsGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=60s \
--set-env-vars="GITHUB_CLIENT_ID=$SANPAI_GH_CLIENT_ID,GITHUB_CLIENT_SECRET=$SANPAI_GH_CLIENT_SECRET"
# ピン留め保存(GitHubでリポジトリを検証するため同じ資格情報を使う)
deploy pinnedReposGo PinnedReposGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=30s \
--set-env-vars="GITHUB_CLIENT_ID=$SANPAI_GH_CLIENT_ID,GITHUB_CLIENT_SECRET=$SANPAI_GH_CLIENT_SECRET"
deploy omikujiGo OmikujiGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=30s \
--set-env-vars="OMIKUJI_COOLDOWN_SECONDS=60,KUDA_BASE_URL=https://kuda.kojiran.workers.dev,KUDA_API_KEY=${{ secrets.DEV_KUDA_API_KEY }}"
deploy ogpRewriteGo OgpRewriteGo \
--trigger-http --allow-unauthenticated --memory=256Mi --timeout=30s \
--set-env-vars="FUNC_BASE_URL=$OGP_FUNC_BASE_URL,OGP_PROJECT_ID=d-shrine-dev"
# userOGPGoは画像合成(base同梱)+WebPエンコード+アバター取得+GCSアップロードを
# 行うため、他HTTP関数より余裕を持たせる(512Mi/60s)。
deploy userOGPGo UserOGPGo \
--trigger-http --allow-unauthenticated --memory=512Mi --timeout=60s \
--set-env-vars="STORAGE_BUCKET_NAME=d-shrine-dev.appspot.com"
# Pub/Subトリガー(スケジュール)関数
deploy rankingUpdateGo RankingUpdateGo \
--trigger-topic=ranking-update-go --memory=256Mi --timeout=300s
deploy rankingCacheGo RankingCacheGo \
--trigger-topic=ranking-cache-go --memory=256Mi --timeout=300s
deploy statusCacheBackfillGo StatusCacheBackfillGo \
--trigger-topic=status-cache-backfill-go --memory=512Mi --timeout=300s
deploy scheduledOgpDeleteGo ScheduledOgpDeleteGo \
--trigger-topic=scheduled-ogp-delete-go --memory=256Mi --timeout=300s \
--set-env-vars="STORAGE_BUCKET_NAME=d-shrine-dev.appspot.com"
fail=0
for idx in "${!NAMES[@]}"; do
name="${NAMES[$idx]}"
if wait "${PIDS[$idx]}"; then
status=OK
else
status=FAILED
fail=1
fi
echo "::group::deploy $status: $name"
cat "/tmp/deploy-$name.log"
echo "::endgroup::"
done
if [ "$fail" -ne 0 ]; then
echo "one or more Go function deployments failed" >&2
fi
exit "$fail"
# スケジュール関数用のCloud Schedulerジョブを作成/更新する(冪等)。
# ジョブはトピックにメッセージを流すだけなので関数デプロイ完了後に一括で行う。
# update が存在しないジョブに対して失敗したら create にフォールバックする。
- name: create/update Cloud Scheduler jobs for scheduled Go functions
run: |
set -uo pipefail
upsert_job() {
local job="$1" schedule="$2" topic="$3"
gcloud scheduler jobs update pubsub "$job" \
--project=d-shrine-dev --location="$APP_ENGINE_LOCATION" \
--schedule="$schedule" \
--topic="$topic" \
--message-body="{}" \
--time-zone=Etc/UTC \
|| gcloud scheduler jobs create pubsub "$job" \
--project=d-shrine-dev --location="$APP_ENGINE_LOCATION" \
--schedule="$schedule" \
--topic="$topic" \
--message-body="{}" \
--time-zone=Etc/UTC
}
upsert_job ranking-update-go "0 * * * *" ranking-update-go
upsert_job ranking-cache-go "0 */2 * * *" ranking-cache-go
upsert_job status-cache-backfill-go "*/30 * * * *" status-cache-backfill-go
upsert_job scheduled-ogp-delete-go "0 */1 * * *" scheduled-ogp-delete-go