gcp-cloud-run
GCP上で本番品質のサーバーレスアプリケーションを構築するための専門スキルです。コンテナベースのCloud Runサービス、イベント駆動型のCloud Run Functions、コールドスタートの最適化、およびPub/Subを活用したイベント駆動アーキテクチャに対応しています。
description の原文を見る
Specialized skill for building production-ready serverless applications on GCP. Covers Cloud Run services (containerized), Cloud Run Functions (event-driven), cold start optimization, and event-driven architecture with Pub/Sub.
SKILL.md 本文
GCP Cloud Run
GCPでのプロダクション対応サーバーレスアプリケーション構築に特化したスキル。Cloud Runサービス(コンテナ化)、Cloud Run Functions(イベント駆動)、コールドスタート最適化、Pub/Subを使用したイベント駆動アーキテクチャをカバーします。
原則
- コンテナにはCloud Run、シンプルなイベントハンドラーにはFunctionsを使用
- スタートアップCPUブーストと最小インスタンス数でコールドスタートを最適化
- ワークロードに基づいて同時実行数を設定(8から開始して調整)
- メモリには/tmpファイルシステムが含まれる - それに応じて計画を立てる
- VPC Connectorが必要な場合のみを使用(レイテンシが増加)
- コンテナはすばやく起動し、ステートレスである必要がある
- クリーンシャットダウンのためにシグナルを適切に処理する
パターン
Cloud Runサービスパターン
Cloud Run上のコンテナ化されたウェブサービス
使用すべき場面: ウェブアプリケーションとAPI、特定のランタイムやライブラリが必要な場合、複数のエンドポイントを持つ複雑なサービス、ステートレスなコンテナ化されたワークロード
# Dockerfile - 小さいイメージのためのマルチステージビルド
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-slim
WORKDIR /app
# プロダクション依存関係のみをコピー
COPY --from=builder /app/node_modules ./node_modules
COPY src ./src
COPY package.json ./
# Cloud Runは PORT環境変数を使用
ENV PORT=8080
EXPOSE 8080
# ルート以外のユーザーとして実行
USER node
CMD ["node", "src/index.js"]
// src/index.js
const express = require('express');
const app = express();
app.use(express.json());
// ヘルスチェックエンドポイント
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
// APIルート
app.get('/api/items/:id', async (req, res) => {
try {
const item = await getItem(req.params.id);
res.json(item);
} catch (error) {
console.error('Error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// グレースフルシャットダウン
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
const PORT = process.env.PORT || 8080;
const server = app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
# cloudbuild.yaml
steps:
# コンテナイメージをビルド
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/my-service:$COMMIT_SHA', '.']
# コンテナイメージをプッシュ
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/my-service:$COMMIT_SHA']
# Cloud Runにデプロイ
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- 'my-service'
- '--image=gcr.io/$PROJECT_ID/my-service:$COMMIT_SHA'
- '--region=us-central1'
- '--platform=managed'
- '--allow-unauthenticated'
- '--memory=512Mi'
- '--cpu=1'
- '--min-instances=1'
- '--max-instances=100'
- '--concurrency=80'
- '--cpu-boost'
images:
- 'gcr.io/$PROJECT_ID/my-service:$COMMIT_SHA'
構造
project/ ├── Dockerfile ├── .dockerignore ├── src/ │ ├── index.js │ └── routes/ ├── package.json └── cloudbuild.yaml
Gcloud デプロイ
gcloud直接デプロイ
gcloud run deploy my-service
--source .
--region us-central1
--allow-unauthenticated
--memory 512Mi
--cpu 1
--min-instances 1
--max-instances 100
--concurrency 80
--cpu-boost
Cloud Run Functionsパターン
イベント駆動型関数(旧Cloud Functions)
使用すべき場面: シンプルなイベントハンドラー、Pub/Subメッセージ処理、Cloud Storageトリガー、HTTPウェブフック
// HTTP関数
// index.js
const functions = require('@google-cloud/functions-framework');
functions.http('helloHttp', (req, res) => {
const name = req.query.name || req.body.name || 'World';
res.send(`Hello, ${name}!`);
});
// Pub/Sub関数
const functions = require('@google-cloud/functions-framework');
functions.cloudEvent('processPubSub', (cloudEvent) => {
// Pub/Subメッセージをデコード
const message = cloudEvent.data.message;
const data = message.data
? JSON.parse(Buffer.from(message.data, 'base64').toString())
: {};
console.log('Received message:', data);
// メッセージを処理
processMessage(data);
});
// Cloud Storage関数
const functions = require('@google-cloud/functions-framework');
functions.cloudEvent('processStorageEvent', async (cloudEvent) => {
const file = cloudEvent.data;
console.log(`Event: ${cloudEvent.type}`);
console.log(`Bucket: ${file.bucket}`);
console.log(`File: ${file.name}`);
if (cloudEvent.type === 'google.cloud.storage.object.v1.finalized') {
await processUploadedFile(file.bucket, file.name);
}
});
# HTTP関数をデプロイ
gcloud functions deploy hello-http \
--gen2 \
--runtime nodejs20 \
--trigger-http \
--allow-unauthenticated \
--region us-central1
# Pub/Sub関数をデプロイ
gcloud functions deploy process-messages \
--gen2 \
--runtime nodejs20 \
--trigger-topic my-topic \
--region us-central1
# Cloud Storage関数をデプロイ
gcloud functions deploy process-uploads \
--gen2 \
--runtime nodejs20 \
--trigger-event-filters="type=google.cloud.storage.object.v1.finalized" \
--trigger-event-filters="bucket=my-bucket" \
--region us-central1
コールドスタート最適化パターン
Cloud Runのコールドスタートレイテンシを最小化
使用すべき場面: レイテンシに敏感なアプリケーション、ユーザー向けAPI、高トラフィックサービス
1. スタートアップCPUブーストを有効化
gcloud run deploy my-service \
--cpu-boost \
--region us-central1
2. 最小インスタンス数を設定
gcloud run deploy my-service \
--min-instances 1 \
--region us-central1
3. コンテナイメージを最適化
# 最小イメージのためにdistrolessを使用
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY src ./src
CMD ["src/index.js"]
4. 重い依存関係を遅延初期化
// 重いライブラリを遅延ロード
let bigQueryClient = null;
function getBigQueryClient() {
if (!bigQueryClient) {
const { BigQuery } = require('@google-cloud/bigquery');
bigQueryClient = new BigQuery();
}
return bigQueryClient;
}
// 必要な時のみ初期化
app.get('/api/analytics', async (req, res) => {
const client = getBigQueryClient();
const results = await client.query({...});
res.json(results);
});
5. メモリを増加(CPU も増加)
# メモリが高いほど、スタートアップ中のCPUも多い
gcloud run deploy my-service \
--memory 1Gi \
--cpu 2 \
--region us-central1
最適化の影響
- スタートアップCPUブースト: コールドスタートが50%高速化
- 最小インスタンス数: トラフィック急増時のコールドスタートを排除
- Distrolessイメージ: 攻撃面が小さく、プルが高速
- 遅延初期化: 重い読み込みを最初のリクエストに遅延
同時実行数設定パターン
Cloud Runの適切な同時実行数設定
使用すべき場面: インスタンス利用率を最適化する必要がある、トラフィック急増を効率的に処理する、コールドスタートを削減したい
同時実行数の理解
# デフォルト同時実行数は80
# ワークロードに基づいて調整
# I/O バウンドワークロード(ほとんどのウェブアプリ)
gcloud run deploy my-service \
--concurrency 80 \
--cpu 1
# CPU バウンドワークロード
gcloud run deploy my-service \
--concurrency 1 \
--cpu 1
# メモリ集約的ワークロード
gcloud run deploy my-service \
--concurrency 10 \
--memory 2Gi
Node.js の同時実行
// Node.js はシングルスレッドだが I/O を並行処理
// すべての I/O 操作に async/await を使用
// 良い例 - 非同期 I/O
app.get('/api/data', async (req, res) => {
const [users, products] = await Promise.all([
fetchUsers(),
fetchProducts()
]);
res.json({ users, products });
});
// 悪い例 - ブロッキング操作
app.get('/api/compute', (req, res) => {
const result = heavyCpuOperation(); // 他のリクエストをブロック!
res.json(result);
});
Python での Gunicorn による同時実行
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# 同時実行用の4つのワーカー
CMD exec gunicorn --bind :$PORT --workers 4 --threads 2 main:app
# main.py
from flask import Flask
app = Flask(__name__)
@app.route('/api/data')
def get_data():
return {'status': 'ok'}
同時実行数ガイドライン
- 同時実行数=1: CPU バウンドまたは安全でないコードのみ
- 同時実行数=8-20: メモリ集約的ワークロード
- 同時実行数=80: デフォルト、I/O バウンドに最適
- 同時実行数=250: 最大、非常に軽量なハンドラー向け
Pub/Sub統合パターン
Cloud Pub/Subを使用したイベント駆動処理
使用すべき場面: 非同期メッセージ処理、疎結合されたマイクロサービス、イベント駆動アーキテクチャ
Cloud Run へのプッシュサブスクリプション
# トピックを作成
gcloud pubsub topics create orders
# Cloud Run へのプッシュサブスクリプションを作成
gcloud pubsub subscriptions create orders-push \
--topic orders \
--push-endpoint https://my-service-xxx.run.app/pubsub \
--ack-deadline 600
// Pub/Subプッシュメッセージを処理
const express = require('express');
const app = express();
app.use(express.json());
app.post('/pubsub', async (req, res) => {
// リクエストが Pub/Sub からであることを検証
if (!req.body.message) {
return res.status(400).send('Invalid Pub/Sub message');
}
try {
// メッセージデータをデコード
const message = req.body.message;
const data = message.data
? JSON.parse(Buffer.from(message.data, 'base64').toString())
: {};
console.log('Processing order:', data);
await processOrder(data);
// 200 を返して確認
res.status(200).send('OK');
} catch (error) {
console.error('Processing failed:', error);
// 500 を返して再試行をトリガー
res.status(500).send('Processing failed');
}
});
メッセージを発行
const { PubSub } = require('@google-cloud/pubsub');
const pubsub = new PubSub();
async function publishOrder(order) {
const topic = pubsub.topic('orders');
const messageBuffer = Buffer.from(JSON.stringify(order));
const messageId = await topic.publishMessage({
data: messageBuffer,
attributes: {
type: 'order_created',
priority: 'high'
}
});
console.log(`Published message ${messageId}`);
return messageId;
}
デッドレターキュー
# DLQ トピックを作成
gcloud pubsub topics create orders-dlq
# サブスクリプションを DLQ で更新
gcloud pubsub subscriptions update orders-push \
--dead-letter-topic orders-dlq \
--max-delivery-attempts 5
Cloud SQL接続パターン
Cloud Run を Cloud SQL に安全に接続
使用すべき場面: リレーショナルデータベースが必要、既存アプリケーションを移行、複雑なクエリとトランザクション
# Cloud SQL 接続でデプロイ
gcloud run deploy my-service \
--add-cloudsql-instances PROJECT:REGION:INSTANCE \
--set-env-vars INSTANCE_CONNECTION_NAME="PROJECT:REGION:INSTANCE" \
--set-env-vars DB_NAME="mydb" \
--set-env-vars DB_USER="myuser"
// Unix ソケット接続を使用
const { Pool } = require('pg');
const pool = new Pool({
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
// Cloud SQL コネクタは Unix ソケットを使用
host: `/cloudsql/${process.env.INSTANCE_CONNECTION_NAME}`,
max: 5, // コネクションプールサイズ
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
});
app.get('/api/users', async (req, res) => {
const client = await pool.connect();
try {
const result = await client.query('SELECT * FROM users LIMIT 100');
res.json(result.rows);
} finally {
client.release();
}
});
# SQLAlchemy を使用した Python
import os
from sqlalchemy import create_engine
def get_engine():
instance_connection_name = os.environ["INSTANCE_CONNECTION_NAME"]
db_user = os.environ["DB_USER"]
db_pass = os.environ["DB_PASS"]
db_name = os.environ["DB_NAME"]
engine = create_engine(
f"postgresql+pg8000://{db_user}:{db_pass}@/{db_name}",
connect_args={
"unix_sock": f"/cloudsql/{instance_connection_name}/.s.PGSQL.5432"
},
pool_size=5,
max_overflow=2,
pool_timeout=30,
pool_recycle=1800,
)
return engine
ベストプラクティス
- コネクションプーリングを使用(インスタンスあたり5~10個)
- 適切なアイドルタイムアウトを設定
- コネクションエラーを適切に処理
- ローカル開発用に Cloud SQL Proxy を検討
Secret Manager 統合
Cloud Run でシークレットを安全に管理
使用すべき場面: APIキー、データベースパスワード、サービスアカウントキー、機密設定
# シークレットを作成
echo -n "my-secret-value" | gcloud secrets create my-secret --data-file=-
# 環境変数としてマウント
gcloud run deploy my-service \
--update-secrets=API_KEY=my-secret:latest
# ファイルボリュームとしてマウント
gcloud run deploy my-service \
--update-secrets=/secrets/api-key=my-secret:latest
// 環境変数としてマウントされたアクセス
const apiKey = process.env.API_KEY;
// ファイルとしてマウントされたアクセス
const fs = require('fs');
const apiKey = fs.readFileSync('/secrets/api-key', 'utf8');
// マウントされていない場合の Secret Manager API でのアクセス
const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');
const client = new SecretManagerServiceClient();
async function getSecret(name) {
const [version] = await client.accessSecretVersion({
name: `projects/${projectId}/secrets/${name}/versions/latest`
});
return version.payload.data.toString();
}
危険な落とし穴
/tmp ファイルシステムがメモリに含まれる
重大度: 高
状況: Cloud Run の /tmp ディレクトリにファイルを書き込み
症状:
- コンテナが OOM エラーで強制終了。
- メモリ使用量が予期せず増加。
- ファイル操作がコンテナを再起動させる。
- ログに「Container memory limit exceeded」が記録。
なぜこれが問題か: Cloud Run は /tmp にメモリ内ファイルシステムを使用します。/tmp に書き込まれたファイルは、コンテナのメモリ割り当てから消費されます。
一般的なシナリオ:
- ファイルを一時的にダウンロード
- テンポラリ処理ファイルを作成
- /tmp にキャッシュするライブラリ
- 大きなログバッファ
512MB のコンテナが 200MB ファイルを /tmp にダウンロードすると、アプリケーション用に約300MB しか残りません。
推奨される修正:
/tmp 使用量を含めてメモリを計算
# cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/gcloud'
args:
- 'run'
- 'deploy'
- 'my-service'
- '--memory=1Gi' # /tmp オーバーヘッドを含める
- '--image=gcr.io/$PROJECT_ID/my-service'
バッファリングの代わりにストリーミング
# 悪い例 - ファイル全体を /tmp にバッファリング
def process_large_file(bucket_name, blob_name):
blob = bucket.blob(blob_name)
blob.download_to_filename('/tmp/large_file')
with open('/tmp/large_file', 'rb') as f:
process(f.read())
# 良い例 - ストリーミング処理
def process_large_file(bucket_name, blob_name):
blob = bucket.blob(blob_name)
with blob.open('rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
process_chunk(chunk)
大きなファイルに Cloud Storage を使用
from google.cloud import storage
def process_with_gcs(bucket_name, input_blob, output_blob):
client = storage.Client()
bucket = client.bucket(bucket_name)
# GCS に直接アクセス/出力
input_blob = bucket.blob(input_blob)
output_blob = bucket.blob(output_blob)
with input_blob.open('rb') as reader:
with output_blob.open('wb') as writer:
for chunk in iter(lambda: reader.read(65536), b''):
processed = transform(chunk)
writer.write(processed)
メモリ使用量を監視
import psutil
import logging
def log_memory():
memory = psutil.virtual_memory()
logging.info(f"Memory: {memory.percent}% used, "
f"{memory.available / 1024 / 1024:.0f}MB available")
同時実行数=1 がスケーリングボトルネックを引き起こす
重大度: 高
状況: リクエスト分離のために同時実行数を1に設定
症状:
- 自動スケーリングが多くのコンテナインスタンスを作成。
- トラフィック急増時のレイテンシが高い。
- コールドスタートが増加。
- インスタンスが増加するため、コストが増加。
なぜこれが問題か: 同時実行数を1に設定すると、各コンテナは1つのリクエストのみを処理します。トラフィック急増時:
- 100個の同時リクエスト = 100個のコンテナインスタンス
- 各インスタンスはコールドスタートのオーバーヘッドがある
- インスタンスが増加するため、コストが高い
- スケーリングに時間がかかり、リクエストがキューイングされる
これは以下の場合のみ使用すべき:
- 処理が本当にシングルスレッド
- リクエストごとのメモリ集約的処理
- スレッド安全でないライブラリを使用
推奨される修正:
適切な同時実行数を設定
# I/O バウンドワークロード(ほとんどのウェブアプリ)
gcloud run deploy my-service \
--concurrency=80 \
--max-instances=100
# CPU バウンドワークロード
gcloud run deploy my-service \
--concurrency=4 \
--cpu=2
# 絶対必要な場合のみ 1 を使用
gcloud run deploy my-service \
--concurrency=1 \
--max-instances=1000 # 多くのインスタンスに対応するよう準備
Node.js - 非同期を適切に使用
// 高い同時実行数では、非同期操作を確保
const express = require('express');
const app = express();
app.get('/api/data', async (req, res) => {
// すべての I/O は非同期である必要があります
const data = await fetchFromDatabase();
const enriched = await enrichData(data);
res.json(enriched);
});
// 同時実行数 80以上は非同期 I/O ワークロードで安全
Python - 非同期フレームワークを使用
from fastapi import FastAPI
import asyncio
import httpx
app = FastAPI()
@app.get("/api/data")
async def get_data():
# 非同期 I/O により高い同時実行数を実現
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
return response.json()
# 非同期フレームワークで同時実行数 80以上は安全
同時実行数を計算
同時実行数 = メモリ制限 / リクエストあたりのメモリ
例:
- 512MB コンテナ
- リクエストあたり 20MB のオーバーヘッド
- 安全な同時実行数: 約 25
リクエストを処理していない時に CPU がスロットル
重大度: 高
状況: リクエスト間のバックグラウンドタスクまたは処理の実行
症状:
- バックグラウンドタスクが極めて遅い実行。
- スケジュールされた作業が完了しない。
- メトリクス収集が失敗。
- コネクション キープアライブが壊れる。
なぜこれが問題か: デフォルトでは、Cloud Run はリクエストをアクティブに処理していない時に CPU をほぼゼロにスロットルします。これは「リクエスト中のみ CPU 割り当て」モードです。
影響を受ける操作:
- バックグラウンドスレッド
- コネクションプールのメンテナンス
- メトリクス/テレメトリー送信
- コンテナ内のスケジュールされたタスク
- レスポンス後のクリーンアップ操作
推奨される修正:
CPU常時割り当てを有効化
# リクエスト外でも CPU を割り当て
gcloud run deploy my-service \
--cpu-throttling=false \
--min-instances=1
# 注意: これはコストが増加しますが、バックグラウンド作業を有効にします
スタートアップ CPU ブーストを初期化に使用
# コールドスタート中の CPU ブースト
gcloud run deploy my-service \
--cpu-boost \
--cpu-throttling=true # デフォルト、リクエスト後はスロットル
バックグラウンド作業を Cloud Tasks に移動
from google.cloud import tasks_v2
import json
def create_background_task(payload):
client = tasks_v2.CloudTasksClient()
parent = client.queue_path(
"my-project", "us-central1", "my-queue"
)
task = {
"http_request": {
"http_method": tasks_v2.HttpMethod.POST,
"url": "https://my-service.run.app/process",
"body": json.dumps(payload).encode(),
"headers": {"Content-Type": "application/json"}
}
}
client.create_task(parent=parent, task=task)
# レスポンスを直ちに処理、バックグラウンドは Cloud Tasks 経由
@app.post("/api/order")
async def create_order(order: Order):
order_id = await save_order(order)
# バックグラウンド処理をキュー
create_background_task({"order_id": order_id})
return {"order_id": order_id, "status": "processing"}
Pub/Sub を使用した非同期処理
# 重い処理を別のサービスに移動
steps:
# メインサービス - 素早くレスポンス
- name: 'gcr.io/cloud-builders/gcloud'
args: ['run', 'deploy', 'api-service',
'--cpu-throttling=true']
# ワーカーサービス - メッセージを処理
- name: 'gcr.io/cloud-builders/gcloud'
args: ['run', 'deploy', 'worker-service',
'--cpu-throttling=false',
'--min-instances=1']
VPC Connector の 10 分間アイドルタイムアウト
重大度: 中
状況: VPC リソースへの接続を行う Cloud Run サービス
症状:
- 非アクティブ期間後のコネクションエラー。
- 「Connection reset」または「Connection refused」エラー。
- VPC リソースへのスポラディックな失敗。
- データベース接続が予期せず切断。
なぜこれが問題か: Cloud Run の VPC コネクタは、コネクションの 10 分間アイドルタイムアウトを持ちます。コネクションが 10 分間アイドルの場合、サイレントに閉じられます。
影響:
- データベースコネクションプール
- Redis コネクション
- 内部 API コネクション
- 任意の永続的 VPC コネクション
推奨される修正:
キープアライブ付きコネクションプールを設定
# SQLAlchemy とコネクション リサイクル
from sqlalchemy import create_engine
engine = create_engine(
DATABASE_URL,
pool_size=5,
max_overflow=2,
pool_recycle=300, # 5分ごとにコネクションを再利用
pool_pre_ping=True # 使用前にコネクションを検証
)
カスタムコネクション用の TCP キープアライブ
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 60)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)
コネクション検証付き Redis
import redis
pool = redis.ConnectionPool(
host=REDIS_HOST,
port=6379,
socket_keepalive=True,
socket_keepalive_options={
socket.TCP_KEEPIDLE: 60,
socket.TCP_KEEPINTVL: 60,
socket.TCP_KEEPCNT: 5
},
health_check_interval=30
)
client = redis.Redis(connection_pool=pool)
Cloud SQL Proxy サイドカー使用
# Cloud SQL コネクタを使用します。これは再接続を処理します。
# requirements.txt
cloud-sql-python-connector[pg8000]
from google.cloud.sql.connector import Connector
import sqlalchemy
connector = Connector()
def getconn():
return connector.connect(
"project:region:instance",
"pg8000",
user="user",
password="password",
db="database"
)
engine = sqlalchemy.create_engine(
"postgresql+pg8000://",
creator=getconn
)
コンテナスタートアップタイムアウト(最大4分)
重大度: 高
状況: 初期化が遅いコンテナのデプロイ
症状:
- デプロイが「Container failed to start」で失敗。
- サービスが健全な状態にならない。
- 「Revision failed to become ready」エラー。
- ローカルで動作するがCloud Runで失敗。
なぜこれが問題か: Cloud Run は、コンテナが 4 分(240 秒)以内に PORT でリッスン開始することを期待します。そうしないと、インスタンスは強制終了されます。
一般的な原因:
- 重いフレームワーク初期化(ML モデルなど)
- スタートアップで外部依存関係を待機
- 大きな依存関係の読み込み
- スタートアップ時のデータベースマイグレーション
推奨される修正:
スタートアップCPUブーストを有効化
gcloud run deploy my-service \
--cpu-boost \
--startup-cpu-boost
遅延初期化
from functools import lru_cache
from fastapi import FastAPI
app = FastAPI()
# インポート時に読み込まない
model = None
@lru_cache()
def get_model():
global model
if model is None:
# 最初のリクエストで読み込み、スタートアップではなく
model = load_heavy_model()
return model
@app.get("/predict")
async def predict(data: dict):
model = get_model() # 最初の呼び出しでのみ読み込み
return model.predict(data)
# スタートアップは高速 - モデルは最初のリクエストで読み込み
直ちにリッスン開始
import asyncio
from fastapi import FastAPI
import uvicorn
app = FastAPI()
# 非同期初期化用のグローバル状態
initialized = asyncio.Event()
@app.on_event("startup")
async def startup():
# バックグラウンド初期化を開始
asyncio.create_task(async_init())
async def async_init():
# 重い初期化はサーバー起動後に発生
await load_models()
await warm_up_connections()
initialized.set()
@app.get("/ready")
async def ready():
if not initialized.is_set():
raise HTTPException(503, "Still initializing")
return {"status": "ready"}
@app.get("/health")
async def health():
# 常に応答 - ヘルスチェックは通過
return {"status": "healthy"}
マルチステージビルドを使用
# ビルドステージ - 遅い
FROM python:3.11 as builder
WORKDIR /app
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
# ランタイムステージ - 高速スタートアップ
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /wheels /wheels
RUN pip install --no-cache /wheels/* && rm -rf /wheels
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
マイグレーションを別途実行
# スタートアップではなく、Cloud Build でマイグレーション実行
steps:
# 最初にマイグレーション実行
- name: 'gcr.io/cloud-builders/gcloud'
entrypoint: 'bash'
args:
- '-c'
- |
gcloud run jobs execute migrate-job --wait
# その後デプロイ
- name: 'gcr.io/cloud-builders/gcloud'
args: ['run', 'deploy', 'my-service', ...]
第2世代実行環境の相違点
重大度: 中
状況: Cloud Run 第2世代実行環境への移行またはその使用
症状:
- ネットワーク動作の変化。
- 異なる syscall サポート。
- ファイルシステム動作の違い。
- コンテナが第1世代と異なるように動作。
なぜこれが問題か: Cloud Run の第2世代実行環境は、異なるサンドボックス(gVisor)を使用し、異なる特性があります:
- より多くの Linux syscall がサポートされる
- 完全な /proc と /sys アクセス
- 異なるネットワークスタック
- HTTP から HTTPS への自動リダイレクトなし
- 異なる tmp ファイルシステム動作
推奨される修正:
実行環境を明示的に設定
# 第1世代(レガシー)
gcloud run deploy my-service \
--execution-environment=gen1
# 第2世代(ほとんどで推奨)
gcloud run deploy my-service \
--execution-environment=gen2
ネットワーク相違に対応
# 第2世代は HTTP から HTTPS への自動リダイレクトなし
from fastapi import FastAPI, Request
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.middleware("http")
async def redirect_https(request: Request, call_next):
# X-Forwarded-Proto ヘッダーをチェック
if request.headers.get("X-Forwarded-Proto") == "http":
url = request.url.replace(scheme="https")
return RedirectResponse(url, status_code=301)
return await call_next(request)
GPU アクセス(第2世代のみ)
# GPU は第2世代でのみ利用可能
gcloud run deploy ml-service \
--execution-environment=gen2 \
--gpu=1 \
--gpu-type=nvidia-l4
実行環境をチェック
import os
def get_execution_environment():
# 第2世代は異なる /proc 構造を持つ
try:
with open('/proc/version', 'r') as f:
version = f.read()
if 'gVisor' in version:
return 'gen2'
except:
pass
return 'gen1'
リクエストタイムアウト設定の不一致
重大度: 中
状況: 長時間実行されるリクエストまたはバックグラウンド処理
症状:
- リクエストが完了前に終了。
- 504 Gateway Timeout エラー。
- 処理が予期せず停止。
- 不安定なタイムアウト動作。
なぜこれが問題か: Cloud Run は複数のタイムアウト設定を持ち、それらを統一する必要があります:
- リクエストタイムアウト(デフォルト 300s、HTTP 最大 3600s、gRPC 最大 60m)
- クライアントタイムアウト
- ダウンストリームサービスタイムアウト
- ロードバランサータイムアウト(外部アクセス)
推奨される修正:
一貫性のあるタイムアウトを設定
# リクエストタイムアウトを増加(HTTP の場合最大 3600s)
gcloud run deploy my-service \
--timeout=900 # 15 分
ウェブフックで長時間実行に対応
from fastapi import FastAPI, BackgroundTasks
import httpx
app = FastAPI()
@app.post("/process")
async def process(data: dict, background_tasks: BackgroundTasks):
task_id = create_task_id()
# バックグラウンド処理を開始
background_tasks.add_task(
long_running_process,
task_id,
data,
data.get("callback_url")
)
# 直ちに返す
return {"task_id": task_id, "status": "processing"}
async def long_running_process(task_id, data, callback_url):
result = await heavy_computation(data)
# 完了時にコールバック
if callback_url:
async with httpx.AsyncClient() as client:
await client.post(callback_url, json={
"task_id": task_id,
"result": result
})
信頼できる長時間実行のために Cloud Tasks を使用
from google.cloud import tasks_v2
def create_long_running_task(data):
client = tasks_v2.CloudTasksClient()
parent = client.queue_path(PROJECT, REGION, "long-tasks")
task = {
"http_request": {
"http_method": tasks_v2.HttpMethod.POST,
"url": "https://worker.run.app/process",
"body": json.dumps(data).encode(),
"headers": {"Content-Type": "application/json"}
},
"dispatch_deadline": {"seconds": 1800} # 30 分
}
return client.create_task(parent=parent, task=task)
長いレスポンスのためのストリーミング
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
@app.get("/large-report")
async def large_report():
async def generate():
for chunk in process_large_data():
yield chunk
return StreamingResponse(generate(), media_type="text/plain")
検証チェック
GCP 認証情報のハードコード
重大度: エラー
GCP 認証情報はソースコードに絶対にハードコードしてはいけません
メッセージ: GCP サービスアカウント認証情報がハードコード。Secret Manager または Workload Identity を使用してください。
GCP API キーのソースコード配置
重大度: エラー
API キーは Secret Manager を使用する必要があります
メッセージ: GCP API キーがハードコード。Secret Manager を使用してください。
リポジトリに認証情報 JSON ファイル
重大度: エラー
サービスアカウント JSON ファイルはソース管理に入れないでください
メッセージ: 認証情報ファイルが検出。.gitignore に追加し、Secret Manager を使用してください。
ルートユーザーとして実行
重大度: 警告
セキュリティ上、コンテナはルート として実行すべきではありません
メッセージ: Dockerfile がルートとして実行。セキュリティのため USER ディレクティブを追加してください。
Dockerfile にヘルスチェックがない
重大度: 情報
Cloud Run は HTTP ヘルスチェックを使用し、Dockerfile の HEALTHCHECK はオプションです
メッセージ: Dockerfile に HEALTHCHECK なし。Cloud Run は独自のヘルスチェックを使用します。
Dockerfile にハードコードされたポート
重大度: 警告
ポートは PORT 環境変数から来るべき
メッセージ: ハードコードされたポート。Cloud Run に対して PORT 環境変数を使用してください。
/tmp への大きなファイル書き込み
重大度: 警告
/tmp 書き込みはコンテナメモリを消費します。大きな書き込みは OOM を引き起こします
メッセージ: /tmp 書き込みがメモリを消費。大きなファイルは Cloud Storage を検討してください。
同期ファイル操作
重大度: 警告
同期ファイル操作は非同期アプリケーションのイベントループをブロック
メッセージ: 同期ファイル操作。より良い同時実行のために非同期バージョンを使用してください。
グローバルミュータブル状態
重大度: 警告
グローバル状態は同時リクエストで問題を引き起こす可能性があります
メッセージ: グローバルミュータブル状態が同時リクエストで問題を引き起こす可能性があります。
スレッド不安全シングルトンパターン
重大度: 警告
シングルトンは同時実行性 > 1 でスレッド安全である必要があります
メッセージ: シングルトンパターン - 同時実行性 > 1 を使用する場合はスレッド安全を確保してください。
協業
デリゲーショントリガー
- ユーザーが AWS サーバーレス を必要とする -> aws-serverless (Lambda, API Gateway, SAM)
- ユーザーが Azure コンテナが必要 -> azure-functions (Azure Container Apps, Functions)
- ユーザーが データベース設計 を必要とする -> postgres-wizard (Cloud SQL 設計, AlloyDB)
- ユーザーが 認証 を必要とする -> auth-specialist (Firebase Auth, Identity Platform)
- ユーザーが AI 統合 を必要とする -> llm-architect (Vertex AI, Cloud Run + LLM)
- ユーザーが ワークフロー オーケストレーション を必要とする -> workflow-automation (Cloud Workflows, Eventarc)
使用時機
このスキルは、リクエストが上記で説明された機能とパターンに明らかに適合する場合に使用します。
制限事項
- このスキルは、上記で説明されたスコープに明らかに適合するタスクのみに使用してください。
- 出力をクラウド環境固有の検証、テスト、またはエキスパートレビューの代替として扱わないでください。
- 必要な入力、権限、安全境界、または成功基準が不明確な場合は、説明を求めて停止してください。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- sickn33
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/sickn33/antigravity-awesome-skills / ライセンス: MIT
関連スキル
superpowers-streamer-cli
SuperPowers デスクトップストリーマーの npm パッケージをインストール、ログイン、実行、トラブルシューティングできます。ユーザーが npm から `superpowers-ai` をセットアップしたい場合、メールまたは電話でサインインもしくはアカウント作成を行いたい場合、ストリーマーを起動したい場合、表示されたコントロールリンクを開きたい場合、後で停止したい場合、またはソースコードへのアクセスなしに npm やランタイムの一般的な問題から復旧したい場合に使用します。
catc-client-ops
Catalyst Centerのクライアント操作・監視機能 - 有線・無線クライアントのリスト表示・フィルタリング、MACアドレスによる詳細なクライアント検索、クライアント数分析、時間軸での分析、SSIDおよび周波数帯によるフィルタリング、無線トラブルシューティング機能を提供します。MACアドレスやIPアドレスでのクライアント検索、サイト別やSSID別のクライアント数集計、無線周波数帯の分布分析、Wi-Fi信号の問題調査が必要な場合に活用できます。
ci-cd-and-automation
CI/CDパイプラインの設定を自動化します。ビルドおよびデプロイメントパイプラインの構築または変更時に使用できます。品質ゲートの自動化、CI内のテストランナー設定、またはデプロイメント戦略の確立が必要な場合に活用します。
shipping-and-launch
本番環境へのリリース準備を行います。本番環境へのデプロイ準備が必要な場合、リリース前チェックリストが必要な場合、監視機能の設定を行う場合、段階的なロールアウトを計画する場合、またはロールバック戦略が必要な場合に使用します。
linear-release-setup
Linear Releaseに向けたCI/CD設定を生成します。リリース追跡の設定、LinearのCIパイプライン構築、またはLinearリリースとのデプロイメント連携を実施する際に利用できます。GitHub Actions、GitLab CI、CircleCIなど複数のプラットフォームに対応しています。
tracking-application-response-times
API エンドポイント、データベースクエリ、サービスコール全体にわたるアプリケーションのレスポンスタイムを追跡・最適化できます。パフォーマンス監視やボトルネック特定の際に活用してください。「レスポンスタイムを追跡する」「API パフォーマンスを監視する」「遅延を分析する」といった表現で呼び出せます。