- 投稿日:2020-07-20T12:10:12+09:00
祝GA‼︎【Go】Lambda + RDS 接続にRDS Proxyを使ってみた
はじめに
2019年末に行われたre:Invent 2019で発表されたRDS Proxyが先日正式リリースになりました!API Gateway + Lambda + RDSを使ってWebアプリケーションを作っていて、そこでRDS Proxyを使ってみたので、備忘録です。
RDS Proxyって何?
公式サイトによると、次のように説明されています。
Amazon RDS プロキシは、Amazon Relational Database Service (RDS) 向けの高可用性フルマネージド型データベースプロキシで、アプリケーションのスケーラビリティやデータベース障害に対する回復力と安全性を高めます。
詳細は公式サイトをご確認ください!
RDS Proxyで何をするのか
RDS Proxyでデータベースへのコネクションプールを確立、管理することで、アプリケーションからのデータベース接続数を少なく抑えることができるというので、今回使ってみました!
Lambda関数は、呼び出すごとに新しいコネクションを作成する必要があります。しかし、LambdaからRDSへの同時接続数には上限があり、これまではコネクション数が上限に達しないようにする必要がありました。これを解決してくれるのがこのRDS Proxyです。RDS Proxyを利用することで、既存のコネクションを再利用することができ、コネクション数を抑えることができます。
つまり、Lambda + RDSの構成が避けられていた原因の1つの同時接続数問題がRDS Proxyで解決できるのです!
料金
料金は有効になっているデータベースインスタンスの vCPU あたり0.018USD/時間(東京リージョン)となります。ただし、最低料金として、2つのvCPU分の料金がかかるので、1つのvCPUの場合でも0.018 × 2 = 0.036USD/時間かかります。
参考:https://aws.amazon.com/jp/rds/proxy/pricing/結論
とても記事が長くなってしまったので、先に結論をまとめます。
- RDS Proxyを介してRDSに接続できることが確認できた
- VPC内にLambdaを設置したときのコールドスタートが改善されていることがわかった
→ IAM認証接続ではパブリックサブネットに置いていたRDSをプライベートサブネットに配置できるようになった- VPCLambda + RDSで構築していたものはエンドポイントをRDS Proxyに向けるだけでOK(コードの修正が不要)
それでは、本題に入っていきます!構成図
このような構成で作成しました!本記事では、Lambda、RDS Proxy、RDS、踏み台のEC2にフォーカスしています。
手順
下記の流れで進めていきます。
- VPC、サブネットの作成
- セキュリティグループの作成
- RDSの構築
- 踏み台EC2の構築
- テーブル作成
- DBユーザの作成
- RDS Proxyの構築
- Lambda関数の作成
やってみる
1. VPC、サブネットの作成
事前準備として、VPCを作成し、作成したVPCの中にプライベートサブネット、パブリックサブネットを作成します。特別な設定は不要なので作成方法は省略します。
2. セキュリティグループの作成
事前準備として、各リソースのセキュリティグループの作成を行います。
Lambda
セキュリティグループ name : sg-lambda
インバウンド
タイプ ポート ソース ー ー ー 特にどこからも許可していなくてもAPI Gatewayからは叩くことができます!
EC2
セキュリティグループ name : sg-ec2-bastion
インバウンド
タイプ ポート ソース SSH 22 許可したいIPアドレス RDS Proxy
セキュリティグループ name : sg-rdsproxy
インバウンド
タイプ ポート ソース MySQL/Aurora 3306 sg-lambda RDS
セキュリティグループ name : sg-rds
インバウンド
タイプ ポート ソース MySQL/Aurora 3306 sg-ec2-bastion MySQL/Aurora 3306 sg-rdsproxy 3. RDSの構築
プライベートサブネットにRDSを立てます。
今回使用したMySQLのバージョン : MySQL 5.7.22
セキュリティグループは2で作成したsg-rdsを選択してください。その他、特に特別な設定は不要なので省略します。
4. 踏み台EC2の構築
RDSに接続して、ユーザやテーブルを作成するための踏み台EC2をたてます。
今回使用したOS : Amazon Linux 2
セキュリティグループは2で作成したsg-ec2-bastionを選択してください。こちらも、特に特別な設定は不要なので省略します。
ただ、IPv4パブリックIPを使ってsshで接続するため、自動割り当てパブリックIPは有効に設定します。(これをせずに、プライベートIPで接続を試みましたが、できませんでした。)※上記の方法(自動割り当てパブリックIP)では、EC2を再起動するごとにパブリックIPアドレスが変更になるのでお気をつけてください。
5. テーブル作成
RDS内にテーブルを作成します。
テーブル内のデータはcsvファイルから取り込むようにしたかったので、今回はcsvファイルを準備しましたが、ただデータをRDSに保存するだけです。csvファイルのアップロード
ローカルから踏み台のEC2にcsvファイルを移動させました。
$ scp -i [キーペア名].pem [ファイル名].csv ec2-user@[パブリックIP]:/home/ec2-user/(EC2内の保存したいディレクトリを指定)EC2にssh接続
$ ssh -i [キーペア名].pem ec2-user@[IPv4パブリックIP]MySQLのインストール
$ sudo yum update $ sudo yum install mysqlRDSへ接続
$ mysql --local-infile=1 -h [RDSエンドポイント] -u [マスタユーザ名] -pテーブル内のデータはcsvファイルから読み込むために、
--local-infile=1
のオプションをつけました。テーブルの作成
まず、テーブルの枠を作成します。
> CREATE TABLE [テーブル名] ([カラムの指定]); # 例 > CREATE TABLE m1_champion (id INT(2) AUTO_INCREMENT NOT NULL PRIMARY KEY, name VARCHAR(30) NOT NULL, champion YEAR NOT NULL, formed YEAR NOT NULL, note VARCHAR(30));次に、下記のようなcsvファイルをインポートします。
> LOAD DATA LOCAL INFILE "[ファイルパス]/[ファイル名].csv" INTO TABLE [テーブル名] FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n';これでテーブルが完成しました。
id name champion formed note 1 ミルクボーイ 2019 2007 コーンフレーク 2 霜降り明星 2018 2013 NULL 3 とろサーモン 2017 2002 NULL 6. DBユーザの作成
上記の手順通り進めば、現在DBにログインしているので、このままLambdaから接続したときに使うユーザを作成します。
> CREATE USER '[ユーザ名]'@'%' IDENTIFIED BY '[パスワード]'; > GRANT SELECT, INSERT, UPDATE, DELETE ON [対象のDB].[対象のテーブル] TO '[ユーザー名]'@'%';上記はSELECT, INSERT, UPDATE, DELETEの権限を許可しています。また、ホスト名には
%
=ワイルドカードを使用し、どこからのアクセスも受け入れるように設定しています。ちなみに、全てのDBとテーブルが対象の場合は*.*
とします。7. RDS Proxyの構築
いよいよRDS Proxyの構築に入ります。
Secrets Manager シークレットの作成
先ほど作成したDBのユーザ名とパスワードを入力し、作成します。
下記URLのAWSの公式ブログに沿って作成してください。
参考:AWS LambdaでAmazon RDS Proxyを使用するIAMロールの作成
ユースケースは
RDS - Add Role to Database
を選択します。そして、必要なポリシーをアタッチします。
Secrets Managerへのアクセス権限のポリシー
{ "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Action": [ "secretsmanager:GetResourcePolicy", "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", "secretsmanager:ListSecretVersionIds" ], "Resource": "[シークレットARN]" } ] }拡張ログを取得したい場合はCloud Watch Logsへのアクセス権限も必要です。ただ、以下のログに関しては、Cloud Watch Logsへのアクセス権限がなくてもロググループに出力されます。
- RDS Proxyの起動終了
- DBへの接続開始終了
- 警告
RDS Proxyの作成
今回はRDS Proxyのコンソールから作成します。
ちなみに、Lambda関数のコンソール上からも作成でき、IAM認証で接続する場合はLambdaと紐付ける手間を省くことができます。ただ、今回のようにDBのユーザ名とパスワードを用いて接続する場合は紐付けは必要ないようなので、RDS Proxyのコンソールから作成しました。
プロキシ識別子(名前)を入力します。
エンジンの互換性ではMySQLとPostgreSQLが選べるようになっていました。ここでプロキシが接続できるDBのタイプを設定します。今回はMySQLなので、MySQLを選択しました。先ほど作成したRDSを選択します。
先ほど作成したSecrets ManagerシークレットとIAMロールを選択します。
サブネットはRDSと同じプライベートサブネットを選択します。
セキュリティグループは2で作成したsg-rdsproxyを選択してください。Cloud Watch Logsでデバッグログを取得したい場合は拡張されたログ記録を有効にするにチェックを入れてください。(Cloud Watch Logsへのアクセス権限も必要です。)
8. Lambda関数の作成
IAMロール
LambdaはVPC内にあるのでENI生成用のポリシーを作成してアタッチします。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Action": [ "ec2:DescribeNetworkInterfaces", "ec2:DeleteNetworkInterface", "ec2:CreateNetworkInterface" ], "Resource": "*" } ] }ログを取得したい場合はCloud Watch Logsへのアクセス権限も必要です。
VPC
Lambda関数の編集画面で設定できます。
カスタムVPCを選択し、RDSとRDS Proxyと同様のVPCとパブリックサブネットを選択します。
セキュリティグループは2で作成したsg-lambdaを選択してください。ソースコード
package main import ( "database/sql" "encoding/json" "fmt" "github.com/aws/aws-lambda-go/lambda" _ "github.com/go-sql-driver/mysql" "os" ) type Response struct { ID int `json:"id"` Name string `json:"name"` Champion string `json:"champion"` Formed string `json:"formed"` Note sql.NullString `json:"note"` } // os.Getenv()でLambdaの環境変数を取得 var dbUser = os.Getenv("dbUser") // DBに作成したユーザ名 var dbPass = os.Getenv("dbPass") // パスワード var dbEndpoint = os.Getenv("dbEndpoint") // RDS Proxyのプロキシエンドポイント var dbName = os.Getenv("dbName") // テーブルを作ったDB名 func RDSConnect() (*sql.DB, error) { connectStr := fmt.Sprintf( "%s:%s@tcp(%s:%s)/%s?charset=%s", dbUser, dbPass, dbEndpoint, "3306", dbName, "utf8", ) db, err := sql.Open("mysql", connectStr) if err != nil { panic(err.Error()) } return db, nil } func RDSProcessing(db *sql.DB) (interface{}, error) { var id int var name string var champion string var formed string var note sql.NullString responses := []Response{} responseMap := Response{} getData, err := db.Query("SELECT * FROM m1_champion") defer getData.Close() if err != nil { return nil, err } for getData.Next() { if err := getData.Scan(&id, &name, &champion, &formed, ¬e); err != nil { return nil, err } fmt.Println(id, name, champion, formed, note) responseMap.ID = id responseMap.Name = name responseMap.Champion = champion responseMap.Formed = formed responseMap.Note = note responses = append(responses, responseMap) } params, _ := json.Marshal(responses) fmt.Println(string(params)) defer db.Close() return string(params), nil } func run() (interface{}, error) { fmt.Println("RDS接続 start!") db, err := RDSConnect() if err != nil { panic(err.Error()) } fmt.Println("RDS接続 end!") fmt.Println("RDS処理 start!") response, err := RDSProcessing(db) if err != nil { panic(err.Error()) } fmt.Println("RDS処理 end!") return response, nil } /************************** メイン **************************/ func main() { lambda.Start(run) }実行結果
上記の手順でRDS Proxyを用いての接続は完了です。(あれ意外と簡単)
Lambdaでの実行結果がこちらです。RDSから値が取得できました!
びっくりするのは応答時間!!!コールドスタートで接続に10秒程度かかるのでご法度とされていたVPCLambdaですが、こんなにはやくなっているんです。
参考:[発表] Lambda 関数が VPC 環境で改善されます
これは使わない手はない!
おわりに
無事、LambdaからRDS Proxyを介してRDSに接続が可能になりました!これで同時接続数問題も気にしなくていい!!また、VPC内にLambdaを設置したときのコールドスタートが改善されたので、Lambdaを非VPCに設置しなくてもいけるし、RDSがプライベートサブネットに置ける!!素晴らしい!
また、RDSのセキュリティグループにLambdaのセキュリティグループを付加し、コード中のエンドポイントの向きをRDS ProxyからRDSに変更するだけで、このコードのままRDSに接続できることが確認できました。つまり、Lambda + RDSで構築していたものは簡単にRDS Proxyを経由することができるんです!
わたしはこれまでIAM認証でのLambda + RDS接続しか経験がなく、コードの実装に手間取ってしまいましたが、上手く接続できないときの問題の切り分け方などとても勉強になりました!GAになったので、これから活用できる場所が増えるのではないかなと思います!
- 投稿日:2020-07-20T03:13:25+09:00
DockerとGoで簡単にapiサーバーを立ててみる
はじめる前に
こんにちはRIN1208です
この記事はDockerとGoを少し触れている程度の学生が書いていますので分からない点、間違っている点等がございましたらコメントして頂けると幸いです。
今回の書いたDockerfileなどはこちらに置いてあります。この記事の対象
- Dockerについてなんとなく知っている人
- Goを知っている人
- とりあえず両方触ってみたい人
この記事で触れない事
- Dockerがどのように動いているか
- Dockerの環境構築について
- Docker Hubの詳細について
- Dockerとは何か
- Goのコードの詳細について
- マルチステージビルド
開発環境
- macOS Catalina
- Go(ver 1.14.4)こちらはなくても問題ありません
- Docker(後半でDocker-composeも使用します)
- VSCode(お好みのエディターを使用して下さい) 上記の環境で説明していきます
さわってみる
今回やる事
- コンテナの起動などの基本動作
- Dockerfileを書く
- Goで簡単なWebサーバーを立てる
今回触れない事
- Docker Networkを使用したコンテナ間通信
Nginxをdockerで起動してみる
下記のコマンドを実行してみましょう
docker run --name hoge -d -p 8080:80 nginx起動後に
http://localhost:8080/
に接続するとWelcome to nginx!と表示されます。今入力したコマンドの説明
docker run: コンテナを起動するコマンドです
--name: 起動するコンテナに名前をつけられます。今回はhogeという名前です
-d: このオプションを使用するとバックグラウンドで起動することができます
-p: ポートのオプションですホストのポート:コンテナのポート
で指定できます。今回はホストが8080、コンテナが80で起動しています。起動したコンテナの確認
下記のコマンドを実行してみましょう
docker psすると以下のように表示されると思います
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 63940a31eae8 nginx "/docker-entrypoint.…" 3 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp hoge表示項目の説明
CONTAINER ID: コンテナのIDです。コンテナを指定する際に使用します
IMAGE: 使用したDockerimageです。今回はnginxを使用したのでnginxになっています
COMMAND: コンテナが起動する際に与らたコマンドです
CREATED: コンテナを作成してからの時間です
STATUS: 起動、停止してからの経過時間です
PORTS: コンテナ及びホストの使用ポート
NAMES: コンテナの名前です。こちらの名前を使用してコンテナを指定することもできます起動したコンテナの停止
下記のコマンドを実行してみましょう
docker stop hoge実行するとコンテナが停止しました。
docker ps
で確認してみましょうCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESとだけ表示されます。こちらの項目に先ほど確認したコンテナが存在しなければコンテナの停止ができています。
またdocker ps
コマンドは起動しているコンテナを確認するコマンドなので他にも起動しているコンテナがある場合は表示されますDockerfileを書いてコンテナを作成する
次はDockerfileを書いてGoのapiサーバーを立ち上げてみましょう。
今回はGoのwebフレームワークであるGinを使用します。今回触れない事
- マルチステージビルド
- Go Modules
Dockerfileとmain.goを作成する
適当な同じディレクトリに内にDockerfileとmain.goを作成しましょう。
main.gopackage main import ( "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/", hoge) r.Run() // デフォルトが8080ポートなので今回は変更しません } func hoge(c *gin.Context) { c.JSON(200, "hogeeeeeeee") }dockerfile
FROM golang:1.14 RUN go get -u github.com/gin-gonic/gin WORKDIR /app COPY . /app ENV CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 EXPOSE 8080 CMD ["go", "run", "main.go"]設定項目の説明
FROM : 実行する環境を指定します。DockerHubにあるimageをしますので使用する際はDockerHubにあるかどうか確認してください。バージョン指定がない場合は最新バージョン(latest)が使用されます。
RUN : コマンドを実行します
WORKDIR : 作業ディレクトリを設定します
COPY : コンテナにディレクトリ、ファイルるをコピーします。
ENV : コンテナに永続的な環境変数を定義します。
コピーする対象 コンテナ内のコピー先
で指定できますEXPOSE : コンテナの使用ポートを指定します
CMD : 実行するコンテナにコマンドと引数を提供します
Dockerfileからimageを作成し起動する。
以下のコマンドでbuildしimageを作成します。Dockerfileのあるディレクトリ内で実行しましょう。
今回はmy-imageという名前のimageを作成し起動します。docker build -t my-image .buildが完了しましたら下記のコマンドを実行しREPOSITORYの項目にmy-imageという名前があるのを確認しましょう。
docker images下記のようにREPOSITORYの項目にmy-imageとあれば問題ありません。
REPOSITORY TAG IMAGE ID CREATED SIZE my-image latest b498e18e4087 6 hours ago 956MB確認することができましたら以下のコマンドでコンテナを起動させましょう。
今回は先ほど作成したimageのmy-imageを使用しmy-apiという名前のコンテナを作成しています。docker run -it --name my-api my-image今回は-dオプションをつけていないのでバックグラウンドで動作させていませんが-dオプションを指定すればバックグラウンドで動かすことができます。
docker run -d -it --name my-api my-image上記のコマンドでapiサーバーが起動できたのを確認できます。
今回はhttp://localhost:8080/
にて立ち上がっています。docker-composeを使ってみる
次は先ほど立ち上げたコンテナをdocker-composeで立ち上げます。
Mysql等との接続は記事が長くなるのに加え、少しややこしいので今回は触りません。docker-compose.yml
version: "3" services: api: # コンテナの名前 build: . # dockerfileの場所を指定できます。 ports: - "8080:8080" tty: trueコンテナのビルド
下記のコマンドでビルドします。
ymlファイルを書き換えた際はbuildしないと反映されません。ちょくちょく忘れてやらかしますdocker-compose buildコンテナの起動
下記のコマンドでコンテナを立ち上げます。 今回は -d オプションを使用しバックグラウンドにて立ち上げています
docker-compose up -d起動できましたら
http://localhost:8080/
にて確認してみましょう。起動したコンテナの確認
立ち上がっているコンテナは下記のコマンドにて確認することができます。
docker-compose psすると以下のように表示されると思います。
Name Command State Ports ------------------------------------------------------------------- docker-test_api_1 go run main.go Up 0.0.0.0:8080->8080/tcpName : コンテナの名前
Command : コンテナが起動する際に与らたコマンドです
State : コンテナのステータスです
Ports : コンテナ及びホストの使用ポートです起動したコンテナの停止
下記のコマンドでコンテナを停止させることができます。
docker-compose down終わりに
ここまで読んでくださりありがとうございます。
この記事は友人のDockerの勉強のためになればと思い書きました。また初めてDocker触る方の手助けになれば幸いです。
間違っている点などがございましたらコメントなどで指摘していただけると助かります。