テクノロジー

既存システムへのAmazon RDS Proxy導入記

はじめに

昨年、運用を担当しているWeb集約基盤にて一部サイトのアクセスが急増し、データベースのコネクション上限に達したことで新たなコネクションが確率できずにサイトダウンとなる障害が発生しました。
再発防止策としてAmazon RDS Proxy(以下、RDS Proxy)を導入したので、選定理由や導入のあれこれを記事にしてみようと思います!

Web集約基盤について

今回障害が発生したのは社内で共通化されているWebサイトの集約基盤で、本番・STG合わせて100以上のサイトが稼働しています。

コストやサイト規模を考慮してデータベースを複数サイトで共用しているため、今回の障害のようにコネクション数が枯渇するとデータベースを共用している全てのサービスに影響が出てしまいます。
サイト数が多く影響範囲も広いことから、再発防止は重要な課題でした。

選定理由

RDS Proxyとは

(以下 AWS公式サイトより引用:https://aws.amazon.com/jp/rds/proxy/)

Amazon RDS Proxy は、Amazon リレーショナルデータベースサービス (RDS) 向けの高可用性フルマネージ型データベースプロキシで、アプリケーションのスケーラビリティやデータベース障害に対する回復力と安全性を高めます。

簡単に言うと、アプリケーションとリレーショナルデータベースの間に配置し、データベースへの接続を効率的に管理することでアプリケーションのスケーラビリティを向上させるサービスです。

導入メリット

  • コネクションプーリングによるリソース効率の改善
  • フェイルオーバー時間の短縮による可用性向上
  • AWS Secrets Manager や IAMを利用した認証情報の管理

RDS Proxy導入のメリットとしては上記の通りいくつかありますが、今回の障害の再発防止策として特にコネクションプーリングに着目しました。
アプリケーションがDBとの接続をプールしたり共有することで、データベース効率とアプリケーションの可用性が向上するため、データベースコネクション数の枯渇の回避が期待できます。

上記のメリットに加え、導入も比較的容易であることからRDS Proxyを導入する方針に決まりました。

その他の検討案

選定理由としてRDS Proxy導入のメリットを挙げましたが、もちろん他の案を検討していなかった訳ではありません。
Auroraサーバレスや分散型データベースの導入なども検討にあがったものの、以下のような理由で却下となりました。

Auroraサーバレス
障害発生時、データベースのCPU/メモリは逼迫していなかったため、同様の事象が起きた際のスケーリングが期待できなかった。

分散型データベース
社内で導入実績が少なく、データ移行も必要となることから対応負荷を考え見送りとなった。

それでは、本題のRDS Proxyの導入について進めていきます!

RDS Proxyの導入

RDS Proxyの導入までは、大まかに以下のような順で進めました。

  • ①Secrets作成
  • ②RDS Proxy構築
  • ③エンドポイント切り替え

①Secrets作成

RDS Proxyでは、Secrets Managerを利用してRDS Proxy↔データベース間の認証を行うため、まずはSecretsを作成します。

集約基盤ではサイトごとにデータベースのユーザー・パスワードが異なるため、各サイト毎にSecretsを作成する必要がありました。

各サイトの設定ファイルでユーザー・パスワード情報を管理しているので、ユーザー・パスワード情報を設定ファイルから抜き出し、CLIコマンドを利用して一括でSecretsを作成しました。
※サイト毎にSecretsが存在するという点が、実は今回一番苦戦する原因となったところです…詳しくはRDS Proxy構築の部分でお話します。

②RDS Proxy構築

いよいよRDS Proxyの構築です!
我々の部署ではCDKやCloudFormationといったIaCを導入していて、インフラもコードで管理することが主流となっています。
集約基盤ではCloudFormationを採用しているため、RDS Proxyの構築はCloudFormationで行いました。

RDS Proxyの構築に必要なリソースは以下となりますが、SecretsはCLIで作成済みのためその他のリソースをCloudFormationで作成・変更していきます。

  • Secrets Manager
  • セキュリティグループ
  • IAMロール・ポリシー
  • RDS Proxy

セキュリティグループ

セキュリティグループは既存のテンプレートで管理しているため、既存テンプレートを修正しました。
詳細は割愛しますが、ECS→RDS Proxy間の通信と、RDS Proxy→データベース間の通信を許可すればOKです。
エンドポイント切り替えまでは ECS→データベースの通信経路となるため、そこの設定は残したままにしておき、切り替え後に安定稼働を確認できたら削除します。

RDS Proxy

RDS Proxyのテンプレートでは、RDS ProxyにSecrets Managerの参照権限を付与するIAMロール・ポリシーも一緒に作成します。
今回作成したRDS Proxyのテンプレートがこちらです。


AWSTemplateFormatVersion: 2010-09-09 
Description: Create RDS Proxy.

#-----------------------------------------------------------------------------------#
Parameters:
#-----------------------------------------------------------------------------------#
  EnvName:
    Type: String
    Default: dev
    AllowedValues:
      - dev
      - stg
      - prod

#-----------------------------------------------------------------------------------#
Metadata:
#-----------------------------------------------------------------------------------#
  AWS::CloudFormation::Interface:
    ParameterGroups: 
      - 
        Label: 
          default: "Common Configuration"
        Parameters: 
          - EnvName

#-----------------------------------------------------------------------------------#
Resources:
#-----------------------------------------------------------------------------------#
  DBProxyRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - rds.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: sample-dbproxy-secret-policy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource:
                  - !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:sample-dbproxy-*"

  DBProxy:
    Type: AWS::RDS::DBProxy
    Properties:
      DBProxyName: sample-dbproxy
      EngineFamily: MYSQL
      IdleClientTimeout: 3600 
      RoleArn: !GetAtt DBProxyRole.Arn
      VpcSubnetIds: 
        - Fn::ImportValue: !Sub ${EnvName}-private0
        - Fn::ImportValue: !Sub ${EnvName}-private1
      VpcSecurityGroupIds: 
        - Fn::ImportValue: !Sub ${EnvName}-DBProxy-securitygroup
      Fn::Transform:
        Name: AWS::Include
        Parameters:
          Location: 
            !Sub "s3://cfn-for-dbproxy-${AWS::Region}/${EnvName}-auth.yml"

  DBProxyTargetGroup:
    Type: AWS::RDS::DBProxyTargetGroup
    Properties:
      TargetGroupName: default
      DBClusterIdentifiers:
        - !Sub ${EnvName}-db-cluster
      DBProxyName: !Ref DBProxy
      ConnectionPoolConfigurationInfo: 
        MaxConnectionsPercent: 90

MaxConnectionsPercent
RDS Proxy がターゲットデータベースに対して確立できる接続の数を制限する項目で、データベースで使用可能な最大接続数(max_connections)に対する割合 (%) で指定します。
デフォルトでは MaxConnectionsPercent の半分(50%)まではアイドル状態の接続が確立されることが期待される挙動となっており、最大接続数はMaxConnectionsPercentを超えることは無いといったしようとなっています。

例えば、データベースターゲットの max_connections が 1000、RDS ProxyのMaxConnectionsPercent が 95 に設定されている場合、RDS Proxyのデータベースターゲットへのアイドル状態の接続は約半数の475前後を保持し、同時接続の最大は 950までとなります。

Web集約基盤ではデータベース接続をRDS Proxy経由で行わないケースが一部存在するため、データベースのmax_connectionsを使い切らないよう90%に設定しています。

SecretsArnの設定について

先ほども少し書きましたが、今回一番苦戦した部分がSecretsArnの設定です。
各環境・各サイト毎にSecretsが存在するため、RDS ProxyのテンプレートにすべてのSecretsを記載してしまうと膨大な量になります。
コードの可読性が下がること、サイト追加の度にテンプレート修正が必要となり運用負荷が高くなることなどからテンプレートに直書きは避けたい部分でした。

そこで、AWS::Include関数を利用して以下のようにSecretsArnの設定部分だけ外部ファイルから取ってくるような仕組みにしました。
各サイトのSecretsArnを記載した${EnvName}-auth.ymlを作成してS3 Bucketに配置し、RDS ProxyのテンプレートでS3 Bucketに配置したファイルを読み込ませます。

  DBProxy:
    Type: AWS::RDS::DBProxy
    Properties:
      DBProxyName: !Sub sample-dbproxy  
~ 中略 ~
      Fn::Transform:
        Name: AWS::Include
        Parameters:
          Location: 
            !Sub "s3://cfn-for-dbproxy-${AWS::Region}/${EnvName}-auth.yml" #S3に配置した${EnvName}-auth.ymlからAuthの値を参照する
Auth:
  - {AuthScheme: SECRETS, IAMAuth: DISABLED, SecretArn: 'arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXXX:secret:sample-dbproxy-service01-XXXXX'} #service01
  - {AuthScheme: SECRETS, IAMAuth: DISABLED, SecretArn: 'arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXXX:secret:sample-dbproxy-service02-XXXXX'} #service02
  - {AuthScheme: SECRETS, IAMAuth: DISABLED, SecretArn: 'arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXXX:secret:sample-dbproxy-service03-XXXXX'} #service03

こうすることで、サイト追加・削除時にRDS Proxyのテンプレートを修正する必要が無くなりました。(※スタックの更新は必要)

RDS Proxyに多数のSecretsArnを設定するケースは少ないかもしれませんが、一部の設定項目をテンプレート外で管理したいケースは多々あるかと思います。
そういった場合にとても便利な関数なので是非取り入れてみてください!

AWS::Include関数に関するAWS公式記事はこちら

③エンドポイント切り替え

RDS Proxyの構築は完了しましたが、まだECS→RDS Proxy→データベース といった通信経路となっていません。
どのデータベースを参照するかは各サービスの設定ファイルに記載しているため、設定ファイルのデータベースエンドポイントをRDS Proxyのエンドポイントに書き変える必要があります。
今回は稼働中のすべてのサービスの設定ファイルに記載してあるエンドポイントの書き替えが必要なため、Ansibleを利用しました。

サービスのディレクトリ配下から設定ファイルを探し出し、エンドポイントを書き変えるplaybookを作成し実行することで、一度にエンドポイントが書き変えられダウンタイム無しでRDS Proxy経由の通信に切り替えることができました。

以上でRDS Proxyの導入は完了です!

さいごに

今回は導入のメリットに触れましたが、RDS Proxyには多少のレイテンシーが発生するなどデメリットも存在するため、メリット・デメリットやコストを比較した上で検討するのをお勧めします!
導入後の効果についてもまたどこかでまとめたいなと思います。

※本記事は2024年02月時点の内容です。

テクノロジーの記事一覧
タグ一覧
TOPへ戻る