Skip to content

Commit 3a867b7

Browse files
authored
Merge pull request #57 from kc3hack/feature/cloudrunDev
Feature/cloudrun dev
2 parents c13d195 + cd1ebe5 commit 3a867b7

File tree

5 files changed

+151
-7
lines changed

5 files changed

+151
-7
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Deploy Backend to Cloud Run
2+
3+
on:
4+
push:
5+
branches:
6+
- develop
7+
- main
8+
paths:
9+
- "Backend/**"
10+
- ".github/workflows/deploy-backend-cloud-run.yml"
11+
workflow_dispatch:
12+
13+
permissions:
14+
contents: read
15+
id-token: write
16+
17+
env:
18+
GCP_PROJECT_ID: kc3hack2026
19+
GCP_REGION: asia-southeast1
20+
CLOUD_RUN_SERVICE: lexiflow-backend
21+
GEMINI_SECRET_NAME: gemini-api-key
22+
DB_CERT_SECRET_NAME: cockroach-root-crt
23+
24+
concurrency:
25+
group: deploy-backend-cloud-run-${{ github.ref }}
26+
cancel-in-progress: true
27+
28+
jobs:
29+
deploy:
30+
runs-on: ubuntu-latest
31+
timeout-minutes: 40
32+
33+
steps:
34+
- name: Checkout
35+
uses: actions/checkout@v4
36+
37+
- name: Validate required secrets
38+
run: |
39+
if [ -z "${{ secrets.GCP_SA_KEY }}" ]; then
40+
echo "::error::Repository secret GCP_SA_KEY is not set."
41+
exit 1
42+
fi
43+
44+
- name: Authenticate to Google Cloud
45+
uses: google-github-actions/auth@v2
46+
with:
47+
credentials_json: ${{ secrets.GCP_SA_KEY }}
48+
49+
- name: Setup gcloud
50+
uses: google-github-actions/setup-gcloud@v2
51+
with:
52+
project_id: ${{ env.GCP_PROJECT_ID }}
53+
54+
- name: Deploy Backend to Cloud Run
55+
run: |
56+
gcloud run deploy "${CLOUD_RUN_SERVICE}" \
57+
--quiet \
58+
--source "${GITHUB_WORKSPACE}/Backend" \
59+
--project "${GCP_PROJECT_ID}" \
60+
--region "${GCP_REGION}" \
61+
--allow-unauthenticated \
62+
--memory 1Gi \
63+
--cpu 1 \
64+
--concurrency 10 \
65+
--timeout 60 \
66+
--min-instances 0 \
67+
--max-instances 2 \
68+
--set-secrets "GEMINI_API_KEY=${GEMINI_SECRET_NAME}:latest,/root/.postgresql/root.crt=${DB_CERT_SECRET_NAME}:latest" \
69+
--set-env-vars "GEMINI_MODEL=gemini-2.5-flash-lite,GEMINI_TIMEOUT_SECONDS=10,GEMINI_PARALLELISM=3,ENABLE_DB_INIT=false"
70+
71+
- name: Smoke test
72+
run: |
73+
SERVICE_URL="$(gcloud run services describe "${CLOUD_RUN_SERVICE}" --region "${GCP_REGION}" --project "${GCP_PROJECT_ID}" --format='value(status.url)')"
74+
echo "Service URL: ${SERVICE_URL}"
75+
curl -fsS "${SERVICE_URL}/"

Backend/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ make docker-clean
6060
Cloud Run へのデプロイ手順は以下を参照してください。
6161

6262
- `/Users/honmayuudai/MyHobby/hackson/KC3Hack2026/doc/cloud-run-deploy-workflow.md`
63+
- 自動デプロイ Workflow: `/Users/honmayuudai/MyHobby/hackson/KC3Hack2026/.github/workflows/deploy-backend-cloud-run.yml`
6364

6465
## 現在の API
6566

Backend/app/services/refer_dictionary.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@ async def _lookup_or_create(
9595
# 1. DB検索
9696
entry = None
9797
if db.is_available:
98-
entry = await asyncio.to_thread(read_dictionary_by_term, compound_term)
98+
try:
99+
entry = await asyncio.to_thread(read_dictionary_by_term, compound_term)
100+
except Exception:
101+
logger.exception("DB検索に失敗したためLLMへフォールバック: term=%s", compound_term)
102+
entry = None
99103

100104
if entry:
101105
return DictionaryEntry(
@@ -125,9 +129,14 @@ async def _lookup_or_create(
125129
meaning_vector=vector,
126130
)
127131
except Exception:
128-
# UNIQUE 違反 → 既に他のタスクが登録済み
129-
logger.info("重複登録をスキップ: %s", compound_term)
130-
entry = await asyncio.to_thread(read_dictionary_by_term, compound_term)
132+
# UNIQUE 違反などで登録に失敗した場合は既存行を読み直す。
133+
# 読み直しにも失敗した場合はLLM結果を返し、API全体は継続させる。
134+
logger.exception("DB登録に失敗したため読み直しを試行: term=%s", compound_term)
135+
try:
136+
entry = await asyncio.to_thread(read_dictionary_by_term, compound_term)
137+
except Exception:
138+
logger.exception("DB再読込にも失敗したためLLM結果を返却: term=%s", compound_term)
139+
entry = None
131140
if entry:
132141
return DictionaryEntry(
133142
term=entry.term,
@@ -170,4 +179,4 @@ def _extract_search_targets(text: str) -> list[tuple[str, ...]]:
170179
if noun_buffer:
171180
search_targets.append(tuple(noun_buffer))
172181

173-
return search_targets
182+
return search_targets

Backend/tests/test_refer_dictionary_endpoint.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,34 @@ def test_refer_dictionary_db_unavailable(mock_external_services) -> None:
161161
mock_external_services["read"].assert_not_called()
162162
mock_external_services["create"].assert_not_called()
163163
mock_external_services["lookup"].assert_called_with("犬")
164+
165+
166+
def test_refer_dictionary_db_read_error_fallback(mock_external_services) -> None:
167+
"""DB readで例外が出てもLLMフォールバックで200を返す。"""
168+
mock_external_services["read"].side_effect = RuntimeError("db read failed")
169+
170+
res = client.post("/analysis/refer_dictionary", json={"text": "犬"})
171+
assert res.status_code == 200
172+
173+
entries = res.json()["entries"]
174+
assert len(entries) == 1
175+
assert entries[0]["term"] == "犬"
176+
assert entries[0]["source"] == "llm"
177+
178+
mock_external_services["lookup"].assert_called_with("犬")
179+
180+
181+
def test_refer_dictionary_db_create_error_fallback(mock_external_services) -> None:
182+
"""DB createで例外が出てもLLM結果を返し500にしない。"""
183+
mock_external_services["read"].return_value = None
184+
mock_external_services["create"].side_effect = RuntimeError("db write failed")
185+
186+
res = client.post("/analysis/refer_dictionary", json={"text": "犬"})
187+
assert res.status_code == 200
188+
189+
entries = res.json()["entries"]
190+
assert len(entries) == 1
191+
assert entries[0]["term"] == "犬"
192+
assert entries[0]["source"] == "llm"
193+
194+
mock_external_services["lookup"].assert_called_with("犬")

doc/cloud-run-deploy-workflow.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ PROJECT_ID="kc3hack2026"
99
REGION="asia-southeast1"
1010
SERVICE_NAME="lexiflow-backend"
1111
SECRET_NAME="gemini-api-key"
12+
CERT_SECRET_NAME="cockroach-root-crt"
1213
```
1314

1415
- GCP のシンガポールリージョン名は `asia-southeast1`(AWS の `ap-southeast-1` とは表記が異なる)
@@ -62,6 +63,15 @@ gcloud secrets add-iam-policy-binding "$SECRET_NAME" \
6263
--project "$PROJECT_ID"
6364
```
6465

66+
証明書 Secret も同様に付与:
67+
68+
```bash
69+
gcloud secrets add-iam-policy-binding "$CERT_SECRET_NAME" \
70+
--member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
71+
--role="roles/secretmanager.secretAccessor" \
72+
--project "$PROJECT_ID"
73+
```
74+
6575
## 2. デプロイ(通常運用)
6676

6777
リポジトリルート(`/Users/honmayuudai/MyHobby/hackson/KC3Hack2026`)で実行:
@@ -79,8 +89,8 @@ gcloud run deploy "$SERVICE_NAME" \
7989
--timeout 60 \
8090
--min-instances 0 \
8191
--max-instances 2 \
82-
--set-secrets "GEMINI_API_KEY=${SECRET_NAME}:latest" \
83-
--set-env-vars "GEMINI_MODEL=gemini-2.0-flash,GEMINI_TIMEOUT_SECONDS=10,GEMINI_PARALLELISM=3,ENABLE_DB_INIT=false"
92+
--set-secrets "GEMINI_API_KEY=${SECRET_NAME}:latest,/root/.postgresql/root.crt=${CERT_SECRET_NAME}:latest" \
93+
--set-env-vars "GEMINI_MODEL=gemini-2.5-flash-lite,GEMINI_TIMEOUT_SECONDS=10,GEMINI_PARALLELISM=3,ENABLE_DB_INIT=false"
8494
```
8595

8696
## 3. デプロイ後の確認
@@ -110,6 +120,15 @@ gcloud run services update "$SERVICE_NAME" \
110120
--update-secrets "GEMINI_API_KEY=${SECRET_NAME}:latest"
111121
```
112122

123+
証明書 Secret を更新した場合:
124+
125+
```bash
126+
gcloud run services update "$SERVICE_NAME" \
127+
--project "$PROJECT_ID" \
128+
--region "$REGION" \
129+
--update-secrets "/root/.postgresql/root.crt=${CERT_SECRET_NAME}:latest"
130+
```
131+
113132
## 5. CORS(フロント本番URL)設定例
114133

115134
`ALLOWED_ORIGINS` はカンマ区切りで指定:
@@ -136,3 +155,12 @@ gcloud secrets delete "$SECRET_NAME" \
136155
--project "$OLD_PROJECT_ID" \
137156
--quiet
138157
```
158+
159+
## 7. GitHub Actions で自動デプロイ
160+
161+
`/Users/honmayuudai/MyHobby/hackson/KC3Hack2026/.github/workflows/deploy-backend-cloud-run.yml` を使う場合、
162+
GitHub Repository Secrets に以下を登録します。
163+
164+
- `GCP_SA_KEY`: Cloud Run デプロイ権限を持つサービスアカウント JSON キー
165+
166+
このワークフローは `develop` / `main` への push(`Backend/**` 変更時)で自動実行されます。

0 commit comments

Comments
 (0)