0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloud RunとVercelで詰まった!コストとデプロイの最適解

0
Posted at

多くのエンジニアが、プロダクトの成長とともに「GCP Cloud RunとVercel、どちらをどう使い分けるべきか?」という壁にぶつかります。特に、コスト最適化とデプロイ速度の両立は、多くのプロジェクトで悩みの種となるでしょう。

この記事では、GCP Cloud RunとVercelそれぞれの特性を深掘りし、実務で直面しやすい課題に対する具体的な解決策と、コストとデプロイ速度を両立させるための最適なアーキテクチャ設計指針を提供します。

多くの人が誤解しているCloud RunとVercelの特性とコスト

Cloud RunとVercelはどちらもサーバーレスなデプロイ環境を提供しますが、その設計思想と最適なユースケースは大きく異なります。このセクションでは、それぞれのサービスの基本的な特性と、多くの人が見落としがちなコスト構造のポイントを解説します。

GCP Cloud Runの特性と料金体系

Cloud Runは、任意のDockerコンテナをフルマネージドなサーバーレス環境で実行できるGCPのサービスです。これにより、開発者は言語やフレームワークの制約なく、インフラ管理の手間なくアプリケーションをデプロイできます。

  • フルマネージドコンテナプラットフォーム: Dockerイメージをそのまま実行できるため、既存のアプリケーションの移行が容易です。任意のプログラミング言語やフレームワーク(Node.js, Python, Java, Go, Ruby, PHPなど)に対応しています。
  • オートスケーリング: リクエスト数に応じてコンテナインスタンスが自動的にゼロからスケールアップ/ダウンします。トラフィックがないときはインスタンスが停止し、課金が発生しないため、コスト効率が非常に高いのが特徴です。
  • 料金体系: 従量課金制で、使用したリソースに対して100ミリ秒単位で課金されます。毎月一定の無料枠(180,000 vCPU秒、360,000 GiB秒、200万リクエストまで)が提供されるため、小規模なプロジェクトであれば無料で運用することも可能です。
  • GPUサポート: AI推論などのGPUを必要とするワークロードにも対応しており、NVIDIA L4 GPUをオンデマンドで利用できます。5秒で起動し、ゼロへのスケーリングもサポートします。
  • 確約利用割引 (CUD): Cloud Runの継続的な使用に対して割引が適用される確約利用割引(CUD)も提供されており、長期的な運用コストをさらに削減できます。

Vercelの特性と料金体系

Vercelは、Next.jsの開発元が提供するフロントエンド開発に特化したプラットフォームです。特に静的サイトやJamstackアプリケーションのデプロイと配信に強みがあります。

  • フロントエンド開発に特化: Next.jsアプリケーションのデプロイに最適化されており、SSR/SSGを含むモダンなWebアプリケーションを簡単にデプロイできます。
  • Git連携と自動デプロイ: GitHub, GitLab, Bitbucketとの連携が非常に強力で、Gitプッシュだけで自動的にデプロイが開始されます。Pull RequestごとにプレビューURLが自動生成されるため、開発体験が非常に優れています。
  • グローバルCDN: デフォルトでグローバルCDNが利用でき、世界中のどこからでも高速なコンテンツ配信が可能です。
  • Serverless Functions & Edge Functions: Node.jsベースのServerless Functionsと、より低レイテンシーなEdge Functionsを提供します。これらを活用することで、APIエンドポイントや認証プロキシなどのバックエンドロジックもVercel上で実装できます。
  • 料金体系: Hobbyプラン(個人利用向け無料)から始まり、Proプラン(月額$20/ユーザー)やEnterpriseプランが提供されます。無料枠が充実しており、小規模なプロジェクトであれば無料で利用できます。

Cloud RunとVercelを組み合わせた実践的なデプロイ戦略

このセクションでは、Cloud RunとVercelをそれぞれの強みを活かして組み合わせる具体的なデプロイ戦略と、そのための実装例を紹介します。

Cloud RunへのNext.jsアプリケーションデプロイ

Next.jsアプリケーションをCloud Runにデプロイする最も効率的な方法は、Next.js 12.2以降で導入されたStandaloneモードを活用することです。これにより、本番環境に必要なファイルだけをコンテナイメージに含めることができ、イメージサイズを削減し、起動時間を短縮できます。

まず、next.config.jsでStandaloneモードを有効にします。

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: "standalone", // Standaloneモードを有効化
};
module.exports = nextConfig;

次に、マルチステージビルドのDockerfileを作成し、Bunをランタイムとして使用する例を示します。Bunを使用することで、依存関係のインストールとビルドが高速化されます。

# --- ビルドステージ ---
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build

# --- 実行ステージ ---
FROM oven/bun:1 AS runner
WORKDIR /app
ENV NODE_ENV=production
# Standaloneモードで生成されたファイルをコピー
COPY --from=builder /app/.next/standalone ./
# 静的アセットをコピー
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
# Next.jsアプリケーションをBunで起動
CMD ["bun", "start"]

最後に、Cloud Runへデプロイするコマンドです。

# Dockerイメージをビルドし、Artifact Registry (またはContainer Registry) にプッシュ
gcloud builds submit --tag gcr.io/YOUR_PROJECT_ID/your-nextjs-app

# Cloud Runサービスとしてデプロイ
gcloud run deploy your-nextjs-app \
  --image gcr.io/YOUR_PROJECT_ID/your-nextjs-app \
  --platform managed \
  --region YOUR_REGION \
  --allow-unauthenticated # 認証なしでアクセスを許可する場合

YOUR_PROJECT_IDYOUR_REGIONはご自身の環境に合わせて変更してください。これにより、Next.jsアプリケーションがCloud Run上で高速かつコスト効率良く動作します。

VercelからCloud Runへのセキュアな認証(Workload Identity Federation)

VercelでホストされたフロントエンドからCloud Run上のバックエンドAPIを呼び出す際、サービスアカウントキーを直接Vercelに設定するのはセキュリティリスクが高いです。GCP Workload Identity FederationとVercel OIDCを組み合わせることで、キー管理なしでセキュアな認証を実現できます。

この仕組みでは、Vercelが発行するOIDCトークンをGCPが信頼し、そのトークンに基づいて一時的なGCPサービスアカウントの権限を付与します。

Vercel FunctionからCloud Runバックエンドを呼び出す例 (Next.js App Routerを想定):

// Vercel Function (api/backend/route.ts など)
import { GoogleAuth } from 'google-auth-library';
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const backend_url = process.env.CLOUD_RUN_SERVICE_URL; // Cloud RunサービスのURLを環境変数で設定
  const gcpProjectId = process.env.GCP_PROJECT_ID;
  const gcpProjectNumber = process.env.GCP_PROJECT_NUMBER;
  const gcpWorkloadIdentityPoolId = process.env.GCP_WORKLOAD_IDENTITY_POOL_ID;
  const gcpWorkloadIdentityPoolProviderId = process.env.GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID;
  const gcpServiceAccountEmail = process.env.GCP_SERVICE_ACCOUNT_EMAIL;

  if (!backend_url || !gcpProjectId || !gcpProjectNumber || !gcpWorkloadIdentityPoolId || !gcpWorkloadIdentityPoolProviderId || !gcpServiceAccountEmail) {
    return NextResponse.json({ error: "Missing environment variables for GCP Workload Identity Federation" }, { status: 500 });
  }

  const auth = new GoogleAuth({
    scopes: "https://www.googleapis.com/auth/cloud-platform",
    projectId: gcpProjectId,
    credentials: {
      type: "external_account",
      audience: `//iam.googleapis.com/projects/${gcpProjectNumber}/locations/global/workloadIdentityPools/${gcpWorkloadIdentityPoolId}/providers/${gcpWorkloadIdentityPoolProviderId}`,
      subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
      token_url: "https://sts.googleapis.com/v1/token",
      service_account_impersonation_url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${gcpServiceAccountEmail}:generateAccessToken`,
      subject_token_supplier: {
        getSubjectToken: async () => {
          // Vercel環境で自動的にx-vercel-oidc-tokenヘッダーが設定される
          const oidcToken = request.headers.get('x-vercel-oidc-token');
          if (!oidcToken) {
            throw new Error("Vercel OIDC token not found in request headers.");
          }
          return oidcToken;
        },
      },
    },
  });

  try {
    const client = await auth.getIdTokenClient(backend_url);
    const response = await client.request({ url: backend_url, method: 'GET' });
    return NextResponse.json(response.data, { status: 200 });
  } catch (error: any) {
    console.error("Error calling Cloud Run backend:", error);
    return NextResponse.json({ error: "Failed to call backend", details: error.message }, { status: 500 });
  }
}

注意点: 上記コードは概念的なものであり、Vercel OIDCとGCP Workload Identity Federationの具体的な設定手順は、それぞれの公式ドキュメントを参照し、最新かつ正確な情報を確認してください。特にx-vercel-oidc-tokenヘッダーはVercel Functionがデプロイされた環境で自動的に付与されますが、ローカル開発環境でのテストには別の認証メカニズムが必要になる場合があります。

Cloud RunとVercelでハマりがちなポイントと解決策

ここでは、Cloud RunとVercelを運用する上でよく発生する課題と、その具体的な回避策を解説します。これを知らないとコスト増大やデプロイの遅延につながる可能性があります。

1. Cloud Runのコールドスタート問題とコスト最適化

Cloud Runのコールドスタートは、トラフィックがない状態でインスタンスが停止し、最初のリクエスト時に起動に時間がかかる現象です。特にNext.jsのようなフレームワークは起動に時間がかかる傾向があり、ユーザー体験に悪影響を与える可能性があります。

  • ハマりどころ: ゼロへのスケーリングによるコスト効率の良さが、レイテンシーの増大というトレードオフを生む。
  • 回避策:
    • min-instancesの設定: レイテンシーが重要なアプリケーションでは、min-instancesを1以上に設定し、常にインスタンスを稼働させておくことでコールドスタートを回避できます。ただし、インスタンスが稼働している間は課金が発生するため、コストは増加します。
      gcloud run deploy your-nextjs-app --min-instances 1 --region YOUR_REGION ...
      
    • イメージの最適化と高速ランタイム: Next.jsのStandaloneモードの活用に加え、Dockerイメージを軽量化し、Bunのような高速なランタイムを使用することで、起動時間を短縮します。Alpine Linuxベースのイメージを利用するのも有効です。
    • ウォームアップリクエスト: 定期的にヘルスチェックやウォームアップリクエストを送信してインスタンスを維持する方法も考えられますが、min-instancesの方が管理が容易で推奨されます。

2. Vercel Serverless Functionsのタイムアウトと処理のオフロード

VercelのServerless Functionsには実行時間制限があります。Hobbyプランでは10秒、Proプランでは60秒、Enterpriseプランでは最大300秒という制限があり、長時間かかる処理ではタイムアウト(504エラー)が発生することがあります。

  • ハマりどころ: VercelのServerless Functionsで複雑な処理やデータ処理を行うと、タイムアウトによりエラーが発生する。
  • 回避策:
    • 処理のオフロード: 長時間かかる処理や大量の計算リソースを必要とする処理は、Cloud Runなどのより汎用的なコンテナプラットフォームにオフロードし、VercelからはそのAPIを呼び出す構成にしましょう。Cloud Runは実行時間の制限が事実上ありません。
    • 処理の分割と最適化: 処理を細かく分割し、各関数が短い時間で完了するように最適化します。非同期処理やストリーミングを活用することも検討してください。
    • Edge Functionsの使い分け: VercelのEdge Functionsは超低レイテンシーを目的とした軽量な関数であり、さらに実行時間制限が短い(5秒)です。用途に応じてServerless FunctionsとEdge Functionsを適切に使い分けましょう。

3. Cloud RunとVercel間のCORSエラー

VercelでホストされたフロントエンドからCloud Run上のバックエンドAPIにリクエストを送信する際、CORS (Cross-Origin Resource Sharing) エラーが発生することがよくあります。これはセキュリティ上の制約であり、適切に設定する必要があります。

  • ハマりどころ: ブラウザからCloud Run APIを呼び出すと、CORSポリシー違反でリクエストがブロックされる。
  • 回避策:
    • Cloud Runサービス側でのCORSヘッダー設定: Cloud Runサービス側で、許可するオリジン、HTTPメソッド、ヘッダーを適切に設定する必要があります。例えば、Honoフレームワークを使用している場合は、以下のように設定できます。
      // Hono + Cloud Run の場合
      import { Hono } from 'hono';
      import { cors } from 'hono/cors';
      
      const app = new Hono();
      
      app.use(cors({
        origin: ['https://your-vercel-app.vercel.app', 'http://localhost:3000'], // VercelのデプロイURLとローカル開発環境のURL
        allowHeaders: ['Content-Type', 'Authorization'],
        allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
        maxAge: 600, // プリフライトリクエストの結果をキャッシュする秒数
      }));
      
      // その他のAPIエンドポイント
      app.get('/api/data', (c) => c.json({ message: 'Hello from Cloud Run!' }));
      
      export default app;
      
    • プリフライトリクエストへの対応: ブラウザはCORSポリシー確認のため、実際のHTTPリクエストの前にOPTIONSメソッドでプリフライトリクエストを送信します。サーバー側がこのOPTIONSリクエストに正しく応答するように設定されていることを確認してください。多くのCORSミドルウェアはこれを自動的に処理します。

4. Cloud Buildのビルド時間とデプロイ速度の最適化

Next.jsのようなアプリケーションのビルドは、依存関係のインストールやイメージ作成に時間がかかり、CI/CDパイプライン全体のデプロイ速度に影響を与えることがあります。これは、本番環境へのリリース頻度や開発体験に直結する重要な課題です。

  • ハマりどころ: gcloud builds submitコマンド実行時に、ビルドが長時間かかり、デプロイが遅延する。
  • 回避策:
    • Cloud Buildロケーションの最適化: Cloud Buildの実行ロケーションをArtifact Registry(またはContainer Registry)と同じリージョンに設定することで、イメージのプッシュ/プル時のネットワークレイテンシーを削減できます。
    • 高速パッケージマネージャーの利用: bunpnpmなど、依存関係のインストールが高速なパッケージマネージャーを使用し、--frozen-lockfileなどの機能でキャッシュを有効活用しましょう。
    • Cloud Buildマシンタイプの高性能化: 必要に応じて、Cloud Buildのビルドに利用するマシンタイプをE2_HIGHCPU_8E2_HIGHCPU_32などの高性能なものに設定することで、ビルド時間を大幅に短縮できます。ただし、これに伴いコストは増加します。
      # cloudbuild.yaml の例
      steps:
      - name: 'gcr.io/cloud-builders/docker'
        args: ['build', '-t', 'gcr.io/YOUR_PROJECT_ID/your-nextjs-app', '.']
        # machineType: 'E2_HIGHCPU_8' # 必要に応じて設定
      images: ['gcr.io/YOUR_PROJECT_ID/your-nextjs-app']
      
    • Dockerマルチステージビルドの活用: 前述のNext.jsデプロイ例のように、マルチステージビルドを適切に利用することで、最終的なコンテナイメージのサイズを最小化できます。これにより、イメージのプッシュ/プル時間だけでなく、Cloud Runでの起動時間も改善されます。

Cloud RunとVercelの最適な使い分けと設計上のトレードオフ

Cloud RunとVercelはそれぞれ異なる強みと弱みを持っています。プロジェクトの要件に応じて、どちらか一方を選択するか、あるいは両方を組み合わせるかを判断するための設計指針とトレードオフを解説します。

Cloud Runのトレードオフとベストプラクティス

Cloud Runは汎用性と柔軟性が高い反面、GCPの他のサービスとの連携やコンテナ管理に関する知識が求められます。

  • トレードオフ:
    • 汎用性と学習コスト: Dockerコンテナを自由に実行できるため高い汎用性を持つが、コンテナイメージのビルド、管理、GCPの他のサービス(IAM, VPCなど)との連携には一定の知識が必要となります。
    • コールドスタートとコスト: ゼロへのスケーリングはコスト効率が良い反面、コールドスタートが発生する可能性があります。レイテンシーが重要なアプリケーションでは、min-instancesの設定によるコスト増を許容する必要がある場合があります。
  • ベストプラクティス:
    • コンテナイメージの最適化: マルチステージビルドを活用し、最終的なイメージサイズを最小化します。不要なファイルや依存関係を含めないように徹底します。
    • 最小インスタンス数の設定: レイテンシーが重要なAPIやWebアプリケーションの場合、コールドスタートを避けるために最小インスタンス数を1以上に設定することを検討します。
    • GCPサービスとの統合: Cloud MonitoringでCPU使用率、リクエスト数、レイテンシーなどのメトリクスを監視し、アラートを設定します。Cloud Loggingでアプリケーションログを一元管理し、トラブルシューティングを容易にします。
    • サーバーレスVPCアクセス: データベース(Cloud SQL)やキャッシュ(Memorystore)など、プライベートネットワーク内のリソースにアクセスする必要がある場合は、サーバーレスVPCアクセスを利用してセキュアな通信経路を確保します。

Vercel Functionsのトレードオフとベストプラクティス

Vercelはフロントエンド開発に特化しており、優れた開発体験を提供しますが、その一方で実行環境には制約があります。

  • トレードオフ:
    • フロントエンド特化と制約: フロントエンド開発に最適化されており開発体験が非常に優れていますが、Serverless Functionsには実行時間制限やメモリ制限などの制約があります。大規模な計算処理や長時間実行される処理には不向きです。
    • IaaCの柔軟性: 基本的にVercelダッシュボードからの設定変更が中心となり、インフラ設定をコードで管理するIaaCの実現はGCPほど柔軟ではない場合があります。
  • ベストプラクティス:
    • 用途の明確化: 静的サイト、SSR/SSGのNext.jsアプリケーション、軽量なAPIエンドポイント、認証プロキシなど、フロントエンド寄りのワークロードにVercel Functionsを使用します。
    • 重い処理のオフロード: 長時間実行される処理や大量の計算リソースを必要とする処理は、Cloud Runなどの汎用的なサービスにオフロードすることを徹底します。
    • Edge Functionsの活用: 超低レイテンシーが求められる処理(認証、リダイレクト、A/Bテスト、アクセス制御など)にはEdge Functionsを積極的に利用し、ユーザー体験を向上させます。
    • 環境変数管理: Vercelの環境変数管理機能を活用し、機密情報を安全に管理します。環境変数はGitにコミットしないように注意しましょう。

Cloud RunとVercelの最適な併用戦略

最も一般的なかつ効果的な戦略は、Vercelをフロントエンド、Cloud Runをバックエンドとして使い分けることです。

  • 役割分担:
    • Vercel: 高速デプロイ、グローバルCDN、優れた開発者体験、Next.jsとの高い親和性を活かし、ユーザーに直接提供するWebフロントエンド(SSR/SSGを含む)をホストします。これにより、初期表示速度の向上と開発効率の最大化を図ります。
    • Cloud Run: 重いバックエンドAPI、バッチ処理、データベースアクセス、外部サービス連携、画像処理など、より柔軟なコンテナ実行環境が必要な処理を担います。これにより、Vercel Functionsの制約を回避しつつ、スケーラブルで堅牢なバックエンドを構築できます。
  • APIゲートウェイを介した連携: VercelからCloud RunのAPIを直接呼び出すのではなく、VercelのServerless FunctionsをAPIゲートウェイとして利用することを検討します。これにより、認証処理、ルーティング、簡単なデータ変換などをフロントエンドに近い場所で実行でき、Cloud RunのURLを直接フロントエンドに公開せずに済みます。
  • マイクロフロントエンド/マイクロサービスアーキテクチャ: 各レイヤーに最適なサービスを選択することで、よりスケーラブルで保守性の高いアーキテクチャを構築できます。例えば、認証はVercel Edge Functions、ユーザー管理はCloud Run上のAPI、決済は別のサービス、といった具合に役割を分担することで、それぞれのサービスの特性を最大限に活かせます。

まとめ:Cloud RunとVercelでコストとデプロイを最適化する

GCP Cloud RunとVercelは、どちらも現代のWebアプリケーション開発において強力なツールですが、それぞれの特性を理解し、適切に使い分けることがコスト最適化とデプロイ速度向上の鍵となります。

  • Cloud Run: 汎用的なバックエンドAPI、計算量の多い処理、データ処理、またはNext.jsのSSR/SSGを自前で完全にコントロールしたい場合に最適です。コールドスタート対策としてmin-instancesの検討が重要になります。
  • Vercel: フロントエンドアプリケーション(特にNext.js)の高速デプロイ、グローバルCDNによる高速配信、優れた開発者体験が必要な場合に最適です。Serverless Functionsのタイムアウトには注意し、重い処理はCloud Runにオフロードする戦略を取りましょう。

両サービスを組み合わせることで、Vercelのフロントエンドの俊敏性とCloud Runのバックエンドの柔軟性を両立させ、最高の開発体験とパフォーマンス、そしてコスト効率を実現できます。

本記事で紹介した実装例やハマりどころを参考に、あなたのプロジェクトに最適なデプロイ戦略を見つけてください。さらに詳細な情報は、各サービスの公式ドキュメントで確認することをお勧めします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?