はじめに
Glolang WebアプリケーションGinを用いたサーバーレスアプリケーションをAWS CDKを使ってAWS Lambdaにデプロイします
今回用いたコードはGitHubで公開しています。
環境
Golang: 1.20.4
AWS CDK: 2.96.0
ディレクトリ構成
├── gin
│ ├── bin
│ │ ├── bootstrap
│ │ └── myFunction.zip
│ ├── main.go
└── infra
Gin
Gin Tutorial
今回はGin Tutorialで作れるRESTful APIをデプロイしようと思います。
このTutorialを終えた後のmain.goは以下の通りです。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// albums slice to seed record album data.
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
var newAlbum album
if err := c.BindJSON(&newAlbum); err != nil {
return
}
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
// getAlbumByID locates the album whose ID value matches the id
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
AWS Lambdaにデプロイするために書き換える
- 以下のパッケージをimportします
その後、以下のコマンドを実行します
import ( "context" "net/http" "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" )
go get .
- GinLambdaを宣言
var ginLambda *ginadapter.GinLambda
- func mainをfunc initに書き換え、以下のように変更する
func init() { router := gin.Default() router.GET("/albums", getAlbums) router.POST("/albums", postAlbums) router.GET("/albums/:id", getAlbumByID) ginLambda = ginadapter.New(router) }
- Handler, main関数を用意する
func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { return ginLambda.ProxyWithContext(ctx, req) } func main() { lambda.Start(Handler) }
最終的にmain.goはこのようになります。
package main
import (
"context"
"net/http"
"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"
)
// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// albums slice to seed record album data.
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
var ginLambda *ginadapter.GinLambda
func init() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)
router.GET("/albums/:id", getAlbumByID)
ginLambda = ginadapter.New(router)
}
// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
var newAlbum album
if err := c.BindJSON(&newAlbum); err != nil {
return
}
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
// getAlbumByID locates the album whose ID value matches the id
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
return ginLambda.ProxyWithContext(ctx, req)
}
func main() {
lambda.Start(Handler)
}
AWS CDK
AWS CDKが初めての方
こちらのAWS Cloud Development Kit(AWS CDK)v2の、Getting startedを実際に行ってみるとわかりやすいです。
今回の実装ではTypeScriptを用います。
AWS Lambda, API Gateway
今回の以下のようなサーバーレスアーキテクチャーをAWS CDKで構築します。
Gin側での準備
今回は、.zipファイルアーカイブを使用してGo Lambda関数をデプロイします。
AWS公式を参考に、以下を実行します。(maxOS/Linuxを想定しています。Windowsの方は左のAWS公式を参照してください。)
-
main.goをコンパイルする
x86_64 architectureの場合/gin$ GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bin/bootstrap main.go
-
zipファイルを作成する
/gin$ zip bin/myFunction.zip bin/bootstrap
AWS CDK
-
init
/infra$ cdk init app --language typescript
-
infra/lib/gin-lambda-stack.tsを以下のように変更し、それに合わせて、infra/bin/infra.ts内のstack名も変更します。
gin-lambda-stack.ts
/infra/lib/gin-lambda-stack.tsimport { aws_s3_assets as assets, aws_lambda as lambda, aws_apigateway as gateway, Stack, StackProps, Duration, } from "aws-cdk-lib"; import * as path from "path"; import { Construct } from "constructs"; export class GinLambdaStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const lambdaAsset = new assets.Asset(this, "GinServerAsset", { path: path.join(__dirname, "../../gin/bin"), }); const lambdaFn = new lambda.Function(this, "GinServer", { code: lambda.Code.fromBucket(lambdaAsset.bucket, lambdaAsset.s3ObjectKey), timeout: Duration.minutes(5), runtime: lambda.Runtime.PROVIDED_AL2, handler: "main", }); new gateway.LambdaRestApi( this, "GinServerLambdaEndpoint", { handler: lambdaFn, } ) } }
-
デプロイ
/infra$ cdk bootstrap
デプロイ後にAPI endpointが表示されます
/infra$ cdk deploy
✅ InfraStack ✨ Deployment time: 32.11s Outputs: InfraStack.GinServerLambdaEndpoint******** = https://**********.execute-api.<region>.amazonaws.com/prod/
実際にAPIを叩くと、Gin Tutorialと同じレスポンスが返ってきます
任意の場所$ curl https://**********.execute-api.<region>.amazonaws.com/prod/albums
[ { "id": "1", "title": "Blue Train", "artist": "John Coltrane", "price": 56.99 }, { "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }, { "id": "3", "title": "Sarah Vaughan and Clifford Brown", "artist": "Sarah Vaughan", "price": 39.99 } ]
参考文献
-
https://blog.dennisokeeffe.com/blog/2020-07-06-deploying-a-serverless-go-api
- この記事を参考にしつつ、現在のAWS CDKのバージョンに適したコードに変更しました。