1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS UserのGCP浮気日記 Day2[サーバーレスアーキテクチャでのコンピューティングサービス(GCP CloudRun と AWS Lambda)]

Posted at

クラウドサービスの選定において、サーバーレスアーキテクチャは運用負荷の削減やスケーラビリティの向上という観点からよく使う方もいるのではないでしょうか?ここでは、コンテナベースのCloud Runと関数単位実行を中心とするAWS Lambdaについて,各サービスの機能や構築方法、活用シーンを比較検討します.

CloudRunとLambdaを比べるのは果たしてどうなんですかね...
有名サービスの比較をした結果案外知らなかったことも見つかったので記事にしました

両サービスとも従量課金制であるため、コスト効率や運用の柔軟性についても考えてみましょう.
本記事は、Google CloudのCloud RunとAWSのLambdaについて、各サービスの概念、仕様、利用シーン及び構築方法を包括的に説明するとともに、それぞれの特徴に基づいた使い分けのポイントを解説するものである。各サービスの基本的な設計思想から運用上のメリット・デメリットまで、段階的に詳述する。

IAM AWS User クラウドサービスをフル活用しよう!

AWS(本妻?)のサービスの紹介・考察,使用事例の紹介,オンプレとの比較をするシリーズ

シリーズ AWS UserのGCP浮気日記

Day1 コンピュートとRDBMSのVPC内接続

Cloud Runの概要と特徴

image.png

Cloud RunはGoogle Cloudが提供するコンテナベースのサーバーレス実行環境である。Cloud RunはKnativeを基盤に動作しており、HTTPリクエストに応じた自動スケーリングが可能で、リクエスト数の増減に応じてインスタンスを迅速にスケールアウト、スケールダウンするという特徴がある。コンテナイメージを直接デプロイする仕組みのため、プログラミング言語やランタイムの制約がなく、アプリケーションの依存関係が複雑な場合やカスタムミドルウェアを用いる必要があるシーンで柔軟に対応できる。この特性により、既存のDockerコンテナを活用した移行や、マイクロサービスアーキテクチャへの採用が進んでいるのである。

Cloud Runは、HTTPをトリガーとするウェブアプリケーションやAPIサーバーに最適化されており、イベントドリブンなシステムにも適用可能であるが、基本的にはステートレスなサービスとして設計されるため、状態管理が求められる場合は外部データベースやキャッシュサービスと組み合わせる必要がある。さらに、サービスのオートスケーリング機能によって、突発的なトラフィックの急増にも柔軟に対応できる運用設計が成り立っているのである。

AWS Lambdaの概要と特徴

image.png

AWS LambdaはAmazon Web Servicesが提供する、イベントドリブンなサーバーレス実行環境である。Lambdaは関数単位でコードが管理され、各種AWSサービス(API Gateway、S3、CloudWatch Events、DynamoDBなど)からのトリガーに対応して動作する。そのため、短期間で完了する処理やリアルタイムのデータ処理、イベント応答型のシステムにおいて非常に有用である。Lambdaは各関数が特定のランタイム環境上で実行され、Node.js、Python、Java、Goなど複数の言語に対応しているが、実行時間やメモリ使用量、パッケージサイズに制約が設けられている。

Lambdaは、コードのデプロイが容易であり、コード更新時の短いデプロイサイクルが特徴である。関数を個別に管理する設計は、ビジネスロジックが明確に分離され、マイクロサービスアーキテクチャの一部としても利用しやすい。加えて、トリガーによる自動実行およびスケーリング機能により、負荷に応じた柔軟な処理能力を提供する。だが、Lambdaの実行環境はステートレスであり、長時間実行する必要がある場合や外部ライブラリの依存が大きい場合には、注意が必要である。

各サービスの構築方法

今回はフロントエンドとバックエンド双方のアプリケーションをdockerで立ち上げる形で両サービスを使用する方法を記載します.

ホスティングするフロントエンドアプリケーション

以下のコマンドでNext.jsアプリケーションを作成する

npx create-next-app@latest --ts

全体のディレクトリ構成は以下の通りです.CloudRunやLambdaにコンテナベースでホスティングする予定のためDoclerfileも作成する.

.
├── Dockerfile
├── eslint.config.mjs
├── next-env.d.ts
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── public
│   ├── file.svg
│   ├── globe.svg
│   ├── next.svg
│   ├── vercel.svg
│   └── window.svg
├── src
│   └── app
│       ├── layout.tsx
│       └── page.tsx
└── tsconfig.json

表示するページレイアウトは以下の通り.

stc/app/page.tsx
"use client";

import React from 'react';

const Home: React.FC = () => {
  return (
      <div className="container">
        <main>
          <h1 className="title">Qiita Welcome!!</h1>
          <p className="description">
            Next.js フロントエンドを使ったサンプルアプリケーションです.
          </p>
          <button className="actionBtn">Get Started</button>
        </main>
        <style jsx>{`
        .container {
          min-height: 100vh;
          display: flex;
          align-items: center;
          justify-content: center;
          padding: 2rem;
          background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
        }
        main {
          background-color: #ffffff;
          padding: 3rem;
          border-radius: 8px;
          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
          text-align: center;
          max-width: 600px;
          width: 100%;
        }
        .title {
          font-size: 3rem;
          margin: 0;
          color: #2c3e50;
          letter-spacing: 0.05em;
        }
        .description {
          margin: 1.5rem 0;
          font-size: 1.25rem;
          color: #7f8c8d;
        }
        .actionBtn {
          background-color: #2ecc71;
          border: none;
          color: white;
          padding: 1rem 2rem;
          text-transform: uppercase;
          font-size: 1rem;
          border-radius: 4px;
          cursor: pointer;
          transition: background-color 0.3s ease;
        }
        .actionBtn:hover {
          background-color: #27ae60;
        }
        @media (max-width: 600px) {
          main {
            padding: 2rem;
          }
          .title {
            font-size: 2.5rem;
          }
          .description {
            font-size: 1rem;
          }
          .actionBtn {
            font-size: 0.9rem;
            padding: 0.8rem 1.5rem;
          }
        }
      `}</style>
      </div>
  );
};

export default Home;

Dockerfileは以下の通り.

FROM node:19-alpine

WORKDIR /app

COPY package.json ./
RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "run", "start"]

見た目

スクリーンショット 2025-03-01 18.32.45.png

GET STARTEDを押しても何も起きません...

デプロイするバックエンドアプリケーション

以下のコマンドでgoアプリケーションを作成する.

go mod init backend

全体のディレクトリ構成は以下のようになっている.本当に必要最小限の構成である.

.
├── Dockerfile
├── go.mod
└── main.go

作成されるgo.modは以下の通りである.使用するパッケージは何もない.(そりゃそう)

go.mod
module backend

go 1.23.4

単純にHello World!を返すAPIを作成した.

main.go
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello World! from Go backend!")
	})
	log.Println("Starting Go server on port 8080...")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatalf("Error starting server: %v", err)
	}
}

Dockerfileは以下の通り.

# ビルド用ステージ
FROM golang:1.23-alpine AS builder

WORKDIR /app

COPY main.go .

COPY go.mod go.sum ./
RUN go mod download

COPY . .


RUN GOOS=linux GOARCH=amd64 go build -o backend main.go

# 実行用ステージ
FROM alpine:latest

WORKDIR /app

COPY --from=builder /app/backend .

RUN chmod +x backend

EXPOSE 8080

CMD ["./backend"]

Cloud Runの構築方法

初めにCloud Runの構築方法を記載する.フロントエンドとバックエンド両方とも以下のようなアーキテクチャを想定しています.

ログインしているIAMユーザーがオーナー権限を持っていない場合は適切なIAMロールをアタッチする必要があります.
この操作をするのに必要なIAMロールは以下の通りです.
Cloud Run 関連

  • Cloud Run 管理者 (roles/run.admin)
    Cloud Run のサービス作成、更新、削除など、管理操作を行うための権限が含まれている.
  • サービスアカウント ユーザー (roles/iam.serviceAccountUser)
    デプロイ時に指定するサービスアカウントを利用して Cloud Run サービスを動かす場合、このロールを対象に付与する必要がある.

Artifact Registry 関連

  • Artifact Registry ライター (roles/artifactregistry.writer)
    コンテナイメージなどのアーティファクトを Artifact Registry にプッシュするために必要な最小限の権限です。
  • Artifact Registry 管理者 (roles/artifactregistry.admin)
    リポジトリの作成や管理など、より広範な操作が必要な場合に使用します。

かなり簡単にしています

スクリーンショット 2025-03-01 14.09.58.png

Google Cloud Artifact Registryとは?

スクリーンショット 2025-03-01 22.28.11.png

Google Cloud Artifact Registryは、Google Cloudが提供する完全マネージド型のソフトウェアアーティファクト管理サービスである。従来のContainer Registryから進化し、コンテナイメージに加え、Maven、npm、Pythonなど複数のパッケージフォーマットを統一的に管理できる。さらに、IAMによる細やかなアクセス制御や、Cloud Build、GKE、Cloud Runとの連携により、セキュアかつ効率的なCI/CDパイプラインの構築が実現されるのである。

Artifact Registryにコンテナイメージをpushする

初めにArtifact Registryにリポジトリを作成する.まずArtifactRegistryのページを開き,「リポジトリの作成」を選択.
スクリーンショット 2025-03-01 22.38.16.png

以下のようにリポジトリの詳細を設定する.
スクリーンショット 2025-03-01 22.37.56.png

  • 形式
    • Docker
  • モード
    • 標準
  • ロケーションタイプ
    • リージョン

リージョンを指定した場合、アーティファクトは特定の地理的リージョン内に格納されるため、低レイテンシやデータ主権の観点で有利である。一方、マルチリージョンを選択すると、アーティファクトは複数のリージョンに自動的にレプリケートされ、グローバルな可用性とアクセス性が向上するが、その分、レプリケーションに伴うコストや地域ごとの細かな制御が難しくなる

  • 暗号化
    • Googleが管理する暗号化

リソースはデフォルトでGoogleが管理する鍵によって暗号化されるので、利用者は自ら鍵の管理を行わずとも暗号化の恩恵を受けることができる。しかし、自身で暗号化鍵管理を必要とする場合には、Cloud KMS鍵(顧客が所有する鍵)を使用でき、Autokey機能によりCloud KMS鍵の作成が自動化される。Google管理の鍵は運用がシンプルである一方、Cloud KMS鍵はより厳密なセキュリティポリシーを適用する場合に有用

  • 不変のイメージタグ
    • 無効

不変のイメージタグとは常に同じイメージダイジェストを指すものであり、これらのタグや対応するイメージを意図的に削除・上書きできなくする設定である。これにより、タグを参照する環境における一貫性と再現性が保証されるが、同じタグが既に別のバージョンに使用されている場合、新たなプッシュが拒否されるため、運用時の注意が必要である。

  • クリーンアップポリシー
    • テストを実行

リポジトリ内のアーティファクトを自動で整理・削除するための基準を定義するものである。ポリシーを有効にして「アーティファクトを削除」を選択すると、指定された条件に合致するアーティファクトが自動的に削除される。逆に「テストを実行」を選択すると、削除処理そのものは行われず、削除対象となるアーティファクトの情報が評価・記録され、Cloud Audit Loggingにテストイベントとして送信されるので、実際の削除を行う前に動作確認が可能となる。

  • 脆弱性スキャン
    • 有効

リポジトリにプッシュされるイメージを自動的にスキャンし、セキュリティ上の脆弱性が存在しないか検出する機能である。脆弱性スキャンが有効であれば、イメージに含まれる潜在的なリスク情報が早期に把握できるが、現時点ではDockerの標準リポジトリおよびリモートリポジトリに対してのみサポートされるため、その利用範囲には制限がある。

ここからはgcloudコマンドを使用します

macの場合ですが...gcloudコマンド導入方法
brew install --cask google-cloud-sdk
上記コマンドが実行できたら
source '/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.zsh.inc'
source '/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/completion.zsh.inc'

リポジトリが作成できたらローカル上にあるアプリケーションをdockerイメージとしてpushする.
まず,gcloudの認証をする

gcloud auth login

イメージを push する前に、Google Cloud CLI を使用して Artifact Registry に対するリクエストを認証する
リージョン us-central1 の Docker リポジトリの認証を設定する.

リージョンに関してはリポジトリ作成時に設定したリージョンを使用する

gcloud auth configure-docker us-central1-docker.pkg.dev

初めにフロントエンドのDocker imageのbuildを行う.フロントエンドのDockerfileがあるディレクトリに移動し,以下のコマンドを実行する.

docker buildx build --platform linux/amd64 -t qiita-frontend-sample-image:tag1 .

次にDocker イメージにレポジトリ名をタグ付けをする.
これでイメージを特定の場所に push するように docker push コマンドが構成される。
次のコマンドを実行して、イメージに qiita-sample-frontend/qiita-sample-frontend-image:tag1 としてタグ付けする.

qiita-sample-frontendの部分はArtifact Registryで作成したリポジトリ名にすること
[PROJECTNAME]には実際にプロジェクト名を入力すること.プロジェクト名はここに書いてある文字列のことです.
スクリーンショット 2025-03-02 14.37.59.png

docker tag qiita-frontend-sample-image:tag1 us-central1-docker.pkg.dev/[PROJECTNAME]/qiita-sample-frontend/qiita-sample-frontend-image:tag1

最後に,イメージを Artifact Registry に push する.認証を構成してローカル イメージにタグ付けしたら、作成したリポジトリにイメージを push できる.
Docker イメージを push するには、次のコマンドを実行する.

docker push us-central1-docker.pkg.dev/[PROJECTNAME]/qiita-sample-frontend/qiita-sample-frontend-image:tag1

これでArtigfact Registryに以下のようにイメージがpushされていることが確認できる.

スクリーンショット 2025-03-02 14.40.04.png

同じような操作をバックエンド側も行うと以下の通りになる.

スクリーンショット 2025-03-02 14.45.29.png

Cloud Runの構築

Cloud Runのページを開き,「コンテナをデプロイ」を選択し,「サービス」を選択する.
スクリーンショット 2025-03-02 14.47.36.png

以下のようにCloud Runの詳細を設定する.
スクリーンショット 2025-03-02 14.56.43.png

  • コンテナイメージのURL
    • 先ほど作成したArtifact RegistryのDocker Image URL(「選択」から選択できます)
  • サービス名
    • 任意
  • リージョン
    • 任意
  • 認証
    • 未認証の呼び出しを許可(公開するAPIなので)
  • 未認証の呼び出しを許可
    • 公開する API やウェブサイトの場合、認証なしで誰でもサービスにアクセスできるように設定
  • 認証が必要
    • Cloud IAM を用いてアクセスできるユーザーを明示的に管理・認可
    • 組織で constraints/iam.allowedPolicyMemberDomains が設定されている場合、未認証の呼び出しが禁止されることがある
  • 課金
    • リクエストベース(コストを抑えるため)
  • リクエスト ベース
    • サービスがリクエストを処理する時だけ費用が発生.リクエストがないときは CPU の使用が制限され、コストがかからない仕組み
  • インスタンス ベース
    • インスタンスのライフサイクル全体にわたって課金される.つまり、インスタンスが動いている期間中は常に CPU コストが発生する
  • サービスのスケーリング
    • 自動スケーリング
  • 自動スケーリング
    • トラフィックに応じて、サービスのインスタンス数が自動的に増減します。
    • 「インスタンスの最小数」を0に設定すると、リクエストがないとインスタンスが完全に削除され、コストが削減されますが、1に設定すると常に少なくとも1つのインスタンスが動作しているため、コールドスタートが減少します。
  • 手動スケーリング(プレビュー)
    • 現在プレビュー中の機能で、ユーザーがインスタンス数を直接制御できるスケーリング方式です。
  • ingress
    • すべて
  • 内部
    • プロジェクト、共有 VPC、またはVPC Service Controls内からのみのアクセスを許可する
    • 他のCloud Runサービスからの通信も、VPC経由でルーティングする必要がある
  • すべて
    • インターネットなどの外部からも直接アクセスできる状態にする.公開サービスに利用されるオプション

Cloud Runの詳細を設定をするページの下部に「コンテナ、ボリューム、ネットワーキング、セキュリティ」がある.ここを開いて「コンテナポート」をDockerfileで指定したポートにする.
スクリーンショット 2025-03-02 15.22.45.png

バックエンドも同じように構築する.この際にコンテナポートを8080にすること.

スクリーンショット 2025-03-05 8.27.55.png

実際にcurlで両方のCloud Runを叩くと以下のような実行結果となる.
フロントエンドの実行結果は以下の通りである.

<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-2f7b9ded6775055f.js"/><script src="/_next/static/chunks/4bd1b696-b5d598cf14dc4190.js" async=""></script><script src="/_next/static/chunks/587-4449b6aa6465fc85.js" async=""></script><script src="/_next/static/chunks/main-app-15095bc7e5f7c35a.js" async=""></script><script src="/_next/static/chunks/app/page-baf105e38b846e91.js" async=""></script><title>Next.js</title><meta name="description" content="Generated by Next.js"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div class="jsx-8abab21ff871dc18 container"><main class="jsx-8abab21ff871dc18"><h1 class="jsx-8abab21ff871dc18 title">Qiita Welcome!!</h1><p class="jsx-8abab21ff871dc18 description">Next.js フロントエンドを使ったサンプルアプリケーションです.</p><button class="jsx-8abab21ff871dc18 actionBtn">Get Started</button></main></div><script src="/_next/static/chunks/webpack-2f7b9ded6775055f.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[5244,[],\"\"]\n3:I[3866,[],\"\"]\n4:I[7033,[],\"ClientPageRoot\"]\n5:I[9809,[\"974\",\"static/chunks/app/page-baf105e38b846e91.js\"],\"default\"]\n8:I[6213,[],\"OutletBoundary\"]\nb:I[6213,[],\"ViewportBoundary\"]\nd:I[6213,[],\"MetadataBoundary\"]\nf:I[4835,[],\"\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"Oiej1_i-Xw4UwYGlL21Ca\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"$undefined\",[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L4\",null,{\"Component\":\"$5\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@6\",\"$@7\"]}],\"$undefined\",null,[\"$\",\"$L8\",null,{\"children\":[\"$L9\",\"$La\",null]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$1\",\"KO77Fl-ag8IRcLUCy0Awg\",{\"children\":[[\"$\",\"$Lb\",null,{\"children\":\"$Lc\"}],null]}],[\"$\",\"$Ld\",null,{\"children\":\"$Le\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$f\",\"$undefined\"],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"6:{}\n7:{}\n"])</script><script>self.__next_f.push([1,"c:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n9:null\n"])</script><script>self.__next_f.push([1,"a:null\ne:[[\"$\",\"title\",\"0\",{\"children\":\"Next.js\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Generated by Next.js\"}]]\n"])</script></body></html>% 

バックエンドの実行結果は以下の通りである.

Hello World! from Go backend! 

AWS Lambdaの構築方法

次にLambdaの構築方法を記載する.フロントエンドとバックエンド両方とも以下のようなアーキテクチャを想定しています.

スクリーンショット 2025-03-05 8.48.06.png

Amazon API Gatewayとは
AWSが提供するフルマネージドなAPI管理サービスである.RESTful API, HTTP API, WebSocket APIの構築と公開を迅速に実現する仕組みである. このサービスは, APIのセキュリティや認証・認可, アクセス制御を柔軟かつ効率的に提供するための高度な機能を有している. APIとAWS Lambdaや他のAWSサービスとの統合が容易であり, サーバーレスアーキテクチャの構築と運用を大幅に簡素化するものである. 加えて自動スケーリング機能によりトラフィックの変動に即応し, CloudWatchとの連携で運用状況の監視やログ管理を実現することができる.

Amazon ECRとは
AWSが提供するフルマネージドなコンテナイメージレジストリである.Dockerなどのコンテナイメージを安全かつ効率的に格納し, 管理するためのサービスである.このサービスは, 高い可用性とスケーラビリティを備え, 認証やアクセス制御の機能によりイメージへの不正アクセスを防止し, またイメージの暗号化やライフサイクルポリシーの設定により, 不要なイメージの自動整理ができる.

ECRにコンテナイメージをpushする

ECRのページに移動する.その後「リポジトリを作成」を選択する.
スクリーンショット 2025-03-05 8.51.29.png

以下のようにリポジトリを作成する.
スクリーンショット 2025-03-05 9.01.23.png

このようにリポジトリが作成できた.
スクリーンショット 2025-03-05 9.02.14.png

作成したリポジトリを選択して,「プッシュコマンドを表示」を選択する.
スクリーンショット 2025-03-05 8.57.33.png

ここで表示されたコマンドに従ってECRへのログインやdocker build や docker pushを行う.buildのみ以下のコマンドで行うこと.

docker buildx build --platform linux/amd64 -t qiita-sample-frontend .

AWS CLI や Dockerのインストールをしてから実行してください.

pushに成功すると以下のようにイメージがpushされていることがわかる.
スクリーンショット 2025-03-05 9.07.01.png

バックエンド側も同じようにリポジトリ作成から行うこと.
ビルドは以下のコマンドで

docker buildx build --platform linux/amd64 -t qiita-sample-backend .

Lambdaでフロントエンドのホスティング

Lambdaのページを開き,「関数を作成」を選択する.
スクリーンショット 2025-03-05 9.19.08.png

以下のように関数を作成する.
スクリーンショット 2025-03-05 9.21.08.png

コンテナイメージのURIは先ほど作成したECRのURIを選択する.

作成が完了すると以下のような画面になる
スクリーンショット 2025-03-05 9.24.56.png

API Gatewayの構築

「トリガーを作成」を選択する
スクリーンショット 2025-03-05 9.24.56.png

以下のようにAPI Gatewayを構築する.
スクリーンショット 2025-03-05 9.30.28.png

Lambdaの画面でAPI GatewayのURLが発行されるのでこれをクリックする.
スクリーンショット 2025-03-05 9.50.45.png

すると以下のように「service unavailable」となる
スクリーンショット 2025-03-05 9.52.48.png

CloudWatchのログを見ると以下のようになっている.

2025-03-05T00:46:03.933Z
> frontend@0.1.0 start
2025-03-05T00:46:03.933Z
> next start -p 3000
2025-03-05T00:46:13.775Z
▲ Next.js 15.2.0
2025-03-05T00:46:13.775Z
- Local: http://localhost:3000
2025-03-05T00:46:13.775Z
- Network: http://169.254.76.1:3000
2025-03-05T00:46:13.775Z
✓ Starting...
2025-03-05T00:46:21.513Z
✓ Ready in 13.5s
2025-03-05T00:46:55.033Z
INIT_REPORT Init Duration: 60063.19 ms Phase: invoke Status: timeout

なぜLambdaではフロントエンドの実行ができないのか

next start で起動する Next.js のサーバーは、Node.js の HTTP サーバーとして永続的に待ち受けるように設計されている.一方、Lambda はイベント駆動で短命な処理を行うため,長時間待ち受けるサーバーをそのまま走らせるのは適していない.
初回のコールドスタートや SSR の実行時に初期化処理が重く,Lambda の最大実行時間に届いてしまっている可能性がある.

Cloud Runでは構築できてLambdaでは構築できない理由

  • 常時実行の仕組み
    Cloud Run:
    コンテナベースのサービスであり、アプリケーションはコンテナ内で常に起動状態となり、指定したポート(例: 3000)で待ち受けるHTTPサーバーとして動作します。リクエストが来るたびに既に起動しているサーバーが応答する.
    AWS Lambda:
    イベント駆動で、各リクエストに対して一度だけ関数が起動し、処理終了後はコンテナがシャットダウンまたはアイドル状態となります。このため、永続的なHTTPサーバーとして動作させるのは難しく、タイムアウトや初期化遅延の問題が発生しやすい.

  • 実行時間の制約
    Lambda:
    最大実行時間の制限(例えば60秒など)があるため、Next.jsの起動やサーバーサイドレンダリングなどの初期化処理がその制限内に収まらなければタイムアウトエラーになりがち.
    Cloud Run:
    インスタンスがリクエストごとに再起動するのではなく、コンテナが継続して起動しているため、初回の起動コストが抑えられ、その後のリクエストにも即座に対応できる.

  • リソース割り当て・スケーリング
    Cloud Run:
    コンテナ単位でリソース(CPUやメモリ)を柔軟に割り当て、かつスケールアウトが自然に行える仕組み.これにより、常時起動しているフロントエンドアプリケーションのパフォーマンスと応答性が改善される.
    Lambda:
    関数実行のたびに必要なリソースが割り当てられますが、毎回の起動時にコールドスタートが発生するリスクや、関数ごとのメモリ・CPUの制約が大きく影響するため、長時間のサーバー起動には不向き.

  • アーキテクチャの違い
    Cloud Run:
    従来のサーバーや仮想マシンに近い動作をするため、Next.jsの「next start」による起動や常駐サーバーの挙動と親和性がある.
    AWS Lambda:
    サーバーレスに特化しており、他の仕組み(例: API GatewayのLambdaプロキシ統合)を取り入れた場合でも、関数としての動作を前提とするため、Next.jsの起動プロセスとの整合性が取りにくい場合がある.

フロントエンドホスティング代替手段

スクリーンショット 2025-03-05 10.10.12.png

  • AWS App Runner
    AWS App Runnerは、コードまたはコンテナをデプロイするだけでウェブアプリケーションを自動でスケーリングし、常駐型の環境で提供してくれる.Next.jsのようなNode.jsアプリケーションのホスティングに適している.

  • 静的サイトホスティング (Amazon S3 + CloudFront)
    Next.jsの静的サイト生成(SSG)やnext buildを利用して、静的コンテンツとして構築する方法.SSRが不要な場合には、静的ホスティングの方が高速かつコスト効果に優れている.

静的サイトホスティング (Amazon S3 + CloudFront)の方法は以下の記事を参考にしてほしい.

今回はApp Runnerを使ってフロントエンドのホスティングを行ってみる.App Runnerの画面で「サービスの作成」を選択する.
スクリーンショット 2025-03-05 11.55.02.png

ECRのコンテナイメージのURIを選択し,ECRアクセスロールを選択する.
スクリーンショット 2025-03-05 10.09.09.png

サービス名,インスタンスの強さ,ポート(3000)を指定する.
スクリーンショット 2025-03-05 10.09.53.png

これで,最後に確認をして,作成をすれば完成.少し待つと以下のように「デフォルトドメイン」が出るのでこれにアクセスする.
スクリーンショット 2025-03-05 11.59.04.png

正しくホスティングできているのがわかる.
スクリーンショット 2025-03-05 12.00.55.png

バックエンドアプリケーションをLambdaにデプロイ

以下のようにバックエンドアプリケーションをデプロイする.
スクリーンショット 2025-03-05 10.15.02.png

APIGatewayをトリガーに追加する.
スクリーンショット 2025-03-05 10.17.23.png

これで作成されたAPI GatewayのURLを実行すると以下のような結果になる

2025-03-05T01:17:54.356Z
2025/03/05 01:17:54 Starting Go server on port 8080...
2025-03-05T01:18:03.576Z
INIT_REPORT Init Duration: 10009.37 ms Phase: init Status: timeout
2025-03-05T01:18:03.623Z
2025/03/05 01:18:03 Starting Go server on port 8080...
2025-03-05T01:18:06.596Z
INIT_REPORT Init Duration: 3003.34 ms Phase: invoke Status: timeout
2025-03-05T01:18:06.596Z
START RequestId: e6812799-4c30-460f-9a94-747440befe38 Version: $LATEST
2025-03-05T01:18:06.601Z
2025-03-05T01:18:06.600Z e6812799-4c30-460f-9a94-747440befe38 Task timed out after 3.01 seconds
2025-03-05T01:18:06.601Z
END RequestId: e6812799-4c30-460f-9a94-747440befe38
2025-03-05T01:18:06.601Z
REPORT RequestId: e6812799-4c30-460f-9a94-747440befe38 Duration: 3007.99 ms Billed Duration: 3000 ms Memory Size: 128 MB Max Memory Used: 3 MB
2025-03-05T01:18:06.624Z
2025/03/05 01:18:06 Starting Go server on port 8080...

このようにtimeoutとなる.

なぜlambdaでは立ち上がらないのか

1. Lambdaの実行モデルとの不整合

  • イベント駆動型の設計
    AWS Lambdaは、リクエストごとに関数が起動し、短時間で処理を終了することを想定しています。ログで「Task timed out after 3.01 seconds」や「INIT_REPORT ... timeout」とあるように、Lambdaは設定されたタイムアウト以内に処理が完了しないと強制終了される.

  • 永続的なHTTPサーバーの不適合
    ログに「Starting Go server on port 8080...」とあるが、通常のGoサーバーは永続的にHTTPリクエストを待ち受けるために設計されている.Lambdaは常駐してリクエストを受け続ける仕組みではなく、イベントに応じて起動し処理が終われば終了する短命な環境であるため、サーバーとしての常駐型アプリケーションを動かすのには適していない.

2. 実装方法の不整合

  • Lambdaハンドラーとしての実装が必要
    AWS LambdaでGoを使う場合は、公式の aws-lambda-go ライブラリなどを利用して、Lambdaが期待するハンドラー関数を定義する必要がある。直接HTTPサーバーを起動し、サーバーがポート8080でリッスンする形では、Lambdaのライフサイクルやイベント処理に合致せず、タイムアウトエラーに繋がる。

  • 初期化と処理のタイムアウト
    初回もしくはウォーム状態でのサーバ起動に時間がかかるため、Lambdaの初期化フェーズでタイムアウトが発生してしいる。Lambdaは短時間でレスポンスを返すことが求められるため、長い初期化処理は致命的な問題となる.

バックエンドアプリケーションをLambdaにデプロイするための対応

以下のようにgithub.com/aws/aws-lambda-goを使用してLambdaハンドラーとしての実装をする.

main.go
package main

import (
	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	return events.APIGatewayProxyResponse{
		StatusCode: 200,
		Body:       "Hello World from Go Backend!",
	}, nil
}

func main() {
	lambda.Start(handler)
}

これでdocker buld,docker push,lambdaへの再デプロイをすれば正しくサーバーは立ち上がる.

スクリーンショット 2025-03-05 11.07.06.png

ただこれだとHandlerを作成しているのみなのでEchoのようなフレームワークを用いたい場合は以下のセクションようなコードに変更する必要がある.

Echoフレームワークを用いたWebサーバーをLambdaにデプロイ

echoパッケージを追加

go get github.com/labstack/echo/v4

main.go

main.go
package main

import (
	"net/http"

	"github.com/labstack/echo/v4"
)

func main() {
	e := echo.New()
	e.GET("/", hello)
	e.Logger.Fatal(e.Start(":8080"))
}

func hello(c echo.Context) error {
	return c.String(http.StatusOK, "Hello World from Go Backend!")
}

Dockerfile

# ビルド用ステージ
FROM golang:1.23-alpine AS builder

WORKDIR /app

# 必要なファイルのコピー
COPY main.go .
COPY go.mod go.sum ./
RUN go mod download

# ソース全体をコピー(必要に応じて)
COPY . .

# Cloud Run は amd64 なので、GOARCH=amd64 を指定
RUN GOOS=linux GOARCH=amd64 go build -o backend main.go

# 実行用ステージ
FROM alpine:latest

WORKDIR /app

# CA証明書のインストール(必要なランタイムパッケージ)
RUN apk add --no-cache ca-certificates

# Lambda Web Adapter を Docker イメージで使用するために必要な1行を追加
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter

# ビルドステージから実行ファイルをコピー
COPY --from=builder /app/backend .

# 実行権限を明示的に付与
RUN chmod +x backend

# Lambda Web Adapter 用の環境変数の設定
ENV PORT=8080
ENV AWS_LWA_ASYNC_INIT=true

# Cloud Run の場合、EXPOSE は例えば 8080 として指定することも可能です
EXPOSE 8080

CMD ["./backend"]

これでdocker build,docker tag,docker pushをしてlambdaに再デプロイをする.

今回はAPI Gatewayを構築する際にパスの設定が必要になるのでlambdaからAPI Gatewayを構築するのではなく,API GatewayからLambdaを繋ぐように構築する.

API Gatewayのページをひらき,「APIを作成」
スクリーンショット 2025-03-05 11.45.09.png

「HTTP API」を選択する.
スクリーンショット 2025-03-05 11.45.44.png

統合するLambda関数とAPI名を入力する.
スクリーンショット 2025-03-05 11.47.24.png

ルートを設定する.
スクリーンショット 2025-03-05 11.48.34.png
あとは何も設定することはないので「次へ」や「作成」をクリックしていく.

API Gatewayの構築が完了したらLambda関数の画面に以下のようにトリガーができている.
スクリーンショット 2025-03-05 11.50.11.png

これにアクセスすると以下のように正しくデプロイできている.
スクリーンショット 2025-03-05 11.07.06.png

Cloud RunとAWS Lambdaの違いと適用シーン

Cloud RunとAWS Lambdaはいずれもサーバーレス環境として、開発者に運用負荷の軽減や自動スケーリングを提供するが、その実装アーキテクチャや利用シーンに違いがある.Cloud Runはコンテナイメージをベースとするため、複雑な依存関係を含むアプリケーションや、特定のライブラリ・ミドルウェアを必要とするケースに最適である。一方、Lambdaは関数単位での速やかな処理に焦点を当て、イベントドリブンなタスクや短時間の処理に重点を置いている。そのため、アプリケーションの性質や要求される処理時間、依存関係の規模に応じた選択が必須である。加えて、Cloud Runはコンテナ技術を活用するため、既存のオンプレミス環境からのアプリケーション移行や、ポータブルな実行環境としての採用が進んでおり、Lambdaは個々のタスクを軽く、かつ迅速に実行するユースケースにおいてその力を発揮する。

運用上の留意点とコスト面の比較

運用上の留意点として、Cloud Runはリクエストベースの従量課金であり、急激なリクエスト増加時でも自動的にスケールアウトするが、同時にコンテナの起動遅延が発生する可能性がある。Lambdaもまた従量課金制を採用しており、特に極短時間で実行されるタスクにおいて費用対効果が高いとされる。しかし、Lambdaは実行時間に制限があるため、長時間の処理が必要な場合や大きなパッケージを持つ場合には、工夫が求められる。いずれのサービスにおいても、適切なモニタリングとログ管理が求められ、クラウドネイティブな設計思想に基づいた運用体制の整備が不可欠である。


総じて、Cloud RunとAWS Lambdaはそれぞれ異なるアプローチでサーバーレス実行環境を提供しており、アプリケーションの仕様や運用上の要件に応じた選択が求められる。Cloud Runはコンテナ技術を活用することで、柔軟かつ高度なカスタマイズが可能であり、既存のコンテナ化されたアプリケーションの移行にも適している。一方、Lambdaはシンプルな関数単位の処理に最適化され、イベントドリブンなタスクを迅速に実行するためのツールとして、特にリアルタイムなデータ処理や短期タスクの自動化において優位性を有する。利用者はシステム全体のアーキテクチャや処理要件、開発・運用のフローを考慮し,これら2つのサービスを適材適所に組み合わせたクラウドネイティブなソリューションの構築を進められると良いだろう.

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?