RenderからGCP Cloud Runへの移行、Neon PostgreSQLへの移行、GitHub連携による継続的デプロイの実装について
コスト削減とスケーラビリティの向上を目指し、RenderからGCP Cloud Runへ移行しました。この記事では、移行の背景、実装の詳細、そしてトラブルシューティングについて解説します。
FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=off
WORKDIR /app
# 依存関係を先にインストールしてレイヤキャッシュを効かせる
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# アプリコードをコピー
COPY . .
# Cloud Run のデフォルトポート(環境変数 PORT が渡される)
ENV PORT=8080
# Gunicorn 起動スクリプトを実行可能に
RUN chmod +x start.sh
# Flask アプリ(app:app)を Gunicorn で起動
CMD ["./start.sh"]
#!/bin/bash
# start.sh
# 環境変数の確認
echo "PORT: $PORT"
echo "DATABASE_URL: $DATABASE_URL"
# GunicornでFlaskアプリを起動
exec gunicorn \
--bind 0.0.0.0:$PORT \
--workers 2 \
--threads 4 \
--timeout 120 \
--access-logfile - \
--error-logfile - \
app:app
# Cloud Runにデプロイ
gcloud run deploy medicine-recommend \
--source . \
--platform managed \
--region asia-northeast1 \
--allow-unauthenticated \
--set-env-vars DATABASE_URL=$DATABASE_URL,OPENAI_API_KEY=$OPENAI_API_KEY
Cloud RunとGitHubを連携し、pushで自動ビルド・デプロイ:
# .github/workflows/deploy.yml
name: Deploy to Cloud Run
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Cloud SDK
uses: google-github-actions/setup-gcloud@v0
with:
service_account_key: ${{ secrets.GCP_SA_KEY }}
project_id: ${{ secrets.GCP_PROJECT_ID }}
- name: Deploy to Cloud Run
run: |
gcloud run deploy medicine-recommend \
--source . \
--platform managed \
--region asia-northeast1 \
--allow-unauthenticated
# config/app_config.py
import os
def get_database_url():
"""
Neon PostgreSQLの接続文字列を取得
"""
database_url = os.getenv('DATABASE_URL')
if not database_url:
# 開発環境用のデフォルト値
database_url = 'postgresql://user:password@localhost:5432/medicine_db'
return database_url
# src/services/database.py
import psycopg2
from psycopg2 import pool
# コネクションプールの作成
connection_pool = None
def init_database():
"""
データベースを初期化
"""
global connection_pool
try:
database_url = get_database_url()
connection_pool = psycopg2.pool.SimpleConnectionPool(
1, 20, database_url
)
# テーブルの作成
create_tables()
return True
except Exception as e:
logger.error(f"データベース初期化エラー: {e}")
return False
def get_connection():
"""
コネクションプールから接続を取得
"""
if connection_pool:
return connection_pool.getconn()
return None
def return_connection(conn):
"""
接続をコネクションプールに返す
"""
if connection_pool:
connection_pool.putconn(conn)
# 環境変数を設定
gcloud run services update medicine-recommend \
--set-env-vars \
DATABASE_URL=$DATABASE_URL,\
OPENAI_API_KEY=$OPENAI_API_KEY,\
DEEPL_API_KEY=$DEEPL_API_KEY,\
SECRET_KEY=$SECRET_KEY
機密情報はSecret Managerに保存:
# config/secrets.py
from google.cloud import secretmanager
def get_secret(secret_id):
"""
Secret Managerからシークレットを取得
"""
client = secretmanager.SecretManagerServiceClient()
name = f"projects/{project_id}/secrets/{secret_id}/versions/latest"
response = client.access_secret_version(request={"name": name})
return response.payload.data.decode("UTF-8")
# app.py
# アプリ起動時にリソースを事前読み込み
def initialize_resources():
"""
リソースを事前読み込み(コールドスタート対策)
"""
# 方言変換リソースの初期化
from src.core.scoring_utils import initialize_dialect_resources
initialize_dialect_resources()
# データベース接続の確立
init_database()
# 辞書データの読み込み
load_symptom_dictionary()
load_ingredient_dictionary()
# アプリ起動時に実行
initialize_resources()
# 最小インスタンス数を1に設定(コールドスタート回避)
gcloud run services update medicine-recommend \
--min-instances 1
| 項目 | Render | GCP Cloud Run | |------|--------|---------------| | 月額固定費 | $25 | $0(従量課金) | | データベース | Cloud SQL ($30/月) | Neon ($0-10/月) | | 合計 | $55/月 | $5-15/月 |
原因: アプリ起動時のリソース読み込みに時間がかかる
解決策:
原因: Neon PostgreSQLの接続文字列が正しく設定されていない
解決策:
DATABASE_URLの確認原因: Cloud Runのメモリ制限を超えている
解決策:
原因: リクエスト処理時間が60秒を超えている
解決策:
学生として開発しているため、コストは重要な課題でした。RenderからGCP Cloud Runへの移行により、月額コストを約85%削減できたことは、大きな成果でした。
学んだこと:
移行期間は2日でしたが、多くの課題に直面しました。Dockerfileの作成、環境変数の設定、データベース接続の確認など、細かい作業が多く、時間がかかりました。
学んだこと:
移行後も、継続的な改善を行っています。コールドスタートの対策、メモリ使用量の最適化、ログの改善など、継続的にシステムを改善しています。
RenderからGCP Cloud Runへの移行により、コストを約85%削減し、スケーラビリティと開発効率を向上させました。Neon PostgreSQLへの移行により、データベースコストも大幅に削減しました。
「継続的な改善」 - この信念を胸に、今後もパフォーマンスの最適化とコスト削減を続けていきます。