LoginSignup
2
1

PulumiでAPI Gateway + Lambda(Go)をデプロイしてみた

Posted at

はじめに

以前から気になっていたIaCツールであるPulumiでAPI Gateway + Lambda(Go)のサーバレスなAPIを作成してみたので記事にしたいと思います。

Pulumiとは

TerraformやCloudFormationなどと同じくIaCツールとなっていて、YAMLだけでなくGoやPython、TypeScriptなどで記述できることやAWS、Google Cloud、Azureなど様々なクラウドサービスに対応してることも特徴です。
さらにPulumiにはPulumi AIというChat GPTのように自然言語でチャットするだけでIaCのコードを生成してくれる機能があります。

そもそもIaCとは

IaC(Infrastructure as Code)はインフラ(サーバー、ネットワーク、ストレージ)をコードで定義して管理する手法のことです。手動での管理と違ってコードで記述するので、手作業でのミスがなくなることやGitなどを用いることで管理の効率化が可能になっています。

開発環境

M1 MacBookで開発しています。

手順

事前にAWSのアカウントを用意して、
aws configureコマンドで設定をしてください。

もしくは、以下の環境変数をセットしておいてください。

export AWS_ACCESS_KEY_ID={ACCESS_KEY}
export AWS_SECRET_ACCESS_KEY={SECRET_ACCESS_KEY}

まずpulumi-cliのインストールをします。

brew install pulumi/tap/pulumi

次に作業用ディレクトリを作成して、以下のコマンドを実行してください。

pulumi new aws-go

環境の作成が完了したら、適当にLambda関数(Go)のプログラムを用意します。

main.go
package main

import (
	"context"

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

func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	return events.APIGatewayProxyResponse{
		Body:       "hello",
		StatusCode: 200,
	}, nil
}

func main() {
	lambda.Start(handler)
}

続いてgoファイルをビルドしたら準備は完了です。

GOOS=linux GOARCH=amd64 go build -ldflags=$(BUILD_LDFLAGS) -o ./bootstrap ./...
zip ../function.zip ./bootstrap

ここで、紹介にもあったPulumi AIを活用していきます。

チャットで作成したい構成を指示しましょう。

ちなみに私は「API Gateway + LambdaでサーバレスなAPIを作成してください。」と指示しました。

生成が完了したらコードをコピー&ペーストして、以下のコマンドを実行してください。

pulumi up

私は1回目で実行した時にエラーが発生してデプロイできませんでしたが、
チャット上でエラーのコードを送付することで解決策の提示してくれて無事デプロイすることができました。

参考程度に手直し済みのコード

package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/apigateway"
	"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/iam"
	"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/lambda"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {

		// Create an IAM role that AWS Lambda will use
		lambdaRole, err := iam.NewRole(ctx, "lambdaRole", &iam.RoleArgs{
			AssumeRolePolicy: pulumi.String(`{
				"Version": "2012-10-17",
				"Statement": [{
					"Action": "sts:AssumeRole",
					"Effect": "Allow",
					"Principal": {
						"Service": "lambda.amazonaws.com"
					}
				}]
			}`),
		})
		if err != nil {
			return err
		}

		// Attach the AWSLambdaBasicExecutionRole policy to the IAM role
		_, err = iam.NewRolePolicyAttachment(ctx, "lambdaPolicyAttachment", &iam.RolePolicyAttachmentArgs{
			Role:      lambdaRole.Name,
			PolicyArn: pulumi.String("arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"),
		})
		if err != nil {
			return err
		}

		// Define the AWS Lambda resource
		lambdaFunc, err := lambda.NewFunction(ctx, "helloLambda", &lambda.FunctionArgs{
			Code:    pulumi.NewFileArchive("function.zip"),
			Role:    lambdaRole.Arn,
			Handler: pulumi.String("bootstrap"),
			Runtime: pulumi.String("provided.al2"), 
		})
		if err != nil {
			return err
		}

		// Create an API Gateway Rest API
		api, err := apigateway.NewRestApi(ctx, "api", &apigateway.RestApiArgs{
			Description: pulumi.String("Example API"),
		})
		if err != nil {
			return err
		}

		_, err = lambda.NewPermission(ctx, "apiGatewayInvoke", &lambda.PermissionArgs{
			Action:    pulumi.String("lambda:InvokeFunction"),
			Function:  lambdaFunc.Name,
			Principal: pulumi.String("apigateway.amazonaws.com"),
		})
		if err != nil {
			return err
		}

		// Create an API Gateway Resource
		resource, err := apigateway.NewResource(ctx, "resource", &apigateway.ResourceArgs{
			RestApi:  api.ID(),
			ParentId: api.RootResourceId,
			PathPart: pulumi.String("hello"),
		})
		if err != nil {
			return err
		}

		// Create an API Gateway Method for the 'GET' HTTP verb
		_, err = apigateway.NewMethod(ctx, "getMethod", &apigateway.MethodArgs{
			RestApi:       api.ID(),
			ResourceId:    resource.ID(),
			HttpMethod:    pulumi.String("GET"),
			Authorization: pulumi.String("NONE"),
		})
		if err != nil {
			return err
		}

		// Create an API Gateway Integration to connect the 'GET' method to the Lambda function
		integration, err := apigateway.NewIntegration(ctx, "getIntegration", &apigateway.IntegrationArgs{
			RestApi:               api.ID(),
			ResourceId:            resource.ID(),
			HttpMethod:            pulumi.String("GET"),
			IntegrationHttpMethod: pulumi.String("POST"),      // Lambda functions are invoked with POST
			Type:                  pulumi.String("AWS_PROXY"), // Use the Lambda proxy integration
			Uri:                   lambdaFunc.InvokeArn,
		})
		if err != nil {
			return err
		}

		// Create a deployment to enable the API Gateway
		deployment, err := apigateway.NewDeployment(ctx, "deployment", &apigateway.DeploymentArgs{
			RestApi: api.ID(),
		}, pulumi.DependsOn([]pulumi.Resource{
			integration,
		}))
		if err != nil {
			return err
		}

		// Create an API Gateway Stage which acts as an environment
		_, err = apigateway.NewStage(ctx, "stage", &apigateway.StageArgs{
			Deployment: deployment.ID(),
			RestApi:    api.ID(),
			StageName:  pulumi.String("prod"), // Name the stage as 'prod'
		})
		if err != nil {
			return err
		}

		// Output the invocation URL of the stage
		ctx.Export("invokeUrl", pulumi.Sprintf("https://%s.execute-api.%s.amazonaws.com/prod/hello", api.ID(), "ap-northeast-1"))

		return nil
	})
}

OutputされたURLにアクセスして、helloが返ってきたら成功です!

リソースの削除を忘れずに。

pulumi destroy

以上です。

全体的なコードはGithubに載せているので、必要であればご参照ください。

さいごに

TerraformやCloudFormationを使ってYAMLで構築することは今までもありましたが、プログラミング言語で記述したことがなかったので良い経験になりました。

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