「AWS無料相談会」をオンラインで開催中

AWS LambdaでGolangのWebフレームワークGinを利用してみた

最近は体調管理に一番困っているやっさんでございます。
頭痛や肩こり、そして腰痛。
年齢が上がると人には話しにくい悩みが増えていきますね。

さて、今回はAWS Lambdaで
GolangのWebフレームワークGinを利用してみました。

Ginとは

GinはGo(Golang)で記述されたWebフレームワークです。
パフォーマンスと優れた生産性が必要な場合は、Ginが気に入るはずです。

AWS Lambda × Ginのメリット

AWS LambdaでGinを利用することのメリットを考えてみます。

再利用性と可搬性の向上

Ginを利用することで、再利用性と可搬性が向上します。
GinはWebフレームワークですので、サーバーでも動かすことができます。
少しの修正でAWS Lamdaからコンテナアーキテクチャに変更することも可能になります。

フレームワークの恩恵を享受できる

GolangではAWS Lambdaに特化したフレームワークはありません。
他の言語であれば、以下のようなフレームワークが存在します。

  • Ruby:Ruby on Jets
  • Python:Chalice

「LambdaでSpring Bootを使えるか」という話を勉強会で耳にしました。
コミュニティから信頼されているフレームワークをLambdaで利用できれば、
生産性も上がり、コミュニティから多くの技術的知見も得られる ...
このような話が出るのは当然です。
(LambdaでSpring Bootを使うのが本番ワークロードに適しているかは、別の話ですが。)

Ginも人気のあるWebフレームワークで、スター数は30,000を超えています。
LambdaでWebフレームワークに必要な機能をフルスクラッチで作るより、
Ginなどの有名なフレームワークを利用したほうが生産性は上がるでしょう。

このようなアプローチは他にもあります。

  • LambdaでExpress(Node.js)を利用する
  • LambdaでFlask(Python)を利用する

AWS LambdaでGinを利用してみる

以下の前提条件で構築しました。

  • Golang : 1.14.3
  • Serverless Framework : 1.67.1

ディレクトリ構成

今回はServerless Frameworkを利用します。

- src
  - hanlder
    - main.go
- Makefile
- serverless.yml

main.goの内容

awslabsが提供しているaws-lambda-go-api-proxyを利用して、
Lambdaのリクエスト情報をGinの機能に受け渡します。

package main

import (
    "log"
    "context"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/awslabs/aws-lambda-go-api-proxy/gin"
    "github.com/gin-gonic/gin"
)

var ginLambda *ginadapter.GinLambda

func init() {
    // stdout and stderr are sent to AWS CloudWatch Logs
    log.Printf("Gin cold start")
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })

    ginLambda = ginadapter.New(r)
}

func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    // If no name is provided in the HTTP request body, throw an error
    return ginLambda.ProxyWithContext(ctx, req)
}

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

serverless.ymlの内容

記述内容にGin特有の変更はありません。
普段通り記述します。

service: aws-lambda-go-api-proxy-gin

provider:
  name: aws
  runtime: go1.x
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "logs:*"
      Resource: "*"

package:
  exclude:
    - ./**
  include:
    - ./bin/**

custom:
  defaultStage: dev

functions:
  api:
    handler: bin/main
    timeout: 900
    events:
      - http:
          path: ping
          method: get

デプロイ

以下のコマンドをMakefileに用意しました。

build:
    go mod download
    env GOOS=linux go build -ldflags="-s -w" -o bin/main src/handler/main.go

deploy: build
    sls deploy --verbose --aws-profile $(PROFILE)

実行してみる

実行できました!

パフォーマンスを測ってみる

Heyというツールを使って測ってみました。
コールドスタートで0.4秒ならよさそうです!

Summary:
  Total:    0.5447 secs
  Slowest:    0.4115 secs
  Fastest:    0.0246 secs
  Average:    0.1200 secs
  Requests/sec:    367.1871

  Total data:    3600 bytes
  Size/request:    18 bytes

Response time histogram:
  0.025 [1]    |
  0.063 [145]    |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.102 [1]    |
  0.141 [2]    |■
  0.179 [2]    |■
  0.218 [0]    |
  0.257 [0]    |
  0.295 [1]    |
  0.334 [8]    |■■
  0.373 [32]    |■■■■■■■■■
  0.411 [8]    |■■


Latency distribution:
  10% in 0.0328 secs
  25% in 0.0381 secs
  50% in 0.0442 secs
  75% in 0.1740 secs
  90% in 0.3583 secs
  95% in 0.3691 secs
  99% in 0.3913 secs

Details (average, fastest, slowest):
  DNS+dialup:    0.0151 secs, 0.0246 secs, 0.4115 secs
  DNS-lookup:    0.0030 secs, 0.0000 secs, 0.0124 secs
  req write:    0.0000 secs, 0.0000 secs, 0.0010 secs
  resp wait:    0.1048 secs, 0.0245 secs, 0.3504 secs
  resp read:    0.0000 secs, 0.0000 secs, 0.0002 secs

Status code distribution:
  [200]    200 responses

いとも簡単にLambdaでGinを利用できました。
ここからにさらにデータソースをDynamoDBからRDBに変更しやすい設計とすれば
より再利用性と可搬性が向上しますね。

今回試したコードは以下にあります。
x-blood / aws-lambda-go-api-proxy-gin

よろしければご覧になってみてください。

以上です!