LoginSignup
0
2

CDK×Lambda×golang×Dynamoでアプリを作ってみる CDK編(第一回)

Last updated at Posted at 2024-05-24

このシリーズでは、CDKLambdagolangDynamoDBの構成で学習をしてみたい自分がその記録も残しつつ、私と同じような、とりあえずこのような構成の雰囲気を掴んでみたいという方向けに書いています。

CDKとは

コードでクラウド インフラストラクチャを定義し、AWS CloudFormation を通じてプロビジョニングするためのオープンソース ソフトウェア開発フレームワークです。(公式)

まあ、簡単にいうとUI上でEC2とかS3とかをぽちぽち設定するんじゃなくて、コードで起動できるよってこと。管理もしやすいから便利だね。

何はともあれ触ってみよう。

とりあえずディレクトリを作成し、モジュールを初期化。

mkdir first_prac_go
cd first_prac_go
cdk init --language go

するとgo.modや他の必要ファイルが色々と作成される。
cdk init --language goでは以下ファイルが生成されます。

.
├── README.md
├── cdk.json
├── directory_name_test.go
├── directory_name.go
├── go.mod
└── go.sum

で、ちょっと面倒ですが、cdkディレクトリにcdk.jsonを移動し、2行目を以下のように変更してください。

{
  "app": "go mod download && go run main.go",

go.modの中身はmodule名とgoのversionが記載されているだけ。

module first_prac_go

go 1.21.6

もしcdkコマンドがnot foundで使えない方は以下コマンドでインストールしましょう。

npm install -g aws-cdk

その後which cdkでインストール先を確認します。

~/Desktop/.nodebrew/current/bin/cdk

次に、.zshrc ファイルに $PATH を永続的に追加します。

echo 'export PATH=$PATH:/Users/fukudanaoto/Desktop/.nodebrew/current/bin' >> ~/.zshrc
source ~/.zshrc

次にCDKライブラリをインストール

go get github.com/aws/aws-cdk-go/awscdk/v2
go get github.com/aws/aws-cdk-go/awscdk/v2/awslambda
go get github.com/aws/constructs-go/constructs/v10
go get github.com/aws/jsii-runtime-go

go mod tidyは忘れずに。

無事インストールされたらCDK用にmain.goを直下に作成してみる。
ちなみに今回は以下のようなディレクトリ構成で作成してみようと思う。

/first_prac_go
  /lambda
    /dynamoDBHandler
      main.go        # Lambda関数のソースコード
  /cdk
    main.go          # CDKデプロイメントコード 今はここを作成する。
  go.mod
  go.sum
touch main.go

main.go 全体像

このmain.goに以下のように記述してみる。
これはlamdaで記述した関数をデプロイするためのコードとなっている。

main.go
package main

import (
	"log"
	"os"

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

type LambdaStackProps struct {
	awscdk.StackProps
}

func NewLambdaStack(scope constructs.Construct, id string, props *LambdaStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}

	stack := awscdk.NewStack(scope, &id, &sprops)

	// Lambda関数の定義
	awslambda.NewFunction(stack, jsii.String("MyFunction"), &awslambda.FunctionProps{
		Runtime: awslambda.Runtime_PROVIDED_AL2(),
		Handler: jsii.String("main"),                                                     // ハンドラー関数名を適切に指定
		Code:    awslambda.Code_FromAsset(jsii.String("lambda/dynamoDBHandler"), nil), // Lambdaコードのパスを指定
	})

	return stack
}

func main() {
	// .envファイルを読み込む
	err := godotenv.Load()
	if err != nil {
		log.Fatalf("Error loading .env file")
	}
	// スタックの入れ物を作るイメージ
	app := awscdk.NewApp(nil)

	// で、その入れ物にLambdaを配置する。この時、idは"LambdaStack"
	NewLambdaStack(app, "LambdaStack", &LambdaStackProps{
		awscdk.StackProps{
			Env: env(),
		},
	})

	app.Synth(nil)
}

func env() *awscdk.Environment {
	awsAccountId := os.Getenv("AWS_ACCOUNT_ID")
	awsRegion := os.Getenv("AWS_REGION")

	return &awscdk.Environment{
		Account: jsii.String(awsAccountId),
		Region:  jsii.String(awsRegion),
	}
}

上記を分割して説明を加えていこうと思う。

import

まず、ここだがこれは説明する程でもないが、packageの名前(通常はディレクトリ名)と先ほどインストールした外部ライブラリを読み込んでいる。反映されずエラ〜が出ている場合go mod tidyをしてみると解決するかも。

package main

import (
	"github.com/aws/aws-cdk-go/awscdk/v2"
	"github.com/aws/aws-cdk-go/awscdk/v2/awslambda"
	"github.com/aws/constructs-go/constructs/v10"
	"github.com/aws/jsii-runtime-go"
)
  • github.com/aws/aws-cdk-go/awscdk/v2
    AWS CDK(Cloud Development Kit)のGo言語版で、AWSリソースを定義してデプロイするためのライブラリです。CDKを使用することで、Go言語でAWSリソースの定義やデプロイを行うことができます。

  • github.com/aws/aws-cdk-go/awscdk/v2/awslambda
    AWS CDKのGo言語版で、AWS Lambda関数を定義してデプロイするためのライブラリです。Lambda関数はサーバーレスアプリケーションを構築する際に使用されます。

  • github.com/aws/constructs-go/constructs/v10
    AWS CDKのコンストラクツを定義するための基本ライブラリです。CDKではコンストラクツを使用してAWSリソースを構築しますが、このライブラリはその基本となるコンストラクツを提供します。

  • github.com/aws/jsii-runtime-go
    JSII(JavaScript Interoperability Interface)のランタイムライブラリで、AWS CDKのGo言語版で使用されます。JSIIは異なるプログラミング言語間での相互運用性を実現するための仕組みであり、AWS CDKはJSIIを使用して複数のプログラミング言語から利用することができます。

Struct

次にStructだが、StackPropsはStackProps.goで定義されている。

type LambdaStackProps struct {
	awscdk.StackProps
}

このファイルを見ると色々と設定項目があることが分かる。

フィールド 説明 デフォルト
AnalyticsReporting ランタイムバージョン情報を含めるかどうか。デフォルトはApp内のanalyticsReporting設定または'aws:cdk:version-reporting'コンテキストキーの値。
CrossRegionReferences ネイティブなクロスリージョンスタック参照を許可するかどうか。実験的な機能で、デフォルトはfalse。 false
Description スタックの説明 未指定
Env スタックのデプロイ先AWS環境(アカウント/リージョン)。具体的な値を設定するか、環境変数 CDK_DEFAULT_REGION / CDK_DEFAULT_ACCOUNT の値を使用。
PermissionsBoundary ステージ内のすべてのIAMロールとユーザーに適用するアクセス許可境界。 未指定の場合は適用されない
StackName スタックのデプロイ名 構築パスから派生
SuppressTemplateIndentation CloudFormationテンプレートのインデント抑制を有効にするかどうか 未指定時はデフォルト値が使用される
Synthesizer スタックのデプロイ時に使用するシンセサイザーの合成方法。 未指定時はデフォルト値が使用される
Tags スタックとリソースに適用されるタグ
TerminationProtection スタックの終了保護を有効にするかどうか false

NewLambdaStack関数

次にNewLambdaStack関数を見てみよう。

func NewLambdaStack(scope constructs.Construct, id string, props *LambdaStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}

	stack := awscdk.NewStack(scope, &id, &sprops)

	// Lambda関数の定義
	awslambda.NewFunction(stack, jsii.String("MyFunction"), &awslambda.FunctionProps{
		Runtime: a.Runtime_PROVIDED_AL2(),
		Handler: jsii.String("main"),
		Code:    awslambda.Code_FromAsset(jsii.String("dynamoDBHandler/main.go")), // Lambdaコードのパス
	})

	return stack
}

ここでは、Lamdaのスタックを作成する関数を定義している。
ちなみにスタックとは、一連のリソース(例えば、EC2インスタンス、S3バケット、RDSデータベースなど)をまとめてデプロイするために使用されるもので、スタックを使用することで、リソースの作成、更新、削除などの管理を一括して行うことができるものだ。

まず、NewLambdaStackは引数をscope id propsの3つを取り、awscdk.Stack型を返す。

引数

scope

scopeパラメータは新しいスタックやリソースが作成される親の構成要素を表します。スタックが存在するコンテキストや名前空間を定義します。

まあ分かりづらいけど、スタックを配置するための場所ってことかな。例えば、同じ場所に置くだけならあまり気にする必要はないが、あるスタックAの中にさらにスタックBを置きたいとなった場合、スタックBを作成する時にスタックAをスコープとすることで配置することができる。

例えば、バックエンドとフロントエンドでそれぞれスタックを分けたいが、最終的にはまとめてcdkとしてデプロイしたい場合、親のスタックを作り、それぞれ親のスタックをスコープとして使用したりとかも1つの方法かな。

// 親スタックを作成
parentStack := awscdk.NewStack(app, "ParentStack", nil)

// 子スタックを作成し、親スタック内に配置
childStack := NewChildStack(parentStack, "ChildStack")

ちょっと話が逸れてしまったが、こんな感じでどこに配置するかを渡している。

id

次に、idだが、これはそのまま。まあ、プロジェクトやチームの方針、開発者の好みによると思うが、説明的で一意性を保つ名前を付けることが一般的なベストプラクティスとされている。個人開発なら正直なんでもいいと思う。

props

引数の最後のpropsだが、これはスタック全体の設定で、さっき上で確認したStackProps.goファイルの中にあるEnvStackNameなどの値を渡すことができる。

今回はenv関数でAccountとRegionを設定し、Envに渡している。

props初期化とスタック作成

次に以下の部分を見ていく。

var sprops awscdk.StackProps
if props != nil {
	sprops = props.StackProps
}

stack := awscdk.NewStack(scope, &id, &sprops)

まず1行目でawscdk.Stackprops型の変数、propsを宣言している。これは当然、ゼロ値となる。まあ設定項目はnilかfalseになるっぽい。

で、引数のpropsがnilではない場合、引数propsStackPropsがpropsに代入される。
引数propsLambdaStackProps構造体なのでprops.StackPropsprops.Envでアクセスできる。

あとは、これらの引数をawscdk.NewStackの引数に渡せばスタックが完成する。

ざっくりまとめると、スタックを作成するために必要な情報を引数で渡して、それを使ってスタックを作成しているってこと。

で、ここまででは実は何も意味がなくてスタックはあくまでもLambdaDynamoDBなどのAWSリソースを配置するための場所である。

だから次のステップで以下で作成したスタックに必要なものを追加していく。

スタックにlambdaの配置

さて、NewLambdaStack関数の最後の説明となる。
先ほど作成したスタックにlambdaを配置していこう。

まず、awslambda.Newfunction関数があるが、その定義は以下の通り

func NewFunction(scope constructs.Construct, id *string, props *FunctionProps) Function {
	_init_.Initialize()

	if err := validateNewFunctionParameters(scope, id, props); err != nil {
		panic(err)
	}
	j := jsiiProxy_Function{}

	_jsii_.Create(
		"aws-cdk-lib.aws_lambda.Function",
		[]interface{}{scope, id, props},
		&j,
	)

	return &j
}

この引数に先ほどawscdk.NewStackで作成したstackを渡し、ここに作るよということを伝える。次に第二引数で一意となるidを渡す。第三匹数にpropsとして&awslambda.FunctionPropsの構造体を渡している。

awslambda.FunctionProps構造体では、かなりの設定項目があるがマストは以下の通り。

フィールド 説明
Code Code Lambda関数のソースコード。Amazon S3バケット内のファイルを指すか、インラインテキストとして指定。
Handler *string Lambdaが実行するコード内のメソッド名。ファイル名や名前空間などを含めた形式で指定。
Runtime Runtime Lambda関数の実行環境。Dockerイメージから定義する場合は Runtime.FROM_IMAGE を使用。

他にも数十個様々あるが、使ううちに知っていくしかない。以下はいくつかの例。

1 2 3
MaxEventAge awscdk.Duration Lambda関数にリクエストを送信する最大期間。最小60秒、最大6時間。デフォルトは6時間。
OnFailure IDestination 失敗した呼び出しの送信先。デフォルトはなし。
OnSuccess IDestination 成功した呼び出しの送信先。デフォルトはなし。
RetryAttempts *float64 エラー時の再試行回数。最小0回、最大2回。デフォルトは2回。
AdotInstrumentation AdotInstrumentationConfig AWS Distro for OpenTelemetry (ADOT) の設定。デフォルトはなし。
AllowAllOutbound *bool Lambdaがすべてのネットワークトラフィックを送信できるかどうか。デフォルトはtrue。
AllowPublicSubnet *bool パブリックサブネットにLambdaを配置する場合の制限を承認。デフォルトはfalse。
ApplicationLogLevel *string 関数のアプリケーションログレベル。デフォルトは "INFO"。

ちなみに、ご存知の通りRunTimeについては、2023年の12月からgo1.xは廃止されAmazon Linux2ランタイムでのみサポートされている。

またちなみに、jsii.Stringとしている理由として、CDKのコンストラクトやプロパティはTypeScriptのオブジェクトを期待しているため、Goの文字列型を直接渡すことはできないからである。そのため、jsii.Stringを使用して変換している。

jsii(JavaScript Interoperability Interface)は、異なるプログラミング言語間での相互運用性を提供するためのツールキットで、AWS Cloud Development Kit(CDK)のようなライブラリがTypeScriptで書かれていても、GoやPython、Javaなどの他の言語からも利用できるようにするために開発されたものらしい。

さて、話を戻すとawslambda.NewFunctionでは、CDKが理解できる形で、どのスタックにどんな名前でどんな設定で突っ込むかを決めるということだ。

awslambda.Code_FromAssetでは、ExcludeやDeployTimeなどを以下のように渡せるが、今回はnilとする。

assetOptions := &awss3assets.AssetOptions{
    Exclude: jsii.Strings("node_modules", "*.md"),
}

Code: awslambda.Code_FromAsset(jsii.String("lambda/dynamoDBHandler"), assetOptions)

さて、これで完了した。

awslambda.NewFunction(stack, jsii.String("MyFunction"), &awslambda.FunctionProps{
    Runtime: awslambda.Runtime_PROVIDED_AL2(),
    Handler: jsii.String("main"),
    Code:    awslambda.Code_FromAsset(jsii.String("lambda/dynamoDBHandler"), nil), 
})

最後に、以下を実行して.envファイルを作成しよう。
大丈夫だとは思いますが.gitignoreへの追加は忘れないようにしましょう。

go get github.com/joho/godotenv
.env
AWS_ACCOUNT_ID=あなたのACCOUNT_ID
AWS_REGION=あなたのREGION

上が完了したらimportとmain関数に以下のように追加する。

package main

import (
	"os"
	"log"
    ...
	"github.com/joho/godotenv"
)
func main() {
    // .envファイルを読み込む
    err := godotenv.Load()
    if err != nil {
        log.Fatalf("Error loading .env file")
    }

そして、最後にmain関数のapp.Synth(nil)で、定義されたスタックとその中のリソースをCloudFormationテンプレートに変換します。

デプロイ

コマンドでデプロイするためにはCDK CLIが必要なので以下を実行する。

npm install -g aws-cdk

で、lambda関数もgo buildでビルドしておく。
次にcdk synthをfirst_prac_goディレクトリで実行する。

成功すると、AWS CDK CLI はコマンドプロンプトに YAML 形式の AWS CloudFormation テンプレートを出力します。JSON 形式のテンプレートもディレクトリにcdk.outが保存されます。

そして、以下コマンドを実行すればデプロイできるが、その前にブートスラップを1回実行する必要があるらしい。

cdk bootstrap aws://ACCOUNT-NUMBER/REGION
cdk deploy

これで無事デプロイ成功しました!
スクリーンショット 2024-05-24 23.50.23.png

CloudFormationの方にも追加されていることがわかります。
スクリーンショット 2024-05-24 23.51.11.png

最後に、お金がかかると怖いので(かからないはずだけど)、cdk destroyで削除しておきます。
実行するとLambdaとCloudFormationのどちらからも削除されていました。

まとめ

とりあえず今回はCDKってどんな感じが雰囲気を掴むために簡単ではありましたが理解することができました。
色々と設定項目があるので、それを都度調べて知っていくしかないなって印象です。

次の記事では、DynamoDBをスタックに追加していきたいと思います!
で、それができたらlambdaの中の関数をDynamoに追加したり削除したりするHandlerを作成するという流れでやっていきます!

0
2
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
0
2