このシリーズでは、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で記述した関数をデプロイするためのコードとなっている。
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
ファイルの中にあるEnv
やStackName
などの値を渡すことができる。
今回は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ではない場合、引数props
のStackProps
がpropsに代入される。
引数props
はLambdaStackProps構造体
なのでprops.StackProps
やprops.Env
でアクセスできる。
あとは、これらの引数をawscdk.NewStack
の引数に渡せばスタックが完成する。
ざっくりまとめると、スタックを作成するために必要な情報を引数で渡して、それを使ってスタックを作成しているってこと。
で、ここまででは実は何も意味がなくてスタックはあくまでもLambda
やDynamoDB
などの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
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
CloudFormationの方にも追加されていることがわかります。
最後に、お金がかかると怖いので(かからないはずだけど)、cdk destroy
で削除しておきます。
実行するとLambdaとCloudFormationのどちらからも削除されていました。
まとめ
とりあえず今回はCDKってどんな感じが雰囲気を掴むために簡単ではありましたが理解することができました。
色々と設定項目があるので、それを都度調べて知っていくしかないなって印象です。
次の記事では、DynamoDBをスタックに追加していきたいと思います!
で、それができたらlambdaの中の関数をDynamoに追加したり削除したりするHandlerを作成するという流れでやっていきます!