Lambda(Go)でDataAPIを利用してAuroraに接続する(Gorm)
はじめに
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 を利用して作成します
ディレクトリに以下のファイルを作成します
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 に接続します
以下のライブラリを利用します
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 へのアクセス権限が必要なので追加します
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
おわりに
ここでも紹介しましたが Lambda(Go)から Gorm を利用して RDS に接続するのにかなり時間がかかりました
ネットに情報があるようでなかったのでハンズオンにしてまとめてみました
記事でも紹介しましたが、サブネットに Lambda を入れた場合は RDS Proxy が必要になるので構成には注意が必要です
今回作成したリポジトリは以下にあります
Discussion