RDS Proxy 経由で RDS に接続する
はじめに
Lambda を利用しているときに RDS を使いたいとなった場合、RDS Proxy を今なら選択するかなと思いますが、最初その構成を設定するのに一苦労したので記事にまとめようと思います。全体の構成としては以下の通りです。
API Gateway をパブリックインターフェースに置いて、統合してある Lambda をキックします。Lambda は、RDS Proxy、RDS と同一 VPC 内に設置してあります。RDS に接続するための秘匿情報については、Secrets Manager で管理するようにしますが、VPC 外にあるので、VPC エンドポイントを通して取得しに行きます。
ネットワーク周りの設定
VPC、サブネット、ルートテーブル
今回は以下のような構成で作成します。
細かい入力項目はこちら
作成するリソース
- VPCのみ
-
VPC、サブネットなど
名前タグの自動生成
デフォルトのまま
IPv4 CIDRブロック
10.0.0.0/16
IPv6 CIDR ブロック
- IPv6 CIDR ブロックなし
-
Amazon 提供の IPv6 CIDR ブロック
テナンシー
デフォルト
アベイラビリティーゾーン (AZ)
2個
カスタマイズしない
パブリックサブネットの数
0個
※ API Gateway は VPC 内のリソースにアクセスできるのでパブリックサブネットは不要です。
プライベートサブネットの数
2個
カスタマイズしない
NAT ゲートウェイ ($)
なし
VPC エンドポイント
なし
DNS オプション
- DNS ホスト名を有効化
-
DNS 解決を有効化
こんな感じで VPC ワークフローが作成できたら完了です。
VPC の作成する際にウィザードが新しくなっており、めちゃくちゃ作成しやすくなっていてびっくりしました。
セキュリティグループ
Lambda
API Gateway と統合することでアクセス可能になるので、ルールは中身は空で作成します。
RDS Proxy
Lambda の Security Group からの 3306 ポートのインバウンドだけ受け付けます。
タイプ: カスタムTCP
ポート範囲: 3306
ソース: sg-xxxxxxxxx(Lambda のSG)
RDS
RDS Proxy の Security Group からの 3306 ポートのインバウンドだけ受け付けます。
タイプ: カスタムTCP
ポート範囲: 3306
ソース: sg-yyyyyyyyy(RDS Proxy のSG)
Secrets Manager
Lambda の Security Group からの HTTPS インバウンドだけを受け付けます。
タイプ: HTTPS
ポート範囲: 443
ソース: sg-xxxxxxxxx(Lambda のSG)
Secrets Manager 用の VPC エンドポイント
サービス名: com.amazonaws.ap-northeast-1.secretsmanager
エンドポイントタイプ: interface
VPC: 上記で作成した、VPC
セキュリティグループ: Secrets Manager 用のセキュリティグループ
サブネット: 上記で作成した、2つのプライベートサブネット
プライベートDNS: 有効
Lambda の作成
Lambda を作成しましょう。この記事ではランタイムに Python3.8 を指定していますが、お好きな言語を選択してください。
先程、VPC 周りを作成したので、それを設定します。
また、Lambda が SecretsManager にアクセスできる必要があるので、SecretsManagerReadWrite のポリシーを Lambda に付与します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
"ec2:AssignPrivateIpAddresses",
"ec2:UnassignPrivateIpAddresses"
],
"Resource": "*"
}
]
}
ソースコードは後で編集するので、一旦デフォルトのまま次に進みます。
API Gateway の作成
色々と設定が楽ちんな HTTP API で構築していきます。
今回は Lambda は1つしか作成しない予定なので、統合も合わせて設定しちゃいます。
先ほど作成した Lambda を統合に設定します。
Lambda は1つしかないので、ルート設定は以下で問題ないでしょう。
メソッド: GET
リソースパス: /
統合ターゲット: (先程作成した Lambda の名前)
ステージはデフォルトかつ自動デプロイを有効化します。
これで作成します。
疎通確認テスト
ここで、一旦疎通確認しておきます。
API Gateway によって、作成されたエンドポイントにアクセスしてみましょう。
Hello from Lambda!
が返って来れば、順調に作成できています。
$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
"Hello from Lambda!"
RDS の作成
サブネットグループの作成
データベースの作成の前に、サブネットグループの作成をしときます。
先程作成した、VPC、2つのプライベートサブネット で構成されるサブネットグループを先に作成してください。
クラスター & データベースの作成
今回はライブラリに、pymysql
を使いたいので DBは Aurora MySQL を選びます。
インスタンス設定のところですが、RDS Proxy はサーバーレス未対応なので、インスタンスクラスを選択します。
Aurora サーバーレスクラスターでは RDS Proxy を使用できません。
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/rds-proxy.html#:~:text=Aurora サーバーレスクラスターでは RDS Proxy を使用できません。
また、接続設定のところで、事前に作成した VPC や セキュリティグループを選択します。
データベース認証は、パスワード認証
にします。
追加設定のところで、データベースも作成します。今回はDB名を test_db
として作成しています。
Secrets Manager の作成
ご丁寧に、サンプルコードが置いてあったので、Lambda に貼り付けて実行してみましょう。
コメントは邪魔なので消すのと、エラーハンドリングは簡略化します。
import boto3
import base64
from botocore.exceptions import ClientError
def lambda_handler(event, context):
secret_name = "YourSecretName" # 書き換えてください
region_name = "ap-northeast-1"
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name
)
try:
get_secret_value_response = client.get_secret_value(
SecretId=secret_name
)
except Exception as e:
raise e
else:
if 'SecretString' in get_secret_value_response:
return get_secret_value_response['SecretString']
else:
return base64.b64decode(get_secret_value_response['SecretBinary'])
実行結果
結果が、timed out
になってしまう場合は、IAM周りの権限設定が不足しているか、ネットワーク(特にセキュリティグループ)の設定を見直してください。
$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com | jp
{
"username": "admin",
"password": "xxxxxxxxxxxxxxx",
"engine": "mysql",
"host": "database-1.cluster-xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com",
"port": 3306,
"dbname": "test_db",
"dbClusterIdentifier": "database-1"
}
RDS Proxy の作成
続いて、RDS Proxy を作成していきます。
ターゲットグループ と、接続 の設定に気をつけてください。
ターゲットグループ
- データベースは先程作成したデータベースを指定します
- リーダーエンドポイントは今回はテストなので不要です
接続
- 作成したシークレットを指定します
- IAM ロールを作成します
- IAM 認証は無効です
- サブネットは最初に作成した2つのプライベートサブネットを指定します
- RDS Proxy 用のセキュリティグループを指定します
Lambda を編集
pymysql をレイヤーに追加
pymysql
は、組み込みのライブラリではないため、レイヤーとして追加する必要があります。
他の SQL ライブラリを使ってもらっても問題ありません。
AWS CloudShell を開きます。
$ sudo amazon-linux-extras install python3.8 -y
$ sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1
$ python3 -V
Python 3.8.5
$ curl -O https://bootstrap.pypa.io/get-pip.py
$ python3 get-pip.py
$ pip install pymysql -t python
$ zip -r pymysql.zip python
$ aws s3 cp pymysql.zip s3://${input-bucket-name}/ # bucket名は要編集
あとは、zip をアップロードするなりして、Lambda 側に追加するだけです。
コード
接続する場合は、以下のようになります。
try:
get_secret_value_response = client.get_secret_value(
SecretId=secret_name
)
except Exception as e:
raise e
else:
secret_info = json.loads(get_secret_value_response["SecretString"])
# DB操作
connect = pymysql.connect(
# Proxy のエンドポイントを指定するのを忘れないでください
host="proxy.proxy-xxxxx.ap-northeast-1.rds.amazonaws.com",
user=secret_info["username"],
passwd=secret_info["password"],
db=secret_info["dbname"],
connect_timeout=5,
cursorclass=pymysql.cursors.DictCursor,
)
あとは、SQL 文を実行するだけです。
疎通確認
RDS Proxy のモニタリングを見てみると、クエリーが来ているのが確認できます。
あと、DB のメトリクスもちゃんと反応しているのが確認できたら、疎通完了です。
お疲れ様でした。作成経験はあるのですが、またネットワーク周りで詰まってしまいました。
ネットワーク難しい...
あと、zenn の マークダウン に ラジオボタンが欲しいので、catnose さんよろしくお願いします。
Discussion