AWS CDK for Goを使用して、Go言語で書かれたLambda関数を定期実行する

## はじめに

AWS CDK（Cloud Development Kit）は、クラウドリソースをコードで定義し、デプロイするためのオープンソースのソフトウェア開発フレームワークです。この記事では、AWS CDK for Goを使用してGo言語で書かれたLambda関数を定期的に実行するスケジュールを設定する方法を説明します。

## 前提条件

- AWSアカウントがすでに設定されていること。
- AWS CDKを操作するCLIがインストールされていること。

CDKのCLIのインストールについては[こちら](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/cli.html)をご覧ください

## 環境

- Go 1.20

## プロジェクトの初期化

新しいディレクトリを作成し、その中に移動します。

```bash
mkdir go-lambda-cdk
cd go-lambda-cdk
```

以下のコマンドを実行して新しいCDKプロジェクトを初期化します。

```bash
cdk init app --language=go 
```

初期化すると次のようなファイルが生成されます。

```bash
.
├── README.md
├── cdk.json
├── go-lambda-cdk.go
├── go-lambda-cdk_test.go
├── go.mod
└── go.sum
```

次に必要なAWS CDKライブラリをインポートします。

```bash
go mod tidy
```

## Lambda関数の作成

Go言語でLambda関数を書きます。詳しくは[こちら](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-handler.html)をご覧ください。

ファイルを作成します。

```bash
mkdir handler
touch handler/main.go
```

`handler/main.go`を以下のように編集しました。

```go
package main

import (
	"context"

	"github.com/aws/aws-lambda-go/lambda"
)

func HandleRequest(ctx context.Context) (*string, error) {
	message := "Hello World!"
	return &message, nil
}

func main() {
	lambda.Start(HandleRequest)
}
```

呼び出されると`Hello World`を出力する単純な関数です。

## ****CDKでLambdaリソースの定義****

`go-lambda-cdk.go`を編集します。

```go
package main

import (
	"os"
	"os/exec"

	"github.com/aws/aws-cdk-go/awscdk"
	"github.com/aws/aws-cdk-go/awscdk/awslambda"
	"github.com/aws/constructs-go/constructs/v3"
	"github.com/aws/jsii-runtime-go"
	"github.com/joho/godotenv"
)

type GoLambdaCdkStackProps struct {
	awscdk.StackProps
}

func NewGoLambdaCdkStack(scope constructs.Construct, id string, props *GoLambdaCdkStackProps) (awscdk.Stack, error) {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}
	stack := awscdk.NewStack(scope, &id, &sprops)

	// Goのビルド
	buildCmd := exec.Command("go", "build", "-tags", " lambda.norpc", "-o", "bin/bootstrap", "handler/main.go")
	buildCmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64")
	if err := buildCmd.Run(); err != nil {
		return nil, err
	}

	// Lambda関数の定義
	awslambda.NewFunction(stack, jsii.String("golang-lambda"), &awslambda.FunctionProps{
		FunctionName: jsii.String("golang-lambda-function"),
		Runtime:      awslambda.Runtime_PROVIDED_AL2(),
		Code:         awslambda.Code_FromAsset(jsii.String("bin"), nil),
		Handler:      jsii.String("main"),
	})

	return stack, nil
}

func main() {
	defer jsii.Close()

	// envファイルの読み込み
	if err := godotenv.Load(); err != nil {
		panic(err)
	}

	app := awscdk.NewApp(nil)

	_, err := NewGoLambdaCdkStack(app, "GoLambdaCdkStack", &GoLambdaCdkStackProps{
		awscdk.StackProps{
			Env: env(),
		},
	})

	if err != nil {
		panic(err)
	}

	app.Synth(nil)
}

func env() *awscdk.Environment {
	return &awscdk.Environment{
		Account: jsii.String(os.Getenv("AWS_ACCOUNT")),
		Region:  jsii.String(os.Getenv("AWS_REGION")),
	}
}
```

ランタイムには`provided.al2`を指定しています。また、実行ファイルには `bootstrap` という名前をつける必要があるため、ビルド時に指定しています。詳しくは[こちら](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-package.html)をご覧ください。

環境変数からaccount_idとregionを指定しています。

## デプロイ

以下のコマンドでデプロイされます。

```bash
cdk deploy
```

AWSの画面からデプロイされているのが確認できると思います。

![qiita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783425/6cb3f9b0-f21c-1d86-a6c6-1bd787ff538f.png)


## ****定期実行のスケジュール設定****

次に定期実行のスケジュール設定します。

```go
func NewGoLambdaCdkStack(scope constructs.Construct, id string, props *GoLambdaCdkStackProps) (awscdk.Stack, error) {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}
	stack := awscdk.NewStack(scope, &id, &sprops)

	// Goのビルド
	buildCmd := exec.Command("go", "build", "-tags", " lambda.norpc", "-o", "bin/bootstrap", "handler/main.go")
	buildCmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64")
	if err := buildCmd.Run(); err != nil {
		return nil, err
	}

	// Lambda関数の定義
	lambdaFunc := awslambda.NewFunction(stack, jsii.String("golang-lambda"), &awslambda.FunctionProps{
		FunctionName: jsii.String("golang-lambda-function"),
		Runtime:      awslambda.Runtime_PROVIDED_AL2(),
		Code:         awslambda.Code_FromAsset(jsii.String("bin"), nil),
		Handler:      jsii.String("main"),
	})

	// 定期実行の設定
	rule := awsevents.NewRule(stack, jsii.String("lambda-batch-rule"), &awsevents.RuleProps{
		Schedule: awsevents.Schedule_Cron(&awsevents.CronOptions{
			Minute: jsii.String("*/5"),
		}),
	})

	rule.AddTarget(awseventstargets.NewLambdaFunction(lambdaFunc, nil))

	return stack, nil
}
```

`NewGoLambdaCdkStack`の中身を編集しました。定期実行のスケジュールは、CloudWatch Eventsのルールを使用しており、5分に一度Lambda関数が実行されます。

## 再****デプロイ****

再びデプロイします。

```bash
cdk deploy
```

![Screenshot 2023-11-26 at 20.17.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783425/68e02596-ae16-9a45-5420-958a3c6c637f.png)


指定したスケジュールでLambda関数が実行されるようになります。

## 参考
https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/cli.html

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-handler.html

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-package.html

https://pkg.go.dev/github.com/aws/aws-cdk-go/awscdk@v1.204.0-devpreview/awslambda

https://pkg.go.dev/github.com/aws/aws-cdk-go/awscdk@v1.204.0-devpreview/awsevents