Lambda (Go) + EventBridge (Cron) + Systems Manager Parameter Store + CDK

Go で作った Lambda 関数(パラメータストアから値を読む)を EventBridge を使って Cron 実行する環境を CDK で書く。

環境

  • aws-cdk: 2.54.0
  • @aws-cdk/aws-lambda-go-alpha: 2.54.0-alpha.0
  • aws-cdk-lib: 2.54.0

作るものの概要

毎月一日に MySQL の DB に接続してデータの生成・削除を行うバッチを作る。

MySQL への接続情報を AWS Systems Manager Parameter Store に保存し、コード実行時に取得して環境変数に動的に設定する。

完成品

ディレクトリ構成は以下の通りとする。

ルート
|
|-app
| |-go.mod
| |-go.sum
| |-main.go
|
|-cdk(cdk init --language typescript で作られるファイル群)
  |-bin
  |-lib
  |-...

Lambda のソースコード

app/main.go

package main

import (
	"database/sql"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ssm"
	_ "github.com/go-sql-driver/mysql"
	"os"
	"strings"
	"time"

	"gitlab.com/k1350/sololog_gql/sololog_back/app/repository/jwk"
)

func rotateKey() error {
	err := fetchParameterStore()
	if err != nil {
		return err
	}
	dsn := os.Getenv("DSN")

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	defer db.Close()
	if err = db.Ping(); err != nil {
		return err
	}

	jwkr := jwk.NewJWKRepository(db)

	now := time.Now().UTC()
	// 新規鍵作成
	err = jwkr.Create(now)
	if err != nil {
		return err
	}

	// 古い鍵削除
	return jwkr.DeleteOldKey(now)
}

func fetchParameterStore() error {
	sess := session.Must(session.NewSession())
	svc := ssm.New(
		sess,
		aws.NewConfig().WithRegion("ap-northeast-1"),
	)

	parameters, err := svc.GetParametersByPath(&ssm.GetParametersByPathInput{
		Path:           aws.String("/sololog"),
		WithDecryption: aws.Bool(true),
	})
	if err != nil {
		return err
	}

	for _, v := range parameters.Parameters {
		names := strings.Split(*v.Name, "/")
		name := names[len(names)-1]
		err = os.Setenv(name, *v.Value)
		if err != nil {
			return err
		}
	}
	return nil
}

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

パラメータストアに予め “/sololog/DSN” という名前で MySQL への接続情報を保存していることを前提としている。

fetchParameterStore 内で、GetParametersByPath を使って “/sololog” というパスの下のパラメータをすべて取得し、環境変数に投入している。

今回のケースでは取得したパラメータを環境変数に入れずにそのまま使っても良かったのだが、ローカル開発となるべく同じコードにしたかったのでこのような実装にした。

CDK

cdk/lib/cdk-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { GoFunction } from "@aws-cdk/aws-lambda-go-alpha";
import { Rule, Schedule } from "aws-cdk-lib/aws-events";
import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
import { Role, ServicePrincipal, ManagedPolicy } from "aws-cdk-lib/aws-iam";

export class CdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const executionLambdaRole = new Role(this, "SolologLambdaExecutionRole", {
      roleName: "sololog-lambda-execution-role",
      assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
      managedPolicies: [
        ManagedPolicy.fromAwsManagedPolicyName(
          "service-role/AWSLambdaBasicExecutionRole"
        ),
        ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMReadOnlyAccess"),
      ],
    });

    const lambda = new GoFunction(this, "handler", {
      entry: "../app",
      role: executionLambdaRole,
    });

    new Rule(this, "scheduleRule", {
      schedule: Schedule.cron({ minute: "0", hour: "16", day: "1" }),
      targets: [new LambdaFunction(lambda, { retryAttempts: 3 })],
    });
  }
}

Lambda からパラメータストアを読み取る必要があるので読み取り権限を付与したロールを作成する。

また EventBridge で毎月一日に Cron 実行するルールを作成し、Lambda にくっつける。

実行

cdk deploy でデプロイ後、Lambda のテスト実行でコードの実行を確認できる。

またテスト用に EventBridge の設定を schedule: Schedule.cron({ minute: "0" }) などとしてデプロイしておくと cron 実行も確認できる。

参考文献

以上