LoginSignup
76
51

More than 3 years have passed since last update.

RDS Proxyと戯れた話【AWS】

Last updated at Posted at 2019-12-27

2020/06/30 GAしましたね!ヤッタネ!
Amazon RDS Proxy – Now Generally Available

TL; DR

re:Invent 2019で発表されたRDS Proxyを検証してみました。
前職のえんフォトというサービスの中でLambdaの処理を使っているのですが、同時接続数の問題に有効なんじゃないかってことでやってみました。
本サービスについてご興味ある方は中の人が書いてくれているので見てみてください。
【Serverless】写真のアップロード速度を爆速にした話

RDS Proxyとは

ざっくり言ってしまうと、RDSの前段に立ってコネクションをよしなにしてくれるマネージドのサービスです。
現在は、東京 / 北ヴァージニア / オハイオ / オレゴン / アイルランド でプレビュー版が利用可能です。
GAになったので下記リージョンにて対応になりました🎉

  • 北ヴァージニア
  • オレゴン
  • 北カリフォルニア
  • オレゴン
  • ムンバイ
  • ソウル
  • シンガポール
  • シドニー
  • 東京
  • カナダ
  • フランクフルト
  • アイルランド
  • ロンドン

※以下公式から引用した画像です
product-page-diagram_RDS Proxy_How-it-works@2x.a18916586f49718a16fd11579d168ab08c83d333.png
アプリケーション側に特に難しい変更は不要です。エンドポイントの指定先をDBからRDS Proxyに変えるだけで完了です。
現在はMySQLのみ対応しておりますが、PostgresSQLもすぐにいくぜ!って言っているのでしばし待ちましょう。

料金体型についてはシンプルでターゲット(※後述しますが対象DBのこと)のvCPU数に依存します。
東京リージョンは 1 vCPU毎に $0.018/hが発生します。
料金表 : https://aws.amazon.com/jp/rds/proxy/pricing/
参考早見表 (2 vCPUs x 24時間 x 30日間 稼働の場合)

Region Hourly Rough standard
ap-northeast-1(東京) $0.018 $25.92
us-east-1(北ヴァージニア) $0.015 $21.60
us-east-2(オハイオ) $0.015 $21.60
us-west-2(オレゴン) $0.015 $21.60
eu-west-1(アイルランド) $0.016 $23.04

利点としては下記が挙げられています。

  • DBへの接続をプールおよび共有することでアプリケーションの拡大に適応できる
  • DBのフェイルオーバーによるダウンタイムを縮小することができる
  • DBへの接続をIAMやSecretManagerによりよりセキュアにすることができる
  • サーバレスを利用したスケーラブルなプロキシがユーザー側の管理不要で実現できる
  • RDSとの互換性があるためコードの変更が不要で使い始めることができる

公式ドキュメント : https://aws.amazon.com/jp/rds/proxy/

Lambda × RDSのアンチパターンで苦い思いをした or している方には結構なビッグニュースだったと思います。
早速やってみます。

RDS Proxyの作成

早速リソースを作成していきます。

周辺リソースの作成

まずはSecretManagerからDB接続情報を呼び出す形ですすめてみます。

Create Secret
$ aws secretsmanager create-secret \
>   --name "proxy_credentials" \
>   --description "RDS proxy users credential" \
>   --region us-east-1 \
>   --secret-string "{\"username\":\"proxy\",\"password\":\"hogehoge\"}"
{
    "ARN": "arn:aws:secretsmanager:us-east-1:123456789012:secret:proxy_credentials-QFzY7g",
    "Name": "proxy_credentials",
    "VersionId": "6f6820b3-3b8b-4133-9752-a05db088545e"
}

これでDB情報接続情報をSecretManagerに格納できました。
続いて、上記で作成したSecretManagerからDB接続情報を読み取れるようKMS Keyを作成します。

Create kms key
$ aws kms create-key --description "RDS proxy key for poc" \
>   --policy "{\"Id\":\"proxy-kms-policy\",\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"Enable IAM User Permissions\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"},\"Action\":\"kms:*\",\"Resource\":\"*\"},{\"Sid\":\"Allow access for Key Administrators\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"arn:aws:iam::123456789012:user/hogehoge@fuga.com\"]},\"Action\":[\"kms:Create*\",\"kms:Describe*\",\"kms:Enable*\",\"kms:List*\",\"kms:Put*\",\"kms:Update*\",\"kms:Revoke*\",\"kms:Disable*\",\"kms:Get*\",\"kms:Delete*\",\"kms:TagResource\",\"kms:UntagResource\",\"kms:ScheduleKeyDeletion\",\"kms:CancelKeyDeletion\"],\"Resource\":\"*\"},{\"Sid\":\"Allow use of the key\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:role/rds-proxy-poc-role\"},\"Action\":[\"kms:Decrypt\",\"kms:DescribeKey\"],\"Resource\":\"*\"}]}"
{
    "KeyMetadata": {
        "AWSAccountId": "123456789012",
        "KeyId": "e5dd527d-7ced-4b43-ba39-33d4a3a47ccf",
        "Arn": "arn:aws:kms:us-east-1:123456789012:key/e5dd527d-7ced-4b43-ba39-33d4a3a47ccf",
        "CreationDate": 1577353505.299,
        "Enabled": true,
        "Description": "RDS proxy key for poc",
        "KeyUsage": "ENCRYPT_DECRYPT",
        "KeyState": "Enabled",
        "Origin": "AWS_KMS",
        "KeyManager": "CUSTOMER",
        "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
        "EncryptionAlgorithms": [
            "SYMMETRIC_DEFAULT"
        ]
    }
}

さらにこれらを利用できるようRDS Proxy用のIAM Roleを作成します。

Create IAM resources
$ aws iam create-role --role-name rds-proxy-poc-role \
>   --path "/service-role/" \
>   --assume-role-policy-document "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"rds.amazonaws.com\"]},\"Action\":\"sts:AssumeRole\"}]}"
{
    "Role": {
        "Path": "/service-role/",
        "RoleName": "rds-proxy-poc-role",
        "RoleId": "AROAY3FJEEGF424NCL2Z5",
        "Arn": "arn:aws:iam::123456789012:role/rds-proxy-poc-role",
        "CreateDate": "2019-12-26T09:40:24Z",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": [
                            "rds.amazonaws.com"
                        ]
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}
$ 
$ aws iam put-role-policy --role-name rds-proxy-poc-role \
>   --policy-name proxy-secret-reader-policy --policy-document '{"Version":"2012-10-17","Statement":[{"Sid":"getsecretvalue","Effect":"Allow","Action":["secretsmanager:GetSecretValue","kms:Decrypt"],"Resource":"*"}]}'

これで必要な周辺リソースは作成完了です。

RDS Proxy作成

ではRDS Proxyを作成していきます。

Create RDS Proxy
$ aws rds create-db-proxy \
>     --db-proxy-name proxy-poc-mysql \
>     --role-arn arn:aws:iam::123456789012:role/rds-proxy-poc-role \
>     --engine-family MYSQL \
>     --vpc-subnet-ids "subnet-private12345678a" "subnet-private12345678b" \
>     --vpc-security-group-ids "sg-default1234567890" "sg-fromvpc1234567890" "sg-todatabase1234567" \
>     --auth SecretArn=arn:aws:secretsmanager:us-east-1:123456789012:secret:proxy_credentials-QFzY7g
{
    "DBProxy": {
        "DBProxyName": "proxy-poc-mysql",
        "DBProxyArn": "arn:aws:rds:us-east-1:123456789012:db-proxy:prx-0b4adb889ce3affe6",
        "Status": "creating",
        "EngineFamily": "MYSQL",
        "VpcSecurityGroupIds": [
            "sg-default1234567890",
            "sg-fromvpc1234567890",
            "sg-todatabase1234567"
        ],
        "VpcSubnetIds": [
            "subnet-private12345678a",
            "subnet-private12345678b"
        ],
        "Auth": [
            {
                "AuthScheme": "SECRETS",
                "SecretArn": "arn:aws:secretsmanager:us-east-1:123456789012:secret:proxy_credentials-QFzY7g",
                "IAMAuth": "DISABLED"
            }
        ],
        "RoleArn": "arn:aws:iam::123456789012:role/rds-proxy-poc-role",
        "RequireTLS": false,
        "IdleClientTimeout": 1800,
        "DebugLogging": false,
        "CreatedDate": "2019-12-26T09:55:50.357Z"
    }
}
# IAM認証も追加する場合
$ aws rds create-db-proxy \
>     --db-proxy-name proxy-poc-mysql \
>     --role-arn arn:aws:iam::123456789012:role/rds-proxy-poc-role \
>     --engine-family MYSQL \
>     --vpc-subnet-ids "subnet-private12345678a" "subnet-private12345678b" \
>     --vpc-security-group-ids "sg-default1234567890" "sg-fromvpc1234567890" "sg-todatabase1234567" \
>     --auth SecretArn=arn:aws:secretsmanager:us-east-1:123456789012:secret:proxy_credentials-QFzY7g,IAMAuth=REQUIRED

        "Auth": [
            {
                "AuthScheme": "SECRETS",
                "SecretArn": "arn:aws:secretsmanager:us-east-1:123456789012:secret:proxy_credentials-QFzY7g",
                "IAMAuth": "REQUIRED"  #ここが変化する
            }
        ]

これで作成されましたが、ターゲットを登録してあげる必要があります。

before registry
$ aws rds describe-db-proxy-targets --db-proxy-name proxy-poc-mysql
{
    "Targets": []
}
registry target
$ aws rds register-db-proxy-targets \
>   --db-proxy-name proxy-poc-mysql \
>   --db-instance-identifiers proxy-sand-rds-mysql
{
    "DBProxyTargets": [
        {
            "Endpoint": "proxy-sand-rds-mysql.chssreu2aek6.us-east-1.rds.amazonaws.com",
            "RdsResourceId": "proxy-sand-rds-mysql",
            "Port": 3306,
            "Type": "RDS_INSTANCE"
        }
    ]
}

これで必要なリソースがそろいました。
DBなどが必要な場合はこちらのリポジトリにDBと踏み台含むものを作成するコードを置いてあるのでよければどうぞ。

RDS Proxyを利用する

今回はインスタンスクラスdb.m5.largeでRDSを用意しました。

インスタンスクラス vCPU ECU メモリ(GiB) VPCのみ EBS 最適化 最大帯域幅 (Mbps) ネットワークパフォーマンス
db.m5.large 2 10 8 はい はい 3,500 最大10Gbps

通常のRDSエンドポイント経由でエラーが発生することを確認します。(念の為)

mysqlslap error
$ mysqlslap \
>   --no-defaults \
>   --concurrency=1000 \
>   --iterations=10 \
>   --auto-generate-sql \
>   --auto-generate-sql-add-autoincrement \
>   --auto-generate-sql-load-type=write \
>   --auto-generate-sql-write-number=1000 \
>   --number-of-queries=100000 \
>   --host=proxy-sand-rds-mysql.chssreu2aek6.us-east-1.rds.amazonaws.com \
>   --port=3306 \
>   --user=proxy \
>   -phogehoge
mysqlslap: Error when connecting to server: 1040 Too many connections
mysqlslap: Error when connecting to server: 1040 Too many connections
mysqlslap: Error when connecting to server: 1040 Too many connections
mysqlslap: Error when connecting to server: 1040 Too many connections
mysqlslap: Error when connecting to server: 1040 Too many connections
mysqlslap: Error when connecting to server: 1040 Too many connections
mysqlslap: Error when connecting to server: 1040 Too many connections
mysqlslap: Error when connecting to server: 1040 Too many connections
mysqlslap: Error when connecting to server: 1040 Too many connections
=================================================以下省略=================================================

めちゃくちゃエラーでましたw
想定通りのエラーでよかったです。

RDS側でも見てみます。

on RDS
mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 20    |
+-------------------+-------+
1 row in set (0.29 sec)

mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 567   |
+-------------------+-------+
1 row in set (0.26 sec)

mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 526   |
+-------------------+-------+
1 row in set (0.20 sec)

mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 20    |
+-------------------+-------+
1 row in set (0.28 sec)

mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 466   |
+-------------------+-------+
1 row in set (0.25 sec)

# おそらくここからToo many connectionが発生したため20から変化せず
mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 20    |
+-------------------+-------+
1 row in set (0.18 sec)

mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 20    |
+-------------------+-------+
1 row in set (0.18 sec)

メトリクスでも一気に落ちているのが確認できました。
1_1.png

ではいよいよProxy経由でアクセスしてみます。
まずはエンドポイントの確認をします。

confirming endpoint
$ aws rds describe-db-proxies --query '*[*].{DBProxy:DBProxyName,Endpoint:Endpoint}'
[
    [
        {
            "DBProxy": "proxy-poc-mysql",
            "Endpoint": "proxy-poc-mysql.proxy-chssreu2aek6.us-east-1.rds.amazonaws.com"
        }
    ]
]

冒頭のドキュメントどおり特に投げるコマンドは変化させずにエンドポイントのみ変更して投げてみます。

to RDS Proxy
$ mysqlslap \
>   --no-defaults \
>   --concurrency=100 \
>   --iterations=10 \
>   --auto-generate-sql \
>   --auto-generate-sql-add-autoincrement \
>   --auto-generate-sql-load-type=write \
>   --auto-generate-sql-write-number=1000 \
>   --number-of-queries=10000 \
>   --host=proxy-poc-mysql.proxy-chssreu2aek6.us-east-1.rds.amazonaws.com \
>   --port=3306 \
>   --user=proxy \
>   -phogehoge
Benchmark
    Average number of seconds to run all queries: 1.273 seconds
    Minimum number of seconds to run all queries: 1.190 seconds
    Maximum number of seconds to run all queries: 1.400 seconds
    Number of clients running queries: 100
    Average number of queries per client: 100

しっかりとベンチマークの結果が返ってきました。
RDS側でも確認してみます。

on RDS from Proxy
mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 312   |
+-------------------+-------+
1 row in set (0.25 sec)

mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 556   |
+-------------------+-------+
1 row in set (0.22 sec)

mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 556   |
+-------------------+-------+
1 row in set (0.19 sec)

mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 556   |
+-------------------+-------+
1 row in set (0.24 sec)

mysql> show processlist;
+------+----------+----------------------+-----------+---------+------+-----------+------------------------------------------------------------------------------------------------------+
| Id   | User     | Host                 | db        | Command | Time | State     | Info                                                                                                 |
+------+----------+----------------------+-----------+---------+------+-----------+------------------------------------------------------------------------------------------------------+
|    2 | proxy    | 192.168.32.121:18233 | NULL      | Sleep   |    0 |           | NULL                                                                                                 |
|    3 | proxy    | 192.168.31.112:56663 | NULL      | Sleep   |    1 |           | NULL                                                                                                 |
|    4 | proxy    | 192.168.20.141:61049 | NULL      | Sleep   |    1 |           | NULL                                                                                                 |
|    5 | proxy    | 192.168.28.76:58809  | NULL      | Sleep   |    1 |           | NULL                                                                                                 |
|    6 | proxy    | 192.168.44.126:51551 | NULL      | Sleep   |    1 |           | NULL                                                                                                 |
|    7 | proxy    | 192.168.29.99:55543  | NULL      | Sleep   |    1 |           | NULL                                                                                                 |
|    8 | proxy    | 192.168.37.103:16061 | NULL      | Sleep   |    1 |           | NULL                                                                                                 |
|    9 | proxy    | 192.168.30.221:12095 | NULL      | Sleep   |    1 |           | NULL                                                                                                 |
=============================================================================================省略=============================================================================================
| 8569 | proxy    | 192.168.24.69:40032  | mysqlslap | Query   |    0 | query end | INSERT INTO t1 VALUES (NULL,364531492,'qMa5SuKo4M5OM7ldvisSc6WK9rsG9E8sSixocHdgfa5uiiNTGFxkDJ4EAwWC2 |
| 8570 | proxy    | 192.168.24.69:40038  | mysqlslap | Query   |    0 | query end | INSERT INTO t1 VALUES (NULL,95275444,'bNIrBDBl81tjzdvuOpQRCXgX37xGtzLKEXBIcE3k7xK7aFtqxC99jqYnpTviK8 |
| 8571 | proxy    | 192.168.24.69:40034  | mysqlslap | Sleep   |    0 |           | NULL                                                                                                 |
| 8572 | proxy    | 192.168.24.69:40036  | mysqlslap | Query   |    0 | update    | INSERT INTO t1 VALUES (NULL,1665728802,'8qB5MAR1caTvNFXB00HKShGa1uToGCMP2ZMHjpABFg6fnTioTz8pZGNQAkEJ |
| 8573 | proxy    | 192.168.24.69:40040  | mysqlslap | Query   |    0 | update    | INSERT INTO t1 VALUES (NULL,95275444,'bNIrBDBl81tjzdvuOpQRCXgX37xGtzLKEXBIcE3k7xK7aFtqxC99jqYnpTviK8 |
| 8574 | proxy    | 192.168.24.69:40042  | mysqlslap | Sleep   |    0 |           | NULL                                                                                                 |
| 8575 | proxy    | 192.168.24.69:40046  | mysqlslap | Sleep   |    0 |           | NULL                                                                                                 |
| 8576 | proxy    | 192.168.24.69:40044  | mysqlslap | Query   |    0 | update    | INSERT INTO t1 VALUES (NULL,364531492,'qMa5SuKo4M5OM7ldvisSc6WK9rsG9E8sSixocHdgfa5uiiNTGFxkDJ4EAwWC2 |
+------+----------+----------------------+-----------+---------+------+-----------+------------------------------------------------------------------------------------------------------+
572 rows in set (0.61 sec)

1_2.png

直接RDS側の上限数が増える訳ではなく、あくまで前段にいるRDS Proxyがプールしながらよしなにしてくれると言った感じでした。
何度か実施したところ、DBConnection数が落ちずに張り付いてmysqlslapも返って来なくなってしまいました。
1_3.png

RDS Proxy側のホストがCPUバーストしきってしまった(そもそもしているかは不明)のかなと予想してますが、どうなんでしょう?

ちなみにCloudwatchでもメトリクスが確認できました。
1_4.png

※ 2020/07/05追記
GA前との差分とどう変わったか差分を出そうとやってみましたが下記エラーでできませんでした。
ちなみにbastionから普通にRDSのエンドポイント指定では前回と変わらず、一定まで行った後帰ってこなくなりました。
普通にProxy経由でDBにアクセスはできているので原因がよくわかりません。完全になぞですね。

error
$ mysqlslap \
>   --no-defaults \
>   --concurrency=1000 \
>   --iterations=10 \
>   --auto-generate-sql \
>   --auto-generate-sql-add-autoincrement \
>   --auto-generate-sql-load-type=write \
>   --auto-generate-sql-write-number=1000 \
>   --number-of-queries=10000 \
>   --host=poc-proxy.proxy-abcdef123456.us-east-1.rds.amazonaws.com \
>   --port=3306 \
>   --user=proxy \
>   -phogehoge
mysqlslap: Cannot run query INSERT INTO t1 VALUES (NULL,100669,'qnMdipW5KkXdTjGCh2PNzLoeR0527frpQDQ8uw67Ydk1K06uuNHtkxYBxT5w8plb2BbpzhwYBgPNYX9RmICWGkZD6fAESvhMzH3yqzMtXoH4BQNylbK1CmEIPGYlC6') ERROR : No database selected

尻切れトンボで申し訳ないのですが、一旦できなかったということだけ書き残しておきます。
※そのうちリベンジするかも。

まとめ

プレビュー版なこともあり、検証をしてみましたが、予期しない挙動が発生したため引き続き検証を進めていきたいと思います。
仮にこれが仕様ですと異常にパフォーマンスが落ちてしまい、これが本番で起きると結構致命的だな…といった感じです。
今回は軽い検証だったのでmysqlslapでリクエストを投げたので、実際はLambdaからリクエストを投げるので違った動きをするかもしれないので実際の開発環境に置いてみてやってみたいと思います。
プレビュー版なので、色々と検証してみることで難しさや楽しさがわかると思いますが、これらをAWSの中の人たちに伝えてあげるともっと喜ぶと思います。
みなさんも新しい機能は積極的に試してみることをおすすめします!ではよきAWSライフを!

2020/07/01 追記
GAになったのでこの記事を書いた時から変更がありました。
ざっくりとした制限は下記です。

  • アカウントに対し最大20個まで作成可能(上限緩和申請も可能)
  • RDS ProxyごとのSSMシークレットパラメーターは200個まで持てる
  • RDS Proxyは最大200ユーザーが接続可能
  • Auroraの場合Writerに接続されるので、Readerエンドポイントは直指定が必要
  • Aurora ServerlessおよびAuroraGlobalDBは非対応
  • RDS Proxy経由でのアクセスは同一VPC内のDBに限る
  • RDS Proxyはパブリックアクセスできない
  • 占有テナントインスタンスがあるVPCだと利用できない
  • カスタムDNSを利用している場合は利用できない
  • DB 1:n RDS Proxyで接続することが可能。逆は不可

共通した制限はこんなところです。
DBエンジンごとの制限は公式ドキュメントからどうぞ!

76
51
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
76
51