Lambda proxy integration×Go(echo)×Terraformでお手軽Web API(サーバレス)

# はじめに
サーバレスで手軽にWeb APIを立てたい場合、**API Gatewayのproxy integrationとlambdaアプリケーションソースでのルーティングでこんなに簡単にお安く構築できるんだな〜**と感心してしまったのでそのご紹介と、そのリソース群のTerraform moduleを作ってTerraform Registryに公開したのでその内容の紹介で記事を書いてみました。
(しかも最近VPC Lambdaのコールドスタートが大幅に改善されたばかり　-> [[発表] Lambda 関数が VPC 環境で改善されます \| Amazon Web Services ブログ](https://aws.amazon.com/jp/blogs/news/announcing-improved-vpc-networking-for-aws-lambda-functions/) )

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">lambda proxy integrationのmodule,S3のartifact指定できるやつなかったので作って、Terraform Registryに公開した🎉<a href="https://t.co/jJxfruBDQ6">https://t.co/jJxfruBDQ6</a></p>&mdash; nari@BOOTHで好評発売中「GoとAWS CDKで作る本格SlackBot入門」 (@fukubaka0825) <a href="https://twitter.com/fukubaka0825/status/1186651590280638464?ref_src=twsrc%5Etfw">October 22, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

後はついでに、[Terraform入門資料(v0.12.0対応) ~基本知識から設計や運用、知っておくべきtipsまで~ - Qiita](https://qiita.com/fukubaka0825/items/68506b1e6644404d6cc0)では詳しく書けなかったlambdaリソースのアプリとインフラのデプロイの疎結合化についてもSampleを紹介しています。

# 対象

- **AWS Lambdaを使ってWeb API立ててみたいなと思っていた方**
- **AWS Lambdaリソースのアプリとインフラのデプロイの疎結合化のサンプルに興味がある方**

## 前提
- 標準のnet/httpを用いたルーティングでも可能ですが、今回はフレームワークとして[echo](https://github.com/labstack/echo)を使用しています
- Lambda proxy integrationとは？ -> [API Gateway の Lambda プロキシ統合をセットアップする - Amazon API Gateway](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html)
- 今回作成したterraformのmoduleは[こちら](https://registry.terraform.io/modules/fukubaka0825/lambda-proxy-integration/aws/1.0.2)

# 開発環境
- Terraform v0.12.6
- Go v1.12.4


# システム全体像
![lambda proxy 全体像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/062d97ff-5399-5cda-a6d4-28103d61bc57.png)

# リポジトリ構成

 ```
├── Makefile
├── README.md
├── go.mod
├── go.sum
├── main.tf
├── modules
│   ├── iam_role
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── valiables.tf
│   └── sg
│       ├── main.tf
│       ├── outputs.tf
│       └── valiables.tf
├── outputs.tf
└── source
    └── main.go
```
また、サンプルコードは、公開したmoduleのexampleとして公開しています
[terraform-aws-lambda-proxy-integration/go1.x_examples at master · fukubaka0825/terraform-aws-lambda-proxy-integration · GitHub](https://github.com/fukubaka0825/terraform-aws-lambda-proxy-integration/tree/master/go1.x_examples)

# Terraformのコード紹介
```hcl:main.tf
module "sample_api"{
  source  = "fukubaka0825/lambda-proxy-integration/aws" //❶
  version = "1.0.2"
  apigw_name = "sample-api"
  function_name = "sample-api"
  lambda_role = module.lambda_role.iam_role_arn
  
  // The repo's name and object key where source code artifact exists　
  s3_bucket = data.terraform_remote_state.storage.outputs.s3_serverless_app_bucket.name
  s3_key = "lambda/sample.zip"
  
  // If you want to use vpc lambda,required　
  security_group_ids = [module.http_sg.security_group_id] //❷
  subnet_ids = module.vpc.app_subnets 
}
```
❶ 今回API proxy integration+S3のartifactをlambdaのソースとして指定できるmoduleを作成したので、そちらを指定しています([Terraform Registry](https://registry.terraform.io/modules/fukubaka0825/lambda-proxy-integration/aws/1.0.2))
❷ security_group_idsとsubnet_idsは、VPC lambaのモードならば指定します。

# Goのコード紹介

```go:main.go
package main

import (
	"github.com/labstack/echo"
	"log"
	"context"
	"net/http"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	echolamda"github.com/awslabs/aws-lambda-go-api-proxy/echo"
)


var echoLambda *echolamda.EchoLambda　//❶

func init() {
	// stdout and stderr are sent to AWS CloudWatch Logs
	log.Printf("Echo cold start")
	e := echo.New()　//❷
	//ここでルーティング
	e.GET("/ping", func(c echo.Context) error{
		return c.NoContent(http.StatusNoContent)
	})
	echoLambda = echolamda.New(e)
}

func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	// If no name is provided in the HTTP request body, throw an error
	// 中でServeHTTPを叩いている
	return echoLambda.ProxyWithContext(ctx, req)　//❸
}

func main() {
	lambda.Start(Handler)
}
```
❶ コールドスタートの関係上、globalにechoLambdaを宣言するのがセオリーっぽいです。
❷ initでroutingを設定します。ここではechoを使っていますが、もちろん自由なフレームワークなりnet/httpなりで設定可能です。
❸ 本来ならば、`proxyrequestからhttp.requstを取り出して、ServeHTTPメソッドで${path}に対応する関数を発火し、http.responseをproxyresponseに変換して返す`処理を自前で書く必要がありますが、[GitHub - awslabs/aws-lambda-go-api-proxy: lambda-go-api-proxy makes it easy to port APIs written with Go frameworks such as Gin (https://gin-gonic.github.io/gin/ ) to AWS Lambda and Amazon API Gateway.](https://github.com/awslabs/aws-lambda-go-api-proxy)という素晴らしいawslabsが提供しているmoduleで同じようなことをやってくれるものがあるのでそれを利用しています。(**なんとgin,echo,net/httpなど主要なパッケージには対応してくれている、最高**)



# lambdaリソースのアプリとインフラのデプロイを疎結合にする運用

[Terraform入門資料(v0.12.0対応) ~基本知識から設計や運用、知っておくべきtipsまで~ - Qiita](https://qiita.com/fukubaka0825/items/68506b1e6644404d6cc0)の3.4 lambdaリソースを扱う場合のインフラとアプリの密結合問題でも扱いましたが、**Terraformのみでデプロイを完結させるとインフラとアプリが密結合になってしまいます**。その解消法の具体例の話をここではしようと思います。


## Makefileを作成する
```sh:Makefile
LAMDA_SOURRE_REPO=serverless-app
LAMDA_SOURRE_KEY=lambda/sample.zip
LAMDA_SOURCE_PATH=s3://${LAMDA_SOURRE_REPO}/${LAMDA_SOURRE_KEY}
deploy_dummy_code:　//❶
	aws s3 cp .build/dummy.zip ${LAMDA_SOURCE_PATH}
build_update: //❷
	GOOS=linux GOARCH=amd64 go build -o .build/main source/main.go
	cd .build && zip -r sample.zip ./
	aws s3 cp .build/sample.zip ${LAMDA_SOURCE_PATH}
	aws lambda update-function-code --function-name sample-search-api --s3-bucket ${LAMDA_SOURRE_REPO} --s3-key ${LAMDA_SOURRE_KEY}
	echo 'Success to lambda update!'
```
❶ dummyのzipを`LAMDA_SOURCE_PATH`にあげる処理を`deploy_dummy_code`コマンドに仕込んでいます。
❷ 本当のlambdaソースのコードを、buildしてzipにし、`LAMDA_SOURCE_PATH`にあげて、`aws lambda update-function-code`コマンドでlambdaのソースをアップデートする処理を`build_update`コマンドに仕込んでいます。

## Makefileを用いたデプロイ運用
```bash
// Put dummy code into S3 repo.
make deploy_dummy_code　//❶
// deploy infra
terraform init　//❷
terraform plan
terraform apply
// deploy app(lambda) 
make build_update　//❸

```
❶ まず`deploy_dummy_code`コマンドで、S3にdummyのコードをあげます。
❷ ❶のコードを参照させる形で、lambdaのインフラ部分をデプロイします。
❸ 最後に、`build_update`で本当のlambdaのアプリ部分をデプロイします。

# 終わりに
VPC lambdaはコールドスタートの改善によって、**レスポンスの要件が非常にシビアでないWeb APIの構築ツールとして、かなり魅了的なものになってきた**と思います。

データソースとのつなぎこみは、RDSのようにスケールしづらく、コネクション制限があるものとのやりとりはまだまだ使い勝手が悪い(internalなAPI経由にすれば解決しますが)ですが、**スケールしやすい分散型のデータソース(DynamoDB、ElasticSearch,redis)とのやりとりなら全然使っていけそう**だなという印象です。

また、この記事で使ったmoduleは、初めてPublic ModuleをTerraform Registry公開してみましたが、これからも**どんどん公開できるものは公開**していきたいと思います。

# 参考文献
- [[発表] Lambda 関数が VPC 環境で改善されます \| Amazon Web Services ブログ](https://aws.amazon.com/jp/blogs/news/announcing-improved-vpc-networking-for-aws-lambda-functions/) )
- [GitHub - sprutner/terraform-aws-lambda-proxy](https://github.com/sprutner/terraform-aws-lambda-proxy)
- [GitHub - developmentseed/tf-lambda-proxy-apigw: Terraform Module for API Gateway Proxy + AWS Lambda](https://github.com/developmentseed/tf-lambda-proxy-apigw)