LoginSignup
2
2

More than 1 year has passed since last update.

CloudFormationでLambda(Python) + RDS Proxy + Auroraを構築する

Posted at

本記事の内容

本記事の手順で以下のような構成のクラウド環境を作成します。
aws_lambda_rdsproxy_aurora.png
※AWSの操作画面はサービスリリースと共に更新されていくため、本記事をご覧いただくタイミングによっては、現在の画面と異なる場合があります。ご了承ください。本記事内の画面キャプチャーは2022年9月時点,

RDS Proxyの詳細は他の記事を参照ください。
こちらが私的に面白かったです。

ここから実際のテンプレートを説明していきます。

実施環境

  • Windows 10
  • Chrome
  • AWS

説明の流れ

  1. S3にLambdaデプロイ用のzipファイルとデータ投入用のCSVファイルの配置
  2. CloudFormationテンプレートの準備
  3. CloudFormationスタックの作成
  4. Lambadaによるテストデータの投入

1. S3にLambdaデプロイ用のzipファイルとデータ投入用のCSVファイルの配置

CloudFormationでのLambdaのデプロイは、事前にデプロイ用ファイルをS3に配置しておき、それをデプロイする方式をとる。
データ投入用のCSVファイルも事前に配置が必要です。

デプロイ用ファイルとCSVファイルの配置の仕方は、以下の記事を参照してご確認ください。

今回はDB接続のエンドポイントとしてRDS Proxyを利用しているため、
前回のプログラムから接続部分の変更を行う必要があります。

変更点は以下の箇所です。環境変数とコネクト時のホストの指定が変更になります。

(変更前)

  DB_WRITER_ENDPOINT_ADDRESS = os.environ["DB_WRITER_ENDPOINT_ADDRESS"]
  conn = pymysql.connect(host=DB_WRITER_ENDPOINT_ADDRESS, user=DB_USER, passwd=DB_PASSWORD, db=DB_NAME, connect_timeout=5)

(変更後)

  DB_PROXY_ENDPOINT_ADDRESS = os.environ["DB_PROXY_ENDPOINT_ADDRESS"]
  conn = pymysql.connect(host=DB_PROXY_ENDPOINT_ADDRESS, user=DB_USER, passwd=DB_PASSWORD, db=DB_NAME, connect_timeout=5)

プログラム全体は以下の通り。

index.py
import boto3
import json
import pymysql.cursors
import os

def lambda_handler(event, context):
  
  #Lambdaの環境変数は、CloudFormationのデプロイ時に自動で設定する
  DB_NAME = os.environ["DB_NAME"]
  DB_USER = os.environ["DB_USER"]
  DB_PASSWORD = os.environ["DB_PASSWORD"]
  DB_PROXY_ENDPOINT_ADDRESS = os.environ["DB_PROXY_ENDPOINT_ADDRESS"]

  #pymysqlを利用してAuroraに接続するため、PyMySQLライブラリーを合わせてデプロイする必要がある
  conn = pymysql.connect(host=DB_PROXY_ENDPOINT_ADDRESS, user=DB_USER, passwd=DB_PASSWORD, db=DB_NAME, connect_timeout=5)
  with conn.cursor() as cur:

    #CreateTableコマンドは、mysqlの通常のコマンドと同じ。
    cur.execute("create table service (system_id varchar(255),name varchar(255),url varchar(2048),PRIMARY KEY (system_id))")

    #S3に配置したS3ファイルをインポートするためには"LOAD DATA FROM S3 FILE"コマンドを利用する。
    # 's3://bucket-name/testData.csv'でインポートするファイルのARNを指定しています。
    # FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' IGNORE 1 LINES の指定で、カンマ区切り・改行が\n・先頭行は無視を指定しています。
    # (system_id,name)でインポートするカラムを指定しています。
    #cur.execute("LOAD DATA FROM S3 FILE 's3://bucket-name/testData.csv' INTO TABLE service FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' IGNORE 1 LINES (system_id,name);")

    #こちらはSQLの従来通りのinsert intoコマンド。一つずつデータを入れる場合はこちらのSQLで良い。
    #cur.execute('insert into service (system_id, name) values("1", "test")')
    conn.commit()
    
    #インポートした結果はこちらのselect文で確認する。
    cur.execute("select * from service")
    for row in cur:
      print(row)
  
  return {
    'statusCode': 200
  }

2. CloudFormationスタックの準備

以下が実際に投入したファイルです。

AWSTemplateFormatVersion: 2010-09-09
#####################################################
# Parameters
#####################################################
Parameters:
  DBMasterUserName:
    Description: Please enter the name of the master user on an RDS.
    Type: String
    Default: master

  DBMasterUserPassword:
    Description: Please enter the password of the master user on an RDS. That should be greater or equal to 8 characters.
    Type: String
    Default: testdbpass
    NoEcho: true

  TestDataS3BucketArn:
    Description: Please enter the ARN of the S3 bucket where the files to be imported into the Aurora database are stored.
    Type: String
    Default: arn:aws:s3:::bucket-name

  S3BucketName:
    Description: Please enter the S3 bucket name.
    Type: String

  VpcSubnet:
    Description: AP VPC subnet
    Type: String
    Default: 10.0.0.0/16

#####################################################
# Mappings
#####################################################
Mappings:
  Constant:
    RDS:
      ClusterName: test-aurora-cluster
      DatabaseName: auroradb
      InstanceName: test-aurora-instance1
      InstanceType: db.t3.small
      RDSProxyName: Auroa-DBProxy

#####################################################
# Resource
#####################################################

#####################################################
#  VPC section
#####################################################
Description: Create VPC
Resources:
  TestVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcSubnet
      Tags:
        - Key: Name
          Value: TestVPC

#####################################################
#  Subnet section
#####################################################
  TestPrivateSub1a:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Select [0, !Cidr [!GetAtt TestVPC.CidrBlock, 2, 8]] #10.0.0.0/24
      MapPublicIpOnLaunch: false
      VpcId: !Ref TestVPC
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: TestPrivateSub1a

  TestPrivateSub1c:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Select [1, !Cidr [!GetAtt TestVPC.CidrBlock, 2, 8]] #10.0.1.0/24
      MapPublicIpOnLaunch: false
      VpcId: !Ref TestVPC
      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: Name
          Value: TestPrivateSub1c

#####################################################
#  SecurityGroup section
#####################################################
  TestSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref "TestVPC"
      GroupDescription: Allow RDS Connection From TCP 3306 Port
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          CidrIp: !GetAtt "TestVPC.CidrBlock"
      Tags:
        - Key: Name
          Value: TestSecurityGroup

  TestSecurityGroupForLambda:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref "TestVPC"
      GroupDescription: Allow MySQL (TCP3306)
      Tags:
        - Key: Name
          Value: TestSecurityGroupForLambda

#####################################################
# RouteTable section
#####################################################
  TestRTPriSub:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref TestVPC
      Tags:
        - Key: Name
          Value: TestRTPriSub

  CfAssocciateRouteTableForPrivateSubnet:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref TestRTPriSub
      SubnetId: !Ref TestPrivateSub1a

#####################################################
#  VPC Endpoint section
#####################################################
  #PrivateSubnetのLambdaがS3にアクセスするためにはVPC S3エンドポイントが必要。Private Subnetのルーティング設定にエンドポイントを追加する。
  VPCS3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - !Ref TestRTPriSub
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      VpcId: !Ref TestVPC

#####################################################
#  DBSubnetGroup section
#####################################################
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: RDS subnet group.
      SubnetIds:
        - !Ref "TestPrivateSub1a"
        - !Ref "TestPrivateSub1c"

#####################################################
# Aurora section
#####################################################
  #AuroraがS3にアクセスするために必要なIAMロールの設定
  RDSS3AccessRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: rds.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: AllowS3Access
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectVersion
                Resource:
                  - !Sub '${TestDataS3BucketArn}'
                  - !Sub '${TestDataS3BucketArn}/*'

  # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbparametergroup.html
  DBParameterGroup:
    Type: "AWS::RDS::DBParameterGroup"
    Properties:
      Description: "RDS DB parameter group the Aurora Cluster's instance(s)."
      Family: "aurora-mysql5.7"
      Parameters:
        max_connections: "10"
      Tags: 
        - Key: Name
          Value: TestClusterParamaterGroup

  # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbclusterparametergroup.html
  DBClusterParameterGroup:
    Type: "AWS::RDS::DBClusterParameterGroup"
    Properties:
      Description: "RDS DB cluster parameter group for Hub-Amber"
      Family: "aurora-mysql5.7"
      Parameters:
        aws_default_s3_role: !GetAtt RDSS3AccessRole.Arn #LOAD DATA FROM S3コマンドを実行し、S3のファイルを読み込むためのIAM role。超重要。
        character_set_client: "utf8mb4"
        character_set_connection: "utf8mb4"
        character_set_database: "utf8mb4"
        character_set_filesystem: "utf8mb4"
        character_set_results: "utf8mb4"
        character_set_server: "utf8mb4"
        collation_connection: "utf8mb4_bin"
        collation_server: "utf8mb4_bin"
        time_zone: "Asia/Tokyo"
      Tags: 
        - Key: Name
          Value: TestClusterParamaterGroup

  DBCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      AssociatedRoles:
        - RoleArn: !GetAtt RDSS3AccessRole.Arn  #LOAD DATA FROM S3コマンドを実行し、S3のファイルを読み込むためのIAM role。超重要。
      Engine: aurora-mysql
      EngineVersion: 5.7.mysql_aurora.2.10.2
      DatabaseName: !FindInMap [Constant, RDS, DatabaseName]
      DBClusterIdentifier: !FindInMap [Constant, RDS, ClusterName]
      MasterUsername: !Ref "DBMasterUserName"
      MasterUserPassword: !Ref "DBMasterUserPassword"
      DBSubnetGroupName: !Ref "DBSubnetGroup"
      DBClusterParameterGroupName: !Ref "DBClusterParameterGroup"
      VpcSecurityGroupIds:
        - !Ref "TestSecurityGroup"

  DBInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      Engine: aurora-mysql
      EngineVersion: 5.7.mysql_aurora.2.10.2
      DBInstanceClass: !FindInMap [Constant, RDS, InstanceType]
      DBSubnetGroupName: !Ref "DBSubnetGroup"
      DBParameterGroupName: !Ref "DBParameterGroup"
      DBClusterIdentifier: !Ref "DBCluster"
      AvailabilityZone: ap-northeast-1a

#####################################################
# Secret Manager section
#####################################################
  RDSProxySecrets:
    Type: AWS::SecretsManager::Secret
    Properties: 
      Name: "SystemManager-db-secret"
      Description: "This is a Secrets Manager secret for an RDS DB instance"
      SecretString: !Sub
        - '{
          "username": "${DBMasterUserName}" ,
          "password": "${DBMasterUserPassword}" ,
          "engine":"mysql",
          "host": "${DBCluster.Endpoint.Address}" ,
          "port":"3306" ,
          "dbname": "${data_base_name}",
          "dbInstanceIdentifier": "${DBCluster}"
        }'
        - data_base_name: !FindInMap [Constant, RDS, DatabaseName]
      Tags: 
        - Key: Name
          Value: RDSProxySecrets
    DependsOn: DBCluster


#####################################################
# RDS Proxy section
#####################################################

  RDSProxyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: rds.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: AllowGetSecretValue
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                  - secretsmanager:DescribeSecret
                Resource:
                  #- !Ref RDSSecretAttachment
                  - !Ref RDSProxySecrets

  DBProxy:
    Type: AWS::RDS::DBProxy
    Properties:
      Auth: 
        - IAMAuth: DISABLED
          AuthScheme: SECRETS
          SecretArn: !Ref RDSProxySecrets #DBの認証情報をあらかじめシークレットARNを利用する
      DBProxyName: !FindInMap [Constant, RDS, RDSProxyName]
      EngineFamily: MYSQL
      IdleClientTimeout: 120
      RequireTLS: false
      RoleArn: !GetAtt RDSProxyRole.Arn #?
      VpcSecurityGroupIds: 
        - !Ref TestSecurityGroup
      VpcSubnetIds: 
        - !Ref TestPrivateSub1a
        - !Ref TestPrivateSub1c
    DependsOn: DBCluster

  DBProxyTargetGroup:
    Type: AWS::RDS::DBProxyTargetGroup
    Properties:
      DBProxyName: !FindInMap [Constant, RDS, RDSProxyName]
      DBClusterIdentifiers:
        - !FindInMap [Constant, RDS, ClusterName]
      TargetGroupName: default
      ConnectionPoolConfigurationInfo:
        MaxConnectionsPercent: 100
        MaxIdleConnectionsPercent: 50
        ConnectionBorrowTimeout: 120
    DependsOn:
      - DBProxy
      - DBCluster
      - DBInstance

#####################################################
# Lambda section (データ投入用Lambda)
#####################################################
  #Lambda実行権限とAuroraの操作権限を付与したIAMロールの設定
  IAMRoleForLambdaFunction:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
        - arn:aws:iam::aws:policy/AmazonRDSFullAccess
      MaxSessionDuration: 3600
      Path: "/"
      RoleName: "RDSFullAccessLambdaExecRole"

  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Environment:
        Variables:
          DB_PORT: '3306'
          DB_NAME: !FindInMap [Constant, RDS, DatabaseName]
          DB_PASSWORD: !Ref "DBMasterUserPassword"
          #DB_WRITER_ENDPOINT_ADDRESS: !GetAtt DBCluster.Endpoint.Address
          DB_PROXY_ENDPOINT_ADDRESS: !GetAtt DBProxy.Endpoint
          DB_USER: !Ref "DBMasterUserName"

      Code:
        S3Bucket: !Ref "S3BucketName"
        S3Key: index.zip
      FunctionName: "TestDataInsertLambda"
      Handler: index.lambda_handler
      Runtime: python3.8
      Role: !GetAtt IAMRoleForLambdaFunction.Arn #Lambda実行権限とAuroraの操作権限を付与したIAM
      Timeout: 10
      VpcConfig:
        SecurityGroupIds:
          - !Ref TestSecurityGroupForLambda
        SubnetIds:
          - !Ref TestPrivateSub1a

前回の記事との差異部分のみ説明します。

RDS proxyをCloudFormationで作成するためには以下が必要になります。

  • 必要な設定
    • RDS ProxyがDBに接続するときに利用する認証情報が格納されたシークレット(AWS::SecretsManager::Secret)
    • RDS Proxyがシークレットを参照するためのIAMロール(AWS::IAM::Role)
    • RDS Proxyの本体。シークレット情報、IAMロール、サブネット、セキュリティグループ等を設定(AWS::RDS::DBProxy)
    • RDS Proxyの接続先情報(AWS::RDS::DBProxyTargetGroup)

以下が今回の差分でメインとなる部分。

#####################################################
# Secret Manager section
#####################################################
  RDSProxySecrets:
    Type: AWS::SecretsManager::Secret
    Properties: 
      Name: "SystemManager-db-secret"
      Description: "This is a Secrets Manager secret for an RDS DB instance"
      SecretString: !Sub
        - '{
          "username": "${DBMasterUserName}" ,
          "password": "${DBMasterUserPassword}" ,
          "engine":"mysql",
          "host": "${DBCluster.Endpoint.Address}" ,
          "port":"3306" ,
          "dbname": "${data_base_name}",
          "dbInstanceIdentifier": "${DBCluster}"
        }'
        - data_base_name: !FindInMap [Constant, RDS, DatabaseName]
      Tags: 
        - Key: Name
          Value: RDSProxySecrets
    DependsOn: DBCluster


#####################################################
# RDS Proxy section
#####################################################

  RDSProxyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: rds.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: AllowGetSecretValue
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                  - secretsmanager:DescribeSecret
                Resource:
                  - !Ref RDSProxySecrets

  DBProxy:
    Type: AWS::RDS::DBProxy
    Properties:
      Auth: 
        - IAMAuth: DISABLED
          AuthScheme: SECRETS
          SecretArn: !Ref RDSProxySecrets #DBの認証情報をあらかじめシークレットARNを利用する
      DBProxyName: !FindInMap [Constant, RDS, RDSProxyName]
      EngineFamily: MYSQL
      IdleClientTimeout: 120
      RequireTLS: false
      RoleArn: !GetAtt RDSProxyRole.Arn #?
      VpcSecurityGroupIds: 
        - !Ref TestSecurityGroup
      VpcSubnetIds: 
        - !Ref TestPrivateSub1a
        - !Ref TestPrivateSub1c
    DependsOn: DBCluster

  DBProxyTargetGroup:
    Type: AWS::RDS::DBProxyTargetGroup
    Properties:
      DBProxyName: !FindInMap [Constant, RDS, RDSProxyName]
      DBClusterIdentifiers:
        - !FindInMap [Constant, RDS, ClusterName]
      TargetGroupName: default
      ConnectionPoolConfigurationInfo:
        MaxConnectionsPercent: 100
        MaxIdleConnectionsPercent: 50
        ConnectionBorrowTimeout: 120
    DependsOn:
      - DBProxy
      - DBCluster
      - DBInstance

3. CloudFormationスタックの作成

AWSコンソールでのCloudFormationテンプレートの投入方法は、前回の記事通りです。
こちらを参考に進めてください。

4. Lambadaによるテストデータの投入

Lambdaによるテストデータについても前回の記事通りです。
こちらを参考に進めてください。

テストデータの投入は以上ですので、必要に応じてPythonプログラムを書き換えて利用できます。

本記事は以上です。
クラウド環境の作成の手助けになれば嬉しいです。

参考文献

本記事の作成に当たり、以下の情報も参考にさせて頂きました。ありがとうございました。

2
2
0

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
2
2