Deploy
ブラウザコインストア (javascript/apps/store/) のデプロイ整備状況と、初回セットアップで必要な手動作業のメモ。 アプリの仕様は ブラウザコイン購入 (Windows) を参照。
環境別 endpoint
| 環境 | URL (GKE Ingress) | Firebase project |
|---|---|---|
| dev | dev.store.uzu-app.com | uzu-dev |
| stg | stg.store.uzu-app.com | uzu-stg |
| prd | store.uzu-app.com | uzu-prd |
backend へはクラスタ内の http://backend-graph/graphql で接続する (SPA は backend を直接呼ばない)。
CI/CD
| 役割 | workflow |
|---|---|
| image build + GKE rollout (dev / stg) | .github/workflows/javascript_store_deploy_push.yml (main push) |
| GKE rollout (prd) | .github/workflows/javascript_store_release.yml (schedule / workflow_dispatch) |
| deploy template (workflow_call) | .github/workflows/javascript_store_deploy_template.yml |
prd は他の JS サービスと同じ個別リリース経路で、平日 JST 14:00 の schedule と手動 dispatch で発火する (auth と同じ構成)。_app_release.yml が store@ タグと changelog PR (docs/docs/changelogs/store.md) を作り、 その SHA を pin して deploy する。
アーキテクチャ判断
- 1 コンテナ: Hono (API + 静的配信) と Vite build 済みの SPA を同じ image に入れ、GKE (uzu / mdms_jp と同居) にデプロイする。
javascript/apps/store/k8s/{base,dev,stg,prd}に kustomize 構成。uzutokyo backend と同じ形 - 環境別 image:
VITE_*は Vite が build 時に bundle へ inline するため、k8s_deploy_template.ymlが渡す build-argAPP_ENVでvite build --mode <env>を実行し、javascript/apps/store/.env.<env>の値を埋め込む。image は環境ごとに別物になる - Secret: 非機密 (
APP_ENV等) はkustomization.template.yamlのsecretGenerator、Stripe の鍵などの機密は Secret Manager → ExternalSecret (mdms_jp / uzutokyo と同パターン) - WIF: GSA
store-server@uzu-{env}をinfra/terraform/modules/iam/main.tfで定義、KSAdefault/storeから impersonate。store が Google API に対して行うのは firebase-admin の ID token 検証 (ローカル処理) だけなので、GSA に IAM ロールは付けていない - 認証: 認証基盤 (apps/auth) + Firebase ID Token。auth の redirect ホワイトリストは
uzu-app.comの suffix match なので追加設定は不要
初回セットアップ (手動作業)
CI が回り始めるまでに 1 回だけ必要。以降は main push で自動デプロイ。
dev
Terraform apply —
infra/terraform/devのstore-serverGSA / WIF binding。main push で GHA が自動 apply するGKE 静的 IP の予約 — Ingress の
kubernetes.io/ingress.global-static-ip-name: store-ipに対応する static IP を確保bashgcloud compute addresses create store-ip --global --project=uzu-dev gcloud compute addresses describe store-ip --global --project=uzu-dev --format='value(address)'Cloudflare DNS レコード追加 —
dev.store.uzu-app.com→ 手順 2 の IP 宛 (A レコード、DNS only / orange-cloud OFF)。 proxied にすると GKE ManagedCertificate の HTTP-01 challenge が Cloudflare edge で吸収されてプロビできない。 手順は DNS・ドメイン設定Secret Manager に Stripe の鍵を登録 — ExternalSecret が
store-stripe-secret-keyを同期できないと K8s Secret が作られず Pod が起動できないので、デプロイ前に登録するbashprintf '%s' 'sk_test_...' | gcloud secrets create store-stripe-secret-key \ --project=uzu-dev --replication-policy=automatic --data-file=-backend の
/webhook/stripeが使う鍵とは分ける。store に要るのは Checkout Sessions の Write と Prices の Read だけなので、 restricted key (rk_...) が望ましいmain push — CI が動いて初回デプロイが回る。
https://dev.store.uzu-app.com/healthzがokを返せば疎通完了 (ManagedCertificate が Active になるまで 10〜30 分かかる)
prd
dev / stg と同じ手順だが、以下が違う:
Terraform apply —
infra/terraform/prdはv*タグ push で自動 apply されるGKE 静的 IP の予約 —
gcloud compute addresses create store-ip --global --project=uzu-prdCloudflare DNS レコード追加 —
store.uzu-app.com→ static IP (A レコード、DNS only)Secret Manager — Stripe 本番アカウントの鍵を
store-stripe-secret-keyに登録store@の起点タグを 1 本 push — release workflow を含む PR の merge 前に済ませる。release-gateは前回リリース起点をchore(store): releasecommit かこのタグから解決する。 どちらも無いと gate が exit 1 になり、schedule のたびに失敗する (auth もauth@6.14.0を手で打って始めた)。タグは store のファイルが入る前の main に打つ。main HEAD に打つと差分がゼロになり、初回の schedule が 何もせず success で終わる。
bashgit tag -a store@0.0.0 -m store@0.0.0 <store を追加する PR の base にした main の SHA> git push origin store@0.0.0release workflow を dispatch (schedule を待たない場合) —
gh workflow run javascript_store_release.yml --ref main。/releaseskill 経由でも発火できる
商品と backend 側の設定
コインの付与は backend の /webhook/stripe が行う。store を公開するには、環境ごとに次も要る (手順は ブラウザコイン購入 (Windows))。
- Stripe Dashboard に webhook endpoint (
https://<env>.graph.backend.app.uzu.one/webhook/stripe、checkout.session.completed/charge.refunded/charge.dispute.created) を登録する - backend の
STRIPE_SECRET_KEY/STRIPE_WEBHOOK_SECRETを Secret Manager のbackend-stripe-secret-key/backend-stripe-webhook-secretに登録する。backend/k8s/deploy/<env>/external-secret.yamlのbackend-secret-external-envが同期する。 ExternalSecret に足すのは Secret Manager への登録後にする (先に足すと同期に失敗して backend の Pod が起動できない)。prd は販売開始まで未登録なので足していない - Stripe の webhook はアカウント内の全イベントを全 endpoint に送るので、dev / stg はサンドボックスを分ける。共有すると別環境の購入イベントが届き、商品が見つからず 5xx と再送が続く。 ローカルだけは dev のサンドボックスを共有し、鍵も uzu-dev の Secret Manager から引く (ローカルの商品とユーザーを dev に揃える前提)
- 商品を backend の
consumable_products(store = WEB) と Stripe の Price (lookup_key=product_id) の両方に登録する
ローカル開発
task store:dev で server (Hono, STORE_PORT+1) と client (Vite, STORE_PORT) を zellij で並べて起動する。 zellij が無ければ task store:server と task store:client を別ターミナルで起動する。ポートは .ports.local を参照。
本番と同じ「Hono が build 済み SPA を配信する」形を確かめるときは Docker image を作る (GitHub Packages の token は gh auth token で足りる):
cd javascript
GH_PKG_TOKEN="$(gh auth token)" DOCKER_BUILDKIT=1 docker build -f apps/store/k8s/back.dockerfile \
--build-arg APP_ENV=dev --secret id=github_packages_token,env=GH_PKG_TOKEN -t store-back:local .
docker run --rm -p 48080:8080 -e APP_ENV=dev store-back:local node --import=tsx server/index.ts