🌟

Lambda Web Adapter + SAMで既存のGo Fiberアプリをサーバーレス化する

2023/01/22に公開

背景

既存のGoアプリをAWS Lambda上で実行するためには、
aws-lambda-goaws-lambda-go-api-proxyなどのライブラリーを使用しなければならず、
それに伴い、アプリケーションコードの侵入性が高く、移行するためには不便な状況がありました。

そこで、AWS Labsが提供するAWS Lambda Web Adapterを使用することで、既存のGoアプリを簡単にサーバーレス化することができるということです。
AWS Lambda Web Adapterは、HTTPリクエストをAWS Lambdaのイベントとして処理することで、既存のGoアプリをAWS Lambda上で実行するために必要なコードの侵入性を最小限に抑え、移行するために必要な変更量を減らします。
これにより、既存のGoアプリをサーバーレスアーキテクチャに移行するために不便なことがなくなり、AWS Lambda上でのスケーリングや管理の問題を解決することができます。

この記事では、AWS Lambda Web AdapterとSAMを使用して既存のGo Fiberアプリケーションをサーバーレスアーキテクチャに移行し、SAMでデプロイする方法を紹介します。

既存のGoアプリをサーバーレス化するための手順

1. SAMテンプレートの作成

次に、AWS Lambda Web Adapterを使用するためのSAMテンプレートを作成します。
長くなるので、アコーディオンで隠しています。

下記の順でSAM+Goのテンプレートを作成します。
-> sam init --name sam-app --runtime go1.x                     

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Infrastructure event management
        3 - Multi-step workflow
Template: 1

Based on your selections, the only Package type available is Zip.
We will proceed to selecting the Package type as Zip.

Based on your selections, the only dependency manager available is mod.
We will proceed copying the template using mod.

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: 

Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: 

Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: go1.x
    Architectures: x86_64
    Dependency Manager: mod
    Application Template: hello-world
    Output Directory: .
    
    Next steps can be found in the README file at ./sam-app/README.md
        

Commands you can use next
=========================
[*] Create pipeline: cd sam-app && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-app && sam validate
[*] Test Function in the Cloud: cd sam-app && sam sync --stack-name {stack-name} --watch


  at …/lambda-web-adapter-example (  ) on 🅰 (jp) underwent 35s
  sam init --name sam-app                

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Multi-step workflow
        3 - Serverless API
        4 - Scheduled task
        5 - Standalone function
        6 - Data processing
        7 - Infrastructure event management
        8 - Serverless Connector Hello World Example
        9 - Multi-step workflow with Connectors
        10 - Lambda EFS example
        11 - Machine Learning
Template: 1

Use the most popular runtime and package type? (Python and zip) [y/N]: 

Which runtime would you like to use?
        1 - aot.dotnet7 (provided.al2)
        2 - dotnet6
        3 - dotnet5.0
        4 - dotnetcore3.1
        5 - go1.x
        6 - go (provided.al2)
        7 - graalvm.java11 (provided.al2)
        8 - graalvm.java17 (provided.al2)
        9 - java11
        10 - java8.al2
        11 - java8
        12 - nodejs18.x
        13 - nodejs16.x
        14 - nodejs14.x
        15 - nodejs12.x
        16 - python3.9
        17 - python3.8
        18 - python3.7
        19 - ruby2.7
        20 - rust (provided.al2)
Runtime: 5

What package type would you like to use?
        1 - Zip
        2 - Image
Package type: 2

Based on your selections, the only dependency manager available is mod.
We will proceed copying the template using mod.

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: 

Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: 

Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Base Image: amazon/go1.x-base
    Architectures: x86_64
    Dependency Manager: mod
    Output Directory: .

    Next steps can be found in the README file at ./sam-app/README.md
    

Commands you can use next
=========================
[*] Create pipeline: cd sam-app && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-app && sam validate
[*] Test Function in the Cloud: cd sam-app && sam sync --stack-name {stack-name} --watch

2. アプリケーションのセットアップ

次に、 sam-app/hello-world/main.goを作成したアプリケーションにそのまま置き換えます。
今回使用するサンプルアプリケーションこちらです。
GitHub: https://github.com/the-exile-110/lambda-web-adapter-example

package main

import (
	"github.com/gofiber/fiber/v2"
	"net/http"
)

func main() {
	app := fiber.New()

	app.Get("/", func(c *fiber.Ctx) error {
		return c.Status(http.StatusOK).JSON(fiber.Map{
			"Message": "Hello, World 👋!",
		})
	})

	app.Listen(":3000")
}

3. template.yamlの編集

今回はAPI Gatewayを使わず、Lambda Function URLを利用するため、 template.yamlを編集します。
M1 Macのため、Architecturesをarm64に変更しています。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app
  
  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 5
    MemorySize: 128

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      PackageType: Image
      Architectures:
-        - x86_64
+        - arm64
-      Events:
-        CatchAll:
-          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
-          Properties:
-            Path: /hello
-            Method: GET
      Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
        Variables:
          PARAM1: VALUE
    Metadata:
      DockerTag: go1.x-v1
      DockerContext: ./hello-world
      Dockerfile: Dockerfile
+  LambdaFunctionUrl:
+    Type: AWS::Lambda::Url
+    Properties:
+      TargetFunctionArn: !GetAtt HelloWorldFunction.Arn
+      AuthType: NONE
+      Cors:
+        AllowMethods: [ 'GET' ]
+        AllowOrigins: [ '*' ]
+        AllowHeaders: [ 'Content-Type', 'Authorization', 'X-Amz-Date', 'X-Api-Key', 'X-Amz-Security-Token', 'X-Amz-User-Agent' ]

Outputs:
-  HelloWorldAPI:
-    Description: "API Gateway endpoint URL for Prod environment for First Function"
-    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
-  HelloWorldFunction:
-    Description: "First Lambda Function ARN"
-    Value: !GetAtt HelloWorldFunction.Arn
-  HelloWorldFunctionIamRole:
-    Description: "Implicit IAM Role created for Hello World function"
-    Value: !GetAtt HelloWorldFunctionRole.Arn
+  Endpoint:
+    Description: URL for function
+    Value: !GetAtt LambdaFunctionUrl.FunctionUrl

4. Dockerfileの編集

次に、aws-lambda-adapterを使うようにhello-world/Dockerfile下記のDockerfileに変えます。
Dockerfileの書き方はそれぞれですが、
重要なのはlambda-adapterのコピーとポート公開の部分です。

FROM arm64v8/golang:1.19.1-bullseye as build-env

WORKDIR /go/src/app

COPY . .

RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o /go/bin/app

FROM gcr.io/distroless/static:latest-arm64

COPY --from=build-env /go/bin/app /

# ここが重要
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.6.0 /lambda-adapter /opt/extensions/lambda-adapter

# ここが重要
ENV PORT=3000
EXPOSE 3000

CMD ["/app"]

5. SAM CLIでビルド

続いて、 sam buildでビルドします。

実行結果
-> sam build
Building image for HelloWorldFunction function
Setting DockerBuildArgs: {} for HelloWorldFunction function
Step 1/8 : FROM arm64v8/golang:1.19.1-bullseye as build-image
 ---> c5e98f0fcc5e
Step 2/8 : WORKDIR /go/src
 ---> Running in 4ff29f303df8
Removing intermediate container 4ff29f303df8
 ---> e989a42012df
Step 3/8 : COPY go.mod go.sum main.go ./
 ---> c6feca10f658
Step 4/8 : RUN go build -o ../bin
 ---> Running in 1d6af16cb47c
go: downloading github.com/gofiber/fiber/v2 v2.41.0
go: downloading github.com/mattn/go-colorable v0.1.13
go: downloading github.com/mattn/go-isatty v0.0.17
go: downloading github.com/mattn/go-runewidth v0.0.14
go: downloading github.com/valyala/bytebufferpool v1.0.0
go: downloading github.com/valyala/fasthttp v1.43.0
go: downloading golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
go: downloading github.com/rivo/uniseg v0.2.0
go: downloading github.com/valyala/tcplisten v1.0.0
go: downloading github.com/andybalholm/brotli v1.0.4
go: downloading github.com/klauspost/compress v1.15.9
Removing intermediate container 1d6af16cb47c
 ---> 52564d727bae
Step 5/8 : FROM public.ecr.aws/lambda/go:1
1: Pulling from lambda/go 
796e5774e352: Pull complete 
1a89c28fd085: Pull complete 
64543d447bd8: Pull complete 
072730bdc14d: Pull complete 
fd7ea672a291: Pull complete 

WARNING: Pulled image with specified platform (linux/arm64), but the resulting image's configured platform (linux/amd64) does not match.
This is most likely caused by a bug in the build system that created the fetched image (public.ecr.aws/lambda/go:1).
Please notify the image author to correct the configuration. ---> f5017fea382b
Step 6/8 : COPY --from=build-image /go/bin/ /var/task/
 ---> 4c9083ccf4b8
Step 7/8 : COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.6.0 /lambda-adapter /opt/extensions/lambda-adapter
 ---> da80197cb166
Step 8/8 : CMD ["hello-world"]
 ---> [Warning] The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
 ---> Running in 1a2ad95d2950
Removing intermediate container 1a2ad95d2950
 ---> e08321a2014c
Successfully built e08321a2014c
Successfully tagged helloworldfunction:go1.x-v1


Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided

6. SAM CLIでデプロイ

最後に、 sam deploy --guidedでデプロイします。
基本的提示メッセージに従って進めば、問題なくデプロイできます。

7. 動作確認

デプロイが完了したら、アウトプットに表示されたURLにアクセスしてみます。

-> curl https://xxx.lambda-url.ap-northeast-1.on.aws/                                                                   
{"Message":"Hello , World 👋!"}

8. リソースの削除

sam deleteで作ったリソースを削除します。

まとめ

今回は、Lambda Web Adapter + SAMで既存のGo Fiberアプリをサーバーレス化する方法を紹介しました。
Lambda Web Adapterを使って、Go FiberのようなGoのWebフレームワークをそのまま使えるので、 既存のGoアプリをサーバーレス化するのに便利です。

Discussion