1
1

Ginを用いたサーバーレスアプリケーションをAWS CDKでデプロイする方法

Posted at

はじめに

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にデプロイするために書き換える

  1. 以下のパッケージを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 .
    
  2. GinLambdaを宣言
    var ginLambda *ginadapter.GinLambda
    
  3. 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)
    }
    
  4. 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で構築します。

 AWS_Architecture.jpg

Gin側での準備

今回は、.zipファイルアーカイブを使用してGo Lambda関数をデプロイします。
AWS公式を参考に、以下を実行します。(maxOS/Linuxを想定しています。Windowsの方は左のAWS公式を参照してください。)

  1. main.goをコンパイルする
    x86_64 architectureの場合

    /gin
    $ GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bin/bootstrap main.go
    
  2. zipファイルを作成する

    /gin
    $ zip bin/myFunction.zip bin/bootstrap
    

AWS CDK

  1. init

    /infra
    $ cdk init app --language typescript
    
  2. infra/lib/gin-lambda-stack.tsを以下のように変更し、それに合わせて、infra/bin/infra.ts内のstack名も変更します。

    gin-lambda-stack.ts
    /infra/lib/gin-lambda-stack.ts
    import {
      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,
          }
        )
      }
    }
    
    
  3. デプロイ

    /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
        }
    ]
    
    

参考文献

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1