APIGateway(REST API) + Lambda環境に echo(Goのweb Framework) を ServerlessFramework でデプロイする
はじめに
今回は goのwebフレームワークであるEchoをLambdaにデプロイして簡単なAPIを実行する環境を構築します。 環境構築はServerlessFrameworkで実施しました。
echoをLambda環境で動かすためには以下のライブラリが必要になります。 このライブラリを使えばAPIGatewayを組み合わせるとGinやEchoやその他のGoフレームワークの実行ができます。
コード
今回作成したコードはこのリポジトリです。
今回は試しに、Basic認証の機能とヘルスチェックのAPIを用意しました。
echoのコード
- ディレクトリ構造
❯ tree go-echo-lambda go-echo-lambda ├── api │ └── healthcheck │ └── healthcheck.go ├── conf │ └── config.go ├── go.mod ├── go.sum ├── handler │ └── auth │ └── auth.go ├── main.go └── serverless.yml
- main.go
package main import ( "context" "log" "yhidetoshi/go-echo-lambda/api/healthcheck" "yhidetoshi/go-echo-lambda/handler/auth" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" echoadapter "github.com/awslabs/aws-lambda-go-api-proxy/echo" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) var echoLambda *echoadapter.EchoLambda func init() { log.Printf("echo cold start") e := echo.New() e.Use(middleware.Recover()) e.Use(auth.BasicAuth()) e.GET("/api/healthcheck", healthcheck.Healthcheck) echoLambda = echoadapter.New(e) } func main() { lambda.Start(Handler) } func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { return echoLambda.ProxyWithContext(ctx, req) }
- api/healthcheck.go
package healthcheck import ( "encoding/json" "log" "net/http" "github.com/labstack/echo/v4" ) type HealthcheckMessage struct { Status int `json:"status"` Message string `json:"message"` } func Healthcheck(c echo.Context) error { msg := &HealthcheckMessage{ Status: http.StatusOK, Message: "Success to connect echo", } res, err := json.Marshal(msg) if err != nil { log.Println(err) } return c.String(http.StatusOK, string(res)) }
- handler/auth.go
package auth import ( "yhidetoshi/go-echo-lambda/conf" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) var ( id = conf.BASIC_AUTH_ID pw = conf.BASIC_AUTH_PASS ) func BasicAuth() echo.MiddlewareFunc { return middleware.BasicAuth(func(username string, password string, context echo.Context) (bool, error) { if username == id && password == pw { return true, nil } return false, nil }) }
- conf/config.go
package conf const ( BASIC_AUTH_ID = "test" BASIC_AUTH_PASS = "pass" )
ライブラリをざっくり確認
それぞれのライブラリの役割
github.com/aws/aws-lambda-go/events
にてHandler(ctx context.Context, req events.APIGatewayProxyRequest)
でcontextとAPIGatewayのリクエストをlambdaで受けます。contextの役割
処理の締め切りを伝達 キャンセル信号の伝播 リクエストスコープ値の伝達
→ lambdaでAPIGatewayのイベントを受け取ってハンドリングするためのライブラリ
- main.go
func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { return echoLambda.ProxyWithContext(ctx, req) }
echoLambda.ProxyWithContext
(echo/adapter.go)に渡せば以下の処理をしてくれる。
ProxyWithContextはcontextとAPI Gatewayのイベントをhttp.Request オブジェクトに変換し、echo.Echo に送信してルーティングを行います
// ProxyWithContext receives context and an API Gateway proxy event, // transforms them into an http.Request object, and sends it to the echo.Echo for routing. // It returns a proxy response object generated from the http.ResponseWriter. func (e *EchoLambda) ProxyWithContext(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { echoRequest, err := e.EventToRequestWithContext(ctx, req) return e.proxyInternal(echoRequest, err) }
これで、この部分だけ用意しておけばlambdaを使ったサーバレスを意識せずに開発できそうですね!
環境構築 ( ServerlessFramework )
ServerlessFrameworkで Lambda関数とAPI Gatewayを作成します。
GoをコンパイルしてLambda関数にデプロイするには以下のコマンドを実行します。なお、 AWSアカウントIDと許可するFromIPを --param
に付与します。
また、リソースポリシーで接続元のIP制限をかけています。
- デプロイコマンド
$ GOARCH=amd64 GOOS=linux go build "-ldflags=-s -w" ./main.go $ sls deploy --param="account_id=${AWS_ACCOUNT_ID}" --param="allow_ip=X.X.X.X/32"
- serverless.yml
service: go-echo-lambda frameworkVersion: "3" provider: name: aws stage: dev runtime: go1.x region: ap-northeast-1 apiName: ${self:service}-${self:provider.stage} endpointType: REGIONAL apiGateway: resourcePolicy: - Effect: Allow Principal: '*' Action: execute-api:Invoke Resource: - arn:aws:execute-api:ap-northeast-1:${param:account_id}:*/* Condition: IpAddress: aws:SourceIp: - ${param:allow_ip} functions: GoEchoLambda: handler: main role: GoEchoLambda timeout: 10 description: go echo lambda test memorySize: 128 events: - http: path: /api/{proxy+} method: any #integration: lambda resources: Resources: GoEchoLambda: Type: AWS::IAM::Role Properties: RoleName: GoEchoLambda AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: GoEchoLambda PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: "*"
作成されるリソース
Lambda
APIGateway
リソース
ステージ
リソースポリシー
APIGWのリソース
- 用意したAPIリソースは Catch-allパス変数"を利用しました。
/api/{proxy+}
:/api/
配下をすべてキャッチしてくれます
events: - http: path: /api/{proxy+}
動作確認
$ curl -u test:pass https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/api/healthcheck {"status":200,"message":"Success to connect echo"}
→ Basic認証が通りヘルスチェック成功のメッセージを受け取り動作確認ができました。
備考
- ServerlessFrameworkで API Gatewayにlambda integrationするために以下のように設定をしましたがダメでした。lambda関数のeventとして
integration: lambda
せずともlambda integrationされました。
events: - http: path: /api/{proxy+} method: any #integration: lambda <-- ここで integrationを指定すると以下のエラーになりました
{"errorMessage":"json: cannot unmarshal object into Go struct field APIGatewayProxyRequest.body of type string","errorType":"UnmarshalTypeError"}
さいごに
今回、echoをlambdaで動かすために以下のライブラリを利用しました。 https://github.com/awslabs/aws-lambda-go-api-proxy
個人開発でEchoを利用するときはGoogle App Engineにデプロイしていましたが、今回のこのライブラリを知ったのでAWS(APIGW + Lambda)も利用でき選択肢が増えてよかったです!個人開発ではなるべくクラウド利用料を減らしたいので、Fargateのように常時起動させて利用外でも課金されるよりもリクエストをベースに実行できる環境はとても良いですね!