テックタッチアドベントカレンダー 13 日目を担当する taisa です。少しずつ減らしていった体重が 2 ヶ月で 6 kg リバウンドして完全に元に戻りました。
さて今年、AWS CDK v2 の開発者プレビューで Go を使えるようになり、CDK for Terraform でも Go を実験的(experimental)に使えるようになりました。
これらは、まだ開発者プレビューや実験的であるため本番においては投入できませんが、アドベントカレンダーをきっかけに触ってみました。また他にも Go が利用できる IaC(Infrastructure as Code) プラットフォームとして Pulumi があるので合わせて触ってみました。
各フレームワーク・ライブラリの概要
本記事で取り上げる各フレームワーク・ライブラリの概要について簡単に説明します。特に比較の考察はないので予めご容赦ください。下記は比較記事のリンクとなります。
AWS CDK
AWS Cloud Development Kit (AWS CDK) は、AWS が提供するオープンソースの IaC フレームワークです。AWS CloudFormation を通じてデプロイします。現在使える言語は TypeScript、Python、Java、C# に加えて AWS CDK v2 開発者プレビューで Go があります。
CDK for Terraform
CDK for Terraform は、HashiCorp 社が提供するオープンソースの IaC ソフトウェアです。AWS CDK のコンセプトとライブラリを活用しながら、AWS だけでなく様々なプロバイダーに対応しています。
CDK for Terraform を使用すると、HashiCorp構成言語(HCL)を学習せず、Terraform エコシステム全体にアクセスでき、既存のツールチェーンの機能をテストや依存関係の管理などに活用できます。現在、CDK for Terraform 自体がベータ版で、現在使える言語は、TypeScript、Python、Java、C# に加えて実験的として Go があります。
Pulumi
Pulumi は、Pulumi 社が提供するオープンソースの IaC ソフトウェアで、様々なプロバイダーに対応しています。現在利用できる言語は、TypeScript、Python、C#、Go で、今後のロードマップに Java、Ruby、PowerShell サポートが予定されています。
Pulumi は今年、クラウドベンダ自身が Pulumi のクラウド対応機能のメンテナンスを担当することで、常に最新のクラウド対応が即時に行われる「Native Providers for Azure and Google Cloud」という新機能をリリースし、後程 AWS 対応のものもリリースしました。
また、プラットフォーム機能を提供していて、チームで利用する場合は有償ですが、今年その価格改定も行われ、以前より利用しやすくなったようです。
実際に動かしてみる
前置きが長くなりましたが、実際に動かしてみます。本記事では、API Gateway 経由で Lambda Function を実行し、POST した内容がレスポンスとして返ってくる簡単なサンプルアプリを構築します。
# 実行例 % curl -X POST -H "Content-Type: application/json" -d '{"name":"World!"}' https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/hello Hello World!
Lambda Function サンプルコード
実行する Lamba Function サンプルコードは下記を利用します。
package main import ( "context" "encoding/json" "errors" "fmt" "net/http" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) type MyEvent struct { Name string } func main() { lambda.Start(HandleRequest) } func HandleRequest(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { event := MyEvent{} if err := json.Unmarshal([]byte(req.Body), &event); err != nil { return events.APIGatewayProxyResponse{ StatusCode: http.StatusBadRequest, }, errors.New("error") } return events.APIGatewayProxyResponse{ StatusCode: http.StatusOK, Body: fmt.Sprintf("Hello %s!", event.Name), }, nil }
Lambda Function は下記のように handler 配下へ配置する構成で進めます。
. ├── go.mod ├── go.sum ├── handler │ ├── main │ └── main.go # lambda function └── main.go # IaC コード
AWS CDK
ではまず AWS CDK を使って構築します。下記コマンドにてインストールしプロジェクトのセットアップを行います。なお、あらかじめ AWSの設定ファイルと認証情報ファイルの設定 が行われている前提で進めます。
# インストール % npm install -g aws-cdk # プロジェクトセットアップ % mkdir sandbox-aws-cdk && cd sandbox-aws-cdk % cdk init --language=go % go mod tidy
プロジェクトを作成しcdk init
するとコードが自動生成されるので、Lambda Function 設定と API Gateway 設定の記述を追記します。
// sandbox-aws-cdk/sandbox-aws-cdk.go package main import ( "github.com/aws/aws-cdk-go/awscdk/v2" "github.com/aws/aws-cdk-go/awscdk/v2/awsapigateway" "github.com/aws/aws-cdk-go/awscdk/v2/awslambda" "github.com/aws/aws-cdk-go/awscdk/v2/awss3assets" "github.com/aws/constructs-go/constructs/v10" "github.com/aws/jsii-runtime-go" ) type SandboxAwsCdkStackProps struct { awscdk.StackProps } func NewSandboxAwsCdkStack(scope constructs.Construct, id string, props *SandboxAwsCdkStackProps) awscdk.Stack { var sprops awscdk.StackProps if props != nil { sprops = props.StackProps } stack := awscdk.NewStack(scope, &id, &sprops) // lambda function 設定 lambdaFn := awslambda.NewFunction(stack, jsii.String("my-lambda-aws-cdk"), &awslambda.FunctionProps{ FunctionName: jsii.String("my-lambda-aws-cdk-func"), Runtime: awslambda.Runtime_GO_1_X(), Code: awslambda.AssetCode_FromAsset(jsii.String("handler"), &awss3assets.AssetOptions{}), Handler: jsii.String("main"), }) // api gateway 設定 apiGW := awsapigateway.NewRestApi(stack, jsii.String("my-api-gw-aws-cdk"), nil) apiGW.Root(). AddResource(jsii.String("hello"), nil). AddMethod(jsii.String("POST"), awsapigateway.NewLambdaIntegration(lambdaFn, nil), nil) return stack } func main() { app := awscdk.NewApp(nil) NewSandboxAwsCdkStack(app, "SandboxAwsCdkStack", &SandboxAwsCdkStackProps{ awscdk.StackProps{ Env: env(), }, }) app.Synth(nil) } // env determines the AWS environment (account+region) in which our stack is to // be deployed. For more information see: https://docs.aws.amazon.com/cdk/latest/guide/environments.html func env() *awscdk.Environment { // If unspecified, this stack will be "environment-agnostic". // Account/Region-dependent features and context lookups will not work, but a // single synthesized template can be deployed anywhere. //--------------------------------------------------------------------------- return nil }
デプロイするために、まず下記コマンドを実行する必要があるので実行します。
% cdk bootstrap
cdk bootstrap
実行後、下記コマンドで Lambda Function をビルドしデプロイします。
# ビルド % GOARCH=amd64 GOOS=linux go build -o handler/main handler/main.go # デプロイ % cdk deploy SandboxAwsCdkStack: deploying... ・・・ Outputs: SandboxAwsCdkStack.myapigwawscdkEndpoint987172E9 = https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/
デプロイ後コンソール画面確認
デプロイができたら下記リクエストを実行して、動作確認をします。
% curl -X POST -H "Content-Type: application/json" -d '{"name":"World!"}' https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/hello Hello World!!
Hello World!! が出力されました。動作確認ができたら下記コマンドで削除しておきます。
% cdk destroy SandboxAwsCdkStack: destroying... ✅ SandboxAwsCdkStack: destroyed
CDK for Terraform
続いて CDK for Terraform で構築します。下記コマンドでインストールしプロジェクトのセットアップをします。
# インストール % brew install hashicorp/tap/terraform # プロジェクトセットアップ % mkdir sandbox-terraform-cdk && cd sandbox-terraform-cdk % npm install --global cdktf-cli % cdktf init --template="go" --local
今回は AWS を利用するので、cdkft.json に aws を追加します。
{ "language": "go", "app": "go run main.go", "codeMakerOutput": "generated", - "terraformProviders": [], + "terraformProviders": [ + "hashicorp/aws@~> 3.42" + ], "terraformModules": [], "context": { "excludeStackIdFromLogicalIds": "true", "allowSepCharsInLogicalIds": "true" } }
cdkft.json を編集後、下記コマンドを実行し AWS 用ライブラリをダウンロードします。これにはめちゃくちゃ時間がかかるので、気長に待ちましょう。ダウンロードが終わると generated ディレクトリにライブラリが大量に入ってきます。
% cdktf get
# めちゃくちゃ時間かかる..
downloading and generating modules and providers...
% go mod tidy
cdktf init
を実行するとコードが自動生成されるので、必要な実装を追加します。ダウンロードした AWS ライブラリを利用するには"cdk.tf/go/stack/generated/hashicorp/aws"
を import します。
// sandbox-terraform-cdk/main.go package main import ( "path/filepath" "time" "cdk.tf/go/stack/generated/hashicorp/aws" "cdk.tf/go/stack/generated/hashicorp/aws/apigateway" "cdk.tf/go/stack/generated/hashicorp/aws/iam" "cdk.tf/go/stack/generated/hashicorp/aws/lambdafunction" "github.com/aws/constructs-go/constructs/v10" "github.com/aws/jsii-runtime-go" "github.com/hashicorp/terraform-cdk-go/cdktf" ) func NewMyStack(scope constructs.Construct, id string) cdktf.TerraformStack { stack := cdktf.NewTerraformStack(scope, &id) // AWS 利用宣言 aws.NewAwsProvider(stack, jsii.String("aws"), &aws.AwsProviderConfig{ Region: jsii.String("ap-northeast-1"), }) // role 作成 role := iam.NewIamRole(stack, jsii.String("my-role-tf-cdk"), &iam.IamRoleConfig{ AssumeRolePolicy: jsii.String(`{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }`), }) absPath, _ := filepath.Abs("handler/main.zip") // lambda function 設定 lambdaFn := lambdafunction.NewLambdaFunction(stack, jsii.String("my-Lambda-tf-cdk"), &lambdafunction.LambdaFunctionConfig{ FunctionName: jsii.String("my-lambda-tf-cdk-func"), Runtime: jsii.String("go1.x"), Filename: jsii.String(absPath), Handler: jsii.String("main"), Role: role.Arn(), }) // api gateway 設定 apiRest := apigateway.NewApiGatewayRestApi(stack, jsii.String("my-api-rest-tf-cdk"), &apigateway.ApiGatewayRestApiConfig{ Name: jsii.String("my-api-gw-tf-cdk-name"), }) apiResource := apigateway.NewApiGatewayResource(stack, jsii.String("my-api-resource-tf-cdk"), &apigateway.ApiGatewayResourceConfig{ PathPart: jsii.String("hello"), ParentId: apiRest.RootResourceId(), RestApiId: apiRest.Id(), }) apiMethod := apigateway.NewApiGatewayMethod(stack, jsii.String("my-api-method-tf-cdk"), &apigateway.ApiGatewayMethodConfig{ ResourceId: apiResource.Id(), RestApiId: apiRest.Id(), HttpMethod: jsii.String("POST"), Authorization: jsii.String("NONE"), }) apiIntegration := apigateway.NewApiGatewayIntegration(stack, jsii.String("my-api-integration-tf-cdk"), &apigateway.ApiGatewayIntegrationConfig{ RestApiId: apiRest.Id(), ResourceId: apiResource.Id(), HttpMethod: apiMethod.HttpMethod(), IntegrationHttpMethod: jsii.String("POST"), Type: jsii.String("AWS_PROXY"), Uri: lambdaFn.InvokeArn(), }) apigateway.NewApiGatewayDeployment(stack, jsii.String("my-api-dep-tf-cdk"), &apigateway.ApiGatewayDeploymentConfig{ StageName: jsii.String("dev"), RestApiId: apiRest.Id(), StageDescription: jsii.String(time.Now().String()), Lifecycle: &cdktf.TerraformResourceLifecycle{ CreateBeforeDestroy: jsii.Bool(true), }, DependsOn: &[]cdktf.ITerraformDependable{ apiIntegration, }, }) lambdafunction.NewLambdaPermission(stack, jsii.String("my-api-per-tf-cdk"), &lambdafunction.LambdaPermissionConfig{ Action: jsii.String("lambda:InvokeFunction"), FunctionName: lambdaFn.FunctionName(), Principal: jsii.String("apigateway.amazonaws.com"), }) return stack } func main() { app := cdktf.NewApp(nil) NewMyStack(app, "sandbox-terraform-cdk") app.Synth() }
下記コマンドでビルドしデプロイします(zip ファイルが必要です)。
# ビルド % GOARCH=amd64 GOOS=linux go build -o handler/main handler/main.go % zip -j ./handler/main.zip ./handler/main # デプロイ % cdktf deploy
下記リクエストを実行して動作確認をします。
% curl -X POST -H "Content-Type: application/json" -d '{"name":"World!"}' https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello Hello World!!
Hello World!! が出力されました。下記コマンドで削除できると思ったのですが、やり方が悪いのか削除されず、手動で削除しました。
% cdktf destroy
CDK for Terraform は慣れていないからか、実験的だからか分かりませんが、動かすのに時間がかかりました。
Pulumi
最後に Pulumi を確認します。下記コマンドでインストールし、プロジェクトをセットアップします。
# インストール % brew install pulumi # AWSアカウントセットアップ % pulumi config set aws:profile {profile-name} or % export aws_access_key_id=xxxxxxxxxxxxxxxxxxx % export aws_secret_access_key=xxxxxxxxxxxxxxxxxxx # プロジェクトセットアップ % mkdir sandbox-pulumi && cd sandbox-pulumi % pulumi new aws-go # 下記ルールでインフラと言語のテンプレート指定が可能 # pulumi new <infra>-<language>
pulumi new
すると下記コードが自動生成されます。
// sandbox-pulumi/main.go package main import ( "github.com/pulumi/pulumi-aws/sdk/v4/go/aws/s3" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { // Create an AWS resource (S3 Bucket) bucket, err := s3.NewBucket(ctx, "my-bucket", nil) if err != nil { return err } // Export the name of the bucket ctx.Export("bucketName", bucket.ID()) return nil }) }
このまま進めてもよいですが、Pulumi は GitHub の examples リポジトリにサンプルがたくさん用意されているので、ここでは example コードをそのまま利用します。下記コマンドを実行すると examples の aws-go-lambda-gateway のコードがそのまま実行できます。
% git clone https://github.com/pulumi/examples % cd examples/aws-go-lambda-gateway # Lambda Function ビルド % make build # pulumi セットアップ % pulumi stack init % pulumi config set aws:region ap-northeast-1 # デプロイ % pulumi up Previewing update (dev) View Live: https://app.pulumi.com/taisa831/go-lambda-gateway/dev/previews/e0cd482e-b3d7-41c4-9c23-8041fd6a249c Type Name Plan pulumi:pulumi:Stack go-lambda-gateway-dev + ├─ aws:iam:Role task-exec-role create + ├─ aws:apigateway:RestApi UpperCaseGateway create + ├─ aws:apigateway:Resource UpperAPI create + ├─ aws:iam:RolePolicy lambda-log-policy create + ├─ aws:apigateway:Method AnyMethod create + ├─ aws:lambda:Function basicLambda create + ├─ aws:lambda:Permission APIPermission create + ├─ aws:apigateway:Integration LambdaIntegration create + └─ aws:apigateway:Deployment APIDeployment create Outputs: + invocation URL: output<string> Resources: + 9 to create 1 unchanged Do you want to perform this update? yes Updating (dev) View Live: https://app.pulumi.com/taisa831/go-lambda-gateway/dev/updates/4 Type Name Status pulumi:pulumi:Stack go-lambda-gateway-dev + ├─ aws:iam:Role task-exec-role created + ├─ aws:apigateway:RestApi UpperCaseGateway created + ├─ aws:apigateway:Resource UpperAPI created + ├─ aws:apigateway:Method AnyMethod created + ├─ aws:iam:RolePolicy lambda-log-policy created + ├─ aws:lambda:Function basicLambda created + ├─ aws:apigateway:Integration LambdaIntegration created + ├─ aws:lambda:Permission APIPermission created + └─ aws:apigateway:Deployment APIDeployment created Outputs: + invocation URL: "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/{message}" Resources: + 9 created 1 unchanged Duration: 21s
AWS CDK と CDK for Terraform で確認した Lambda Function とは少し違いますが、下記リクエストで動作確認ができます。実行すると {message} の内容が大文字に変換されて返ってきます。
# 例)https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/{message} # MESSAGE % curl https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/Hello,World! HELLO,WORLD!
下記コマンドで削除できます。
% pulumi destroy Destroying (dev) View Live: https://app.pulumi.com/taisa831/go-lambda-gateway/dev/updates/8 Type Name Status - pulumi:pulumi:Stack go-lambda-gateway-dev deleted - ├─ aws:apigateway:Deployment APIDeployment deleted - ├─ aws:lambda:Permission APIPermission deleted - ├─ aws:apigateway:Integration LambdaIntegration deleted - ├─ aws:apigateway:Method AnyMethod deleted - ├─ aws:lambda:Function basicLambda deleted - ├─ aws:apigateway:Resource UpperAPI deleted - ├─ aws:iam:RolePolicy lambda-log-policy deleted - ├─ aws:iam:Role task-exec-role deleted - └─ aws:apigateway:RestApi UpperCaseGateway deleted Outputs: - invocation URL: "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/{message}" Resources: - 10 deleted Duration: 9s
また、pulumi stack history
コマンドで実行履歴を確認したり、管理画面が提供されるので、管理画面からアクティビティの詳細を確認したりいろいろできるようです。この辺りはまた別の機会に触ってみようと思います。
実行履歴
% pulumi stack history Version: 11 UpdateKind: destroy Status: succeeded Message: Add Crosswalk API Gateway multi-language examples (#1122) +0-10~0 0 Updated 20 hours ago took 9s exec.kind: cli git.author: Daniel Bradley git.author.email: daniel@pulumi.com git.committer: GitHub git.committer.email: noreply@github.com git.dirty: true git.head: 5b1b42e6bd2da3461e36630965f343e365ea5ff3 git.headName: refs/heads/master vcs.kind: github.com vcs.owner: pulumi vcs.repo: examples Version: 10 UpdateKind: update Status: succeeded Message: Add Crosswalk API Gateway multi-language examples (#1122) +9-0~0 1 Updated 20 hours ago took 21s exec.kind: cli git.author: Daniel Bradley git.author.email: daniel@pulumi.com git.committer: GitHub git.committer.email: noreply@github.com git.dirty: true git.head: 5b1b42e6bd2da3461e36630965f343e365ea5ff3 git.headName: refs/heads/master vcs.kind: github.com vcs.owner: pulumi vcs.repo: examples
アクティビティの詳細画面
Pulumi は他と違い Go がサポート対象なのもあり、ドキュメントやサンプルが充実していて、使っていて安定感を感じました。
まとめ
AWS CDK・CDK for Terraform・Pulumi それぞれを実際に触ってみました。個人的に AWS CDK と Pulumi は比較的すんなり使えてよかったです。特に Pulumi に関してはドキュメントの充実さやデプロイの速さなど好感触だったので今後も動向を追ってみたいと思います。
明日は @macchiitaka の「React Query のレンダリング最適化を目指した話」です!