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?

lambda-web-adapterを使って、lambdaでWebサーバー(echo)をデプロイしてみる[Go,AWS]

Last updated at Posted at 2025-03-15

awsでのサーバーレスでのアプリケーションの実行環境として、lambdaとecsFargateが挙げられると思います。

その中でlambdaは一つのhandler関数を実行するため、echo等のwebサーバーを実行するにはまた別の方法が必要になります。

本来であればWebサーバーをlambdaで安直にデプロイしてホスティングするのは無理なのですが、代替方法としてlambda-web-adapterというものがaws側で用意されているそうです。

今回はlambda-web-adapterを使ってechoサーバーをホスティングしてみようと思います。

lambda-web-adapterとは

lambdaはawsのコンピューティングサービスの一つで、一つのhandler関数を作成し実行します。

動かす際にGoでは下のようにコードを書きます。

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

func handleRequest(ctx context.Context, event json.RawMessage) error {

上のコードのように引数にevent(JSON)が指定され、それをlambdaがよしなに受け取れるようになっています。

しかし、echo等のwebサーバーフレームワーク使った場合は下のような書き方になり、入力インターフェースが多少異なってきます。

func main() {
	e := echo.New()

	e.GET("/",handleRequest)

	e.Logger.Fatal(e.Start(":8000"))
}

これを解決する仕組みとしてlambda-web-adapterが存在します。

lambda-web-adapterはWebAdapterと呼ばれる箇所でlambdaとwebサーバーが扱えるようなデータ形式に相互変換を行っています。

例えば、リクエスト時にはlambdaでイベントとして扱われているリクエストをHTTPリクエストに変換します。

レスポンス時には、webサーバーでHTTPレスポンスとして処理された内容をlambdaレスポンスとして変換します。

  • 変換表
ケース 変換元 変換先
リクエスト イベント HTTPリクエスト
レスポンス HTTPレスポンス lambdaレスポンス
  • lambda-web-adapterの変換の流れ
    image.png

引用:https://aws.amazon.com/jp/builders-flash/202301/lambda-web-adapter/

このようにしてlambdaのインターフェースとwebサーバーにおけるHTTPを使ったインターフェースの相互変換の仕組みを使って、lambdaでもwebサーバーを実行できるようにしています。

lambda-web-adapterの実装

lambda-web-adapterを使って簡単なAPIサーバーを実装してみようと思います。

アプリケーションの実装

まず、Dockerfileを準備します。

# build_baseというステージ名をつける
FROM golang:1.24-alpine AS build_base 
# 依存関係の解消のためにgitを入れてるのかなと
RUN apk add --no-cache git
# 作業ディレクトリの指定
WORKDIR /tmp/echo

# コードをコンテナ内にコピー
COPY . .
# 依存関係をダウンロード
RUN go mod download

# ビルド
# GOOSでOSをlinuxに指定
# CGO_ENABLED=0でCのコード呼び出すためのツール(CGO)をオフにする(ただマルチステージビルド時はデフォルトでオフらしい?
RUN GOOS=linux CGO_ENABLED=0 go build -o bootstrap .

# 実行環境を指定する
FROM alpine:3.9 
# HTTPS通信するための証明書を取得
RUN apk add ca-certificates
# webadapterを取得
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.0 /lambda-adapter /opt/extensions/lambda-adapter
# アプリケーションのコピー
COPY --from=build_base /tmp/echo/bootstrap /app/bootstrap

# アプリケーションの使用するポートを指定
ENV PORT=8000 
EXPOSE 8000

# アプリケーションの起動コマンド
CMD ["/app/bootstrap"]

このDockerfileは、aws公式が別の有名なwebフレームワークであるginのexampleリポジトリを作ってくれてるので、そこからコードをecho用に多少いじりました。
https://github.com/awslabs/aws-lambda-web-adapter/tree/main/examples/gin

まず、マルチステージビルドをを使ってgoのアプリケーションをビルド、そこからlinuxディストリビューションであるalpineイメージにビルドしたファイルを移して実行するようにしています。

次にechoサーバーをホスティングするためのコードを作成したいと思います。

main.go
package main

import (
	"net/http"

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

func main() {
	e := echo.New()

	e.GET("/demo", func(c echo.Context) error {
		return c.JSON(http.StatusOK, map[string]string{
			"message": "Hello World!",
		})
	})

	e.Logger.Fatal(e.Start(":8000"))
}

main.goは簡単にAPIを叩いたらJSONが返ってくるようにしてみました。

terraform

今回はAWS環境をterraformで構築してみました。

ただ、lambdaでのコンテナデプロイのために下記の流れで作成する必要があります。

ecrのリソースを作成

イメージをpush

lambda等のリソースを作成

イメージを先に配置しないとlambdaがイメージのあるecrのリポジトリを参照できないので、まずecrのリソースから作成します。

ecrにpush

ecrのpushをする前にterraform環境の作成をします。

ECR(Elastic Container Registry)とは

image.png

AWSのマネージドコンテナイメージレジストリサービスで、Dockerのコンテナイメージの簡単な保存、管理、デプロイが行うことができます。

main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-3"
  profile = "admin-tf"
}

まずmain.tfでプロバイダーをawsに指定します。

この状態でinitコマンドを使用して必要なモジュールを準備します。

initコマンドを打ってからecrのリソースを作成します。

ecr.tf
resource "aws_ecr_repository" "lambda-web-adapter" {
  name                 = "lambda-test"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }

}

ここまでできたらapplyします。

その後、イメージをawscliでpushします。

zsh
 aws lambda update-function-code --function-name <lambda関数名名> --image-uri ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-3.amazonaws.com/<リポジトリ名>:latest

ここでコンソール確認して下のようになってたらイメージの準備OKです。
image.png

lambdaのリソースを作成する

ecrにpushできたらlambdaのリソースを作成します。

Lambdaとは

image.png

AWSのコンピューティングサービスの一つで、アプリケーションのデプロイのためのサーバーのセットアップ(プロビジョニング)やメンテナンスが必要なく、アプリケーションの実行が行えるフルマネージドサービスです。

lambda.tf
resource "aws_lambda_function" "lambda-web-adapter" {
  function_name = "lambda-web-adapter" //関数名
  image_uri = "${aws_ecr_repository.lambda-web-adapter.repository_url}:latest" //imageがないとここが参照できない
  role          = aws_iam_role.iam_for_lambda.arn 
  package_type  = "Image" //コンテナ使用時はここを指定
  architectures = ["arm64"] //macでビルドしてるのでここ書いておいた
}

 //関数URLで簡単に検証するためにリソース作ってます。
resource "aws_lambda_function_url" "test_latest" {
  function_name      = aws_lambda_function.lambda-web-adapter.function_name
  authorization_type = "NONE"
}


resource "aws_iam_role" "iam_for_lambda" {
  name               = "iam_for_lambda"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

//dataブロックでassumeロールしてCloudwatch等のリソースと連携できるようにポリシーを指定
data "aws_iam_policy_document" "assume_role" {
  statement {
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }

    actions = ["sts:AssumeRole"]
  }
}

これでapplyしてデプロイします。

これでcurlコマンドを使ってgetします。

すると下のようにjsonが帰ってきます。ホスティングはできてそうですね。

zsh
❯ curl https://t2ff4wsmviyocn7kkqdwj6bbjq0cburc.lambda-url.ap-northeast-3.on.aws/demo
{"message":"Hello World!"}

CD

今回はGithubActionsでついでにCDも組んでみます。

CD(継続デリバリー)

あるアクションをトリガーとして自動でサーバーにホスティングする開発手法のことをCDという。
CDを行うことで迅速な開発体制を整えることができます。

下準備

今回OIDCという仕組みを使ってAssumeRoleを行います。

OIDCとは

OIDC(Open ID Connect)とは、認証を行うためのプロトコルで、OAuthの拡張として知られています。

OAuthは誰かに対してリソースのアクセスを許可する認可の仕組みなのに対して、OIDCはIDトークンを渡して誰がリソースにアクセスしているかを把握するための認証の仕組みです。

AssumeRoleとは

AWSではIAMロールという仕組みを使って一時的に権限を多数の人に与えることができるAssumeRoleという仕組みがあります。

AssumeRoleは、信頼ポリシーに従って、そのIAMロールをAWSリソース等から引き受けてIAMロールになりすますようなAWSの認証認可の仕組みです。

ロールを作成する。

まず、IAMロールを作成します。

IAMとは

image.png

AWS リソースへのアクセスを安全に管理するためのウェブサービスです。

IAMロールは、短期的な認証情報を作成し、複数のユーザーやリソースにアクセス権限を与えられる仕組みのことです。

iam.tf
//OIDCプロバイダを作成する
resource "aws_iam_openid_connect_provider" "token_actions_github" {
 url = "https://token.actions.githubusercontent.com"

 client_id_list = [
   "sts.amazonaws.com",
 ]

}

//iamロールを作成する。
resource "aws_iam_role" "from_github_lambda_web_adapter_test" {
 name = "from_github_lambda_web_adapter_test"

//信頼ポリシーを作成する。
 assume_role_policy = jsonencode({
   "Version": "2012-10-17",
   "Statement": [
       {
           "Effect": "Allow",
           //ロールを実行できる相手を指定
           "Principal": {
               "Federated": aws_iam_openid_connect_provider.token_actions_github.arn
           },
           "Action": "sts:AssumeRoleWithWebIdentity",
           //ロールの実行タイミング
           "Condition": {
               "StringEquals": {
                   "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
               },
               //Githubリポジトリを指定、ブランチは指定しないようにしてる
               "StringLike": {
                   "token.actions.githubusercontent.com:sub": "repo:maooz4426/lambda-web-adapter-test:ref:refs/heads/*"
               }
           }
       }
   ]
})

}

//許可ポリシーを定義します。
resource "aws_iam_role_policy" "lambda_web_adapter_test" {
 name = "lambda_web_adapter_test"
 role = aws_iam_role.from_github_lambda_web_adapter_test.id

 policy = jsonencode({
   "Version": "2012-10-17",
   //許可するアクションを指定
   "Statement": [
       //後述するaws-actions/amazon-ecr-loginでTokenを渡せるように
   	{
   		"Effect": "Allow",
   		"Action": "ecr:GetAuthorizationToken",
   		"Resource": "*"
   	},
       //ecrでの操作の許可
   	{
   		"Effect": "Allow",
   		"Action": [
   			"ecr:UploadLayerPart",
   			"ecr:PutImage",
   			"ecr:InitiateLayerUpload",
   			"ecr:CompleteLayerUpload",
   			"ecr:BatchGetImage", //これをつけることでactionsでImageを扱える
   			"ecr:BatchCheckLayerAvailability"
   		],
   		"Resource": aws_ecr_repository.lambda-web-adapter.arn
   	},
       //lambdaのupdateを許可
   	{
   		"Effect": "Allow",
   		"Action": "lambda:UpdateFunctionCode",
   		"Resource": "*"
   	}
   ]
})
}

JSONで書いている内容はdataブロックとjsonencodeの2種類存在します。

個人的にはjsonencodeはAWSドキュメントに書かれていることが多くコピペで流用しやすいので、jsonencodeを使用します。

このタイミングでterraform applyして、リソースを作成します。

実際に組んでいく

実際にCDの仕組みをGithubActionsで組んでいきます。

ローカルリポジトリの.github/workflows配下にdeploy.ymlを作成します。

deploy.yml
  name: deploy

//mainブランチにpushされたら起動
on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write //OIDCの仕組みではここら辺のpermissionが必要
      contents: read
    env:
      REGION: ap-northeast-3
    steps:
      - name: checkout
        uses: actions/checkout@v4
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/from_github_lambda_web_adapter_test
          aws-region: ${{ env.REGION }}
      - name: Login to Amazon ECR private
        id: login-ecr-private
        uses: aws-actions/amazon-ecr-login@v2.0.1
        env:
          AWS_DEFAULT_REGION: ap-northeast-3
          AWS_REGION: ${{ env.REGION }}
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3.10.0
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          push: true
          platforms: linux/arm64
          provenance: false
          tags: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-3.amazonaws.com/lambda-test:latest
      - name: update for lambda
        run: |
          aws lambda update-function-code --function-name lambda-web-adapter --image-uri ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-3.amazonaws.com/lambda-test:latest

checkoutしてactions上にリポジトリをクローンします。

- name: checkout
  uses: actions/checkout@v4

次にaws-actions/configure-aws-credentialsを使ってAssumeRoleします。

 - name: Configure AWS credentials
   uses: aws-actions/configure-aws-credentials@v4
   with:
    role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/from_github_lambda_web_adapter_test 
    aws-region: ${{ env.REGION }}

次にECRのプライベートリポジトリを使うためにECRにログインします。

- name: Login to Amazon ECR private
  id: login-ecr-private
  uses: aws-actions/amazon-ecr-login@v2.0.1
  env:
   AWS_DEFAULT_REGION: ap-northeast-3
   AWS_REGION: ${{ env.REGION }}

次にイメージをビルドするためにbuildxをセットアップする。

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3.10.0

イメージのビルドとpushを行います。
tag付けも行っており、それを利用してイメージをecrにpushしています。

- name: Build and push
  uses: docker/build-push-action@v6
  with:
    push: true
    platforms: linux/arm64
    provenance: false
    tags: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast3.amazonaws.com/lambda-test:latest 

provenanceフィールドとは?

docker/build-push-action時に provenance: falseをつけないと余分だなと感じるイメージが作成されます。
image.png

この0.01MBのはSLSA Provenanceというものらしく
ソフトウェアのサプライチェーン(開発からビルドまでの過程)において外部からの攻撃がされていないという完全性を把握するためのセキュリティの仕組みだそうです。

参考:https://chroju.dev/blog/docker_buildx_slsa_provenance
https://donbulinux.hatenablog.jp/entry/2023/08/03/195648

Image Indexはmanifestに関連してるものらしく、別プラットフォーム(armとかamdとか)のバージョンを管理するための仕組みだそうです。

参考:https://zenn.dev/ncdc/articles/25d03e908ce38e#%E3%83%9E%E3%83%8B%E3%83%95%E3%82%A7%E3%82%B9%E3%83%88%E4%BD%9C%E6%88%90%E3%81%AE%E6%B5%81%E3%82%8C

今回は必要ないのでprovenance:falseにしています。

最後にlambdaのupdate functionをawscliで実行しています。

 - name: update for lambda
   run: |
          aws lambda update-function-code --function-name lambda-web-adapter --image-uri ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-3.amazonaws.com/lambda-test:latest

最後に

今回はlambda-web-adapterを利用して、lambdaにechoのwebサーバーをホスティングしてみました。

これを利用することでlambdaを使って簡単にwebサーバーをホスティングすることができると思います。

lambdaは無料利用枠があり、100万リクエストまで無料です。

これを利用して学生はハッカソン等で利用して欲しいと思います。

しかし、lambdaにはボトルネックがいくつか存在します。

15分の起動制限、同時実行数が1000回n存在するので、サーバーレスかつ安定的な運用をしたいのであればECSonFargateの方がいいと思います。

なので、脳死でlambdaを採用したら、安定的な運用ができるとも限りません。

ここら辺もまた記事でまとめていきたいなと思います。

Go関連の記事

GithubActions関連の記事

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?