📝

Lambda(Go)でDataAPIを利用してAuroraに接続する(Gorm)

2022/11/05に公開

はじめに

Go(gin)と Lambda(serverless framework)で CICD するで ServerlessFramework を使って Go を Lambda にデプロイしました

今回は Lambda 上から Aurora にアクセスするまでをハンズオンでお行います

普段やっている通りに Aurora にアクセスしてもできるのですが、プールの関係で Lambda からの接続はRDS ProxyまたはDataAPIを中継してアクセスしたほうが良いです

詳しくはこちらにまとめました

ネットで情報を集めている中で Go と DataAPI で接続する方法についてほとんど情報がなかったためハンズオン形式でまとめていきたいと思います

開発環境

VSCode
Ubuntu 20.04 (WSL2)
Docker 20.10.12
docker-compose version v2.2.3
git version 2.25.1

準備

こちらのリポジトリで行っていきます

前回のハンズオンからの続きになります

リポジトリをローカルの環境にクローンします

$ git clone https://github.com/jinwatanabe/go_lambda_cicd.git

$ cd go_lambda_cicd

Aurora の準備

ここからは AWS コンソール(リージョンは東京)から必要なリソースを作成していきます

ベース環境

まずは Aurora を設置するためのVPCサブネットを作成します

コンソールでVPCを検索します

左メニューから「お使いの VPC」→「VPC を作成」をクリック

項目
名前 handson-vpc
IPv4 CIDR 10.0.0.0/16

「VPC を作成」をクリック

作成ができました

次に左メニューから「サブネット」をクリックして「サブネットを作成」をクリックします

今回は Aurora ように 2 つのプライベートサブネットを作ります。Aurora は 2 つのサブネットをまたがるように配置するという仕様になっています

プライベートサブネット A

項目
VPC ID handson-vpc
サブネット名 handson-subnetA
アベイラビリティゾーン ap-northeast-1a
IPv4 CIDR ブロック 10.0.0.0/24

「新しいサブネットを追加」をクリック

プライベートサブネット C

項目
VPC ID handson-vpc
サブネット名 handson-subnetC
アベイラビリティゾーン ap-northeast-1c
IPv4 CIDR ブロック 10.0.1.0/24

「サブネットを作成」をクリック

サブネットが 2 つ作成できました

Aurora

Aurora を作成していきます
ここではserverless v1というタイプの RDS を作成する必要があります
この後利用する DataAPI が対応しているのが v1 のみだからです

コンソールから作成ができなかったので、CloudFormation を利用して作成します
ディレクトリに以下のファイルを作成します

rds.yml
Parameters:
  ProjectName:
    Description: Type of this ProjectName.
    Type: String
  VPC:
    Type: "AWS::EC2::VPC::Id"
  SubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
  DBName:
    Description: Type of this DatabaseName example sample_db.
    Type: String
  DBPassword:
    Description: Type of this DatabasePassword.
    Type: String


Resources:
  DBCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      DBClusterIdentifier: !Sub "${ProjectName}-db-cluster"
      DBSubnetGroupName: !Ref "DBSubnetGroup"
      DatabaseName: !Sub "${DBName}"
      Engine: aurora-mysql
      EngineMode: "serverless"
      EngineVersion: "5.7.mysql_aurora.2.10.2"
      DBClusterParameterGroupName: !Ref DBParameterGroup
      MasterUserPassword: !Sub "${DBPassword}"
      MasterUsername: admin
      StorageEncrypted: true
      ScalingConfiguration:
        MinCapacity: 1
        AutoPause: false
        MaxCapacity: 2
      VpcSecurityGroupIds:
        - !Ref AuroraSecurityGroup
      DBSubnetGroupName: !Ref DBSubnetGroup
    DeletionPolicy: Delete

  # ------------------------------------------------------------#
  #  parameter group
  # ------------------------------------------------------------#
  DBParameterGroup:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Family: aurora-mysql5.7
      Description: Database Parameter Group
      Parameters:
        character_set_database: utf8mb4
        character_set_client: utf8mb4
        character_set_connection: utf8mb4
        character_set_results: utf8mb4
        character_set_server: utf8mb4
        time_zone: Asia/Tokyo

  # ------------------------------------------------------------#
  #  subnet group
  # ------------------------------------------------------------#
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupName: !Sub "${ProjectName}-db-subnet-group"
      DBSubnetGroupDescription: for db
      SubnetIds: !Split [",", !Join [",", !Ref SubnetIds]]

  # ------------------------------------------------------------#
  #  security group
  # ------------------------------------------------------------#
  AuroraSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: SecurityGroup for Aurora
      VpcId: !Sub "${VPC}"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          CidrIp: 10.0.1.0/24
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectName}-db-sg"
    # DependsOn: VPC

  # ------------------------------------------------------------#
  #  secret manager
  # ------------------------------------------------------------#
  SecretManager:
    Type: AWS::SecretsManager::Secret
    Properties:
      Description: "Secrets Manager for RDS"
      SecretString:
        !Sub '{"username": "admin","password": "${DBPassword}"}'
      Name: !Sub "${ProjectName}-Secrets"
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-Secrets

  SecretManagerAttachment:
    Type: AWS::SecretsManager::SecretTargetAttachment
    Properties:
      SecretId: !Ref SecretManager
      TargetId: !Ref DBCluster
      TargetType: AWS::RDS::DBCluster

Outputs:
  Endpoint:
    Value: !GetAtt DBCluster.Endpoint.Address
  ResourceArn:
    Value: !Sub "arn:aws:rds:ap-northeast-1:${AWS::AccountId}:cluster:${ProjectName}-db-cluster"
  SecretArn:
    Value: !Ref SecretManagerAttachment

コンソールから「CloudFormation」と検索します

右上の「スタックの作成」から「新しいリソースを使用」をクリック

テンプレートリソースを「テンプレートファイルのアップロード」選択
「ファイルの選択」をクリックして作成した「rds.yml」を選択して「次へ」をクリック

項目
スタックの名前 handsonrds
DBName handsonrds
DBPassword password
ProjectName handsonrds
SubnetIds handson-subnetA, handson-subnetC
VPC handson-vpc

「次へ」→「次へ」→「送信」をクリックして作成します

作成に成功しました

DataAPI の設定

Aurora に DataAPI の設定を行います。

コンソールから「rds」と検索します

左メニューから「データベース」をクリックして、先ほど作成した「handsonrds」をクリックします
「変更」をクリックします

接続から「Data API」にチェックを入れて「続行」→「クラスターの変更」をクリック

Secret Mangaer

コンソールから「secret manager」を検索します

「新しいシークレットを保存する」をクリック

項目
ユーザー名 admin
パスワード password
データベース handson-db-cluster

「次」をクリック

「シークレットの名前」に「handson-secret」と設定して「次」→「次」→「保存」

作成できました

「handson-secret」をクリックして「シークレットの ARN」をメモしておきます

データベースの初期設定

コンソールから「rds」を検索して左メニューから「クエリエディタ」をクリック

項目
データベースインスタンスまたはクラスター handsonrds-db-cluster
データベースユーザー名 Secrets Manager ARN と接続する
Secrets manager ARN 先ほどメモした ARN

「データベースに接続します」をクリック

create database handson;

と入力して「実行」をクリック

ここから lambda から RDS に接続してユーザーテーブルが作成できるようにしていきます

Go に DataAPI 接続の設定をする

今回は Data API を介して Lambda から RDS に接続します
以下のライブラリを利用します

https://github.com/krotscheck/go-rds-driver

main.go を以下に編集します

/src/handler/main.go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	ginadapter "github.com/awslabs/aws-lambda-go-api-proxy/gin"
	"github.com/gin-gonic/gin"
	"github.com/krotscheck/go-rds-driver"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var ginLambda *ginadapter.GinLambda

type User struct {
	Name      string
	CreatedAt time.Time
	UpdatedAt time.Time
}

func init() {

	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {

		conf := &rds.Config{
			ResourceArn: "DBのリソースARN",
			SecretArn:   "DBを紐づけたシークレット",
			Database:    "handson",
			AWSRegion:   "ap-northeast-1",
			SplitMulti:  false,
			ParseTime:   true,
		}
		dsn := conf.ToDSN()

		DB, err := gorm.Open(mysql.New(mysql.Config{
			DriverName: rds.DRIVERNAME,
			DSN:        dsn,
		}), &gorm.Config{})

		if err != nil {
			fmt.Println(err)
		}

		DB.AutoMigrate(&User{})

		C.JSOn(200, "ok")

	})

	ginLambda = ginadapter.New(r)
}

func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	// If no name is provided in the HTTP request body, throw an error
	return ginLambda.ProxyWithContext(ctx, req)
}

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

ここでは GORM を利用して DataAPI に接続するようにします
次に「SecretARN」の部分を先ほどメモしたものにします
「ResourceARN」はデータベースの詳細から確認して変更します

ServerlessFramework から RDS と Secrets へのアクセス権限が必要なので追加します

serverless.yml
service: aws-lambda-go-api-proxy-gin

provider:
  name: aws
  runtime: go1.x
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "logs:*"
        - "secretsmanager:*"
        - "rds-data:*"
      Resource: "*"

package:
  exclude:
    - ./**
  include:
    - ./bin/**

custom:
  defaultStage: dev

functions:
  api:
    handler: bin/main
    timeout: 900
    events:
      - http:
          path: ping
          method: get

接続確認

Serverless Framework を使ってデプロイを行います
ここの設定はこちらで行っています

$ docker-compose -f docker-compose.production.yml run --rm app make build
$ docker-compose -f docker-compose.production.yml run --env AWS_ACCESS_KEY_ID=[作成したアクセスキー] --env AWS_SECRET_ACCESS_KEY=[作成したシークレットアクセスキー] --rm app make deploy

/pingにアクセスしてうまくいけば以下の画面が表示されます

先ほどのクエリエディタで以下のコマンドを実行します

use handson;
show tables;

以下のようにuserテーブルが作成されえていれば成功です

お片付け

以下のリソースを削除します

  • ServerlessFramework 関係
  • ServerlessFramework 用の IAM
  • secret manager
  • RDS (cloudFromation のスタック削除)
  • サブネット 2 つ
  • vpc

おわりに

https://qiita.com/Sicut_study/items/9b9136734dc047b0f237

ここでも紹介しましたが Lambda(Go)から Gorm を利用して RDS に接続するのにかなり時間がかかりました

ネットに情報があるようでなかったのでハンズオンにしてまとめてみました
記事でも紹介しましたが、サブネットに Lambda を入れた場合は RDS Proxy が必要になるので構成には注意が必要です

今回作成したリポジトリは以下にあります

https://github.com/jinwatanabe/go_lambda_dataapi_handson

参考

GitHubで編集を提案

Discussion