Skip to content

Deploy ​

ブラウザコインストア (javascript/apps/store/) のデプロイ整備状況と、初回セットアップで必要な手動作業のメモ。 アプリの仕様は ブラウザコイン購入 (Windows) を参照。

環境別 endpoint ​

環境URL (GKE Ingress)Firebase project
devdev.store.uzu-app.comuzu-dev
stgstg.store.uzu-app.comuzu-stg
prdstore.uzu-app.comuzu-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-arg APP_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 で定義、KSA default/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 ​

  1. Terraform apply — infra/terraform/dev の store-server GSA / WIF binding。main push で GHA が自動 apply する

  2. GKE 静的 IP の予約 — Ingress の kubernetes.io/ingress.global-static-ip-name: store-ip に対応する static IP を確保

    bash
    gcloud compute addresses create store-ip --global --project=uzu-dev
    gcloud compute addresses describe store-ip --global --project=uzu-dev --format='value(address)'
  3. Cloudflare DNS レコード追加 — dev.store.uzu-app.com → 手順 2 の IP 宛 (A レコード、DNS only / orange-cloud OFF)。 proxied にすると GKE ManagedCertificate の HTTP-01 challenge が Cloudflare edge で吸収されてプロビできない。 手順は DNS・ドメイン設定

  4. Secret Manager に Stripe の鍵を登録 — ExternalSecret が store-stripe-secret-key を同期できないと K8s Secret が作られず Pod が起動できないので、デプロイ前に登録する

    bash
    printf '%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_...) が望ましい

  5. main push — CI が動いて初回デプロイが回る。https://dev.store.uzu-app.com/healthz が ok を返せば疎通完了 (ManagedCertificate が Active になるまで 10〜30 分かかる)

prd ​

dev / stg と同じ手順だが、以下が違う:

  1. Terraform apply — infra/terraform/prd は v* タグ push で自動 apply される

  2. GKE 静的 IP の予約 — gcloud compute addresses create store-ip --global --project=uzu-prd

  3. Cloudflare DNS レコード追加 — store.uzu-app.com → static IP (A レコード、DNS only)

  4. Secret Manager — Stripe 本番アカウントの鍵を store-stripe-secret-key に登録

  5. store@ の起点タグを 1 本 push — release workflow を含む PR の merge 前に済ませる。 release-gate は前回リリース起点を chore(store): release commit かこのタグから解決する。 どちらも無いと gate が exit 1 になり、schedule のたびに失敗する (auth も auth@6.14.0 を手で打って始めた)。

    タグは store のファイルが入る前の main に打つ。main HEAD に打つと差分がゼロになり、初回の schedule が 何もせず success で終わる。

    bash
    git tag -a store@0.0.0 -m store@0.0.0 <store を追加する PR の base にした main の SHA>
    git push origin store@0.0.0
  6. release workflow を dispatch (schedule を待たない場合) — gh workflow run javascript_store_release.yml --ref main。 /release skill 経由でも発火できる

商品と 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 で足りる):

bash
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