AssociatedRolesを使ってCFnでS3とデータをロード&アンロードできるAurora MySQL5.7を構築する

2019.09.07

おはようございます、もきゅりんです。

CFnを使用してS3とデータをロード&アンロードできるAurora MySQL5.7を構築したので、まとめておきます。

テンプレートの再利用等で役立てば幸いです。

基本的な利用方法、概要は把握している前提で進ます。

そもそも概要を理解するためのドキュメントはこちらですが、

Amazon S3 バケットのテキストファイルから Amazon Aurora MySQL DB クラスターへのデータのロード

読みやすく簡潔にまとまっている下記弊社ブログもとても参考になります。

Amazon Aurora MySQLとS3間でデータをロード&アンロードする

上記記事からセットアップ手順の概要を再掲しておきます。

  1. RDSがS3にアクセスするためのIAMロールを作成
  2. DB クラスターパラメーターグループのパラメーター(aws_default_s3_role)にIAMロールのARNを指定。Auroraクラスターを起動。
  3. 1のIAMロールをAuroraクラスターにアタッチ
  4. mysqlクライアントからSELECT INTO OUTFILE S3/ LOAD DATA FROM S3を実行

上記1~3の手順をCFnテンプレートで自動化します。

※ ブログをアップした際に気付かなかったのですが、2019/8/29にUpdateかかっており、CFnのドキュメントを英語版にするとプロパティが表示されました。。 間抜けでした...

AWS Documentation » AWS CloudFormation » User Guide » Release History

もともとはセットアップ手順3を手動、またはカスタムリソースを利用して設定しなければなりませんでしたが、今回の更新によってすべて自動で対応できるものとなりました。 ブログアップ時は手動およびカスタムリソースで対応していました。

前提

  • DBSubnetは作成済み
  • DBのSecurityGroupは作成済み(DB-sgでエクスポートされているとする)
  • S3バケットを作成済み(${ENV}-${AWS::AccountId}-bucketとする)

やること

  1. スタックを作成する
  2. LOAD DATA FROM S3をテストする
  3. オマケ

1. スタックを作成する

スタック作成の前に、Parameter StoreにDBパスワードを格納します。

aws ssm put-parameter --name DBMasterPassword --value 'DBPASSWORD' --type SecureString

スタックを作成します。

aws cloudformation deploy --stack-name demo-aurora-stack \
  --template -file sample1.yml \
  --parameter-overrides \
  DBName="DBNAME" \
  DBMasterUserName="DBMasterUserName" \
  DBSubnetGroupName="DBSubnetGroupName" \
  NameTagPrefix="NameTagPrefix" \
  ENV="ENV" \
  --capabilities CAPABILITY_NAMED_IAM

ImportValueやParamtersを参照している部分は、利用する環境に応じて適宜調整して下さい。

各種パラメータも適宜調整が必要となるかと思います。

AWSTemplateFormatVersion: 2010-09-09
Description: Create Aurora MySQL5.7 Cluster

# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
  NameTagPrefix:
    Type: String
    Default: test
    Description: Prefix of Name tags.
  ENV:
    Type: String
    Default: stg
    Description: Prefix of Env tags.
  MultiAZ:
    Description: MultiAZ true or false
    Type: String
    Default: false
    AllowedValues:
      - true
      - false
  DBName:
    Description: Enter DB Name
    Type: String
  DBMasterUserName:
    Description: DB MasterUserName
    Type: String
  DBInstanceClass:
    Description: DBInstanceType
    Type: String
    Default: 'db.t3.small'
  DBSubnetGroupName:
    Description: DB SubnetName
    Type: String
  BackupRetentionPeriod:
    Description: BuckUp Generation(1-35)
    Type: String
    Default: '7'
  BackupWindow:
    Description: Enter BackupWindow
    Type: String
    Default: '19:00-19:30'
  MaintenanceWindow:
    Description: Enter MaintenanceWindow(UTC)
    Type: String
    Default: 'sun:19:30-sun:20:00'
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
  # IAM Role for S3 Access
  S3AccessRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${NameTagPrefix}-${ENV}-DB-AccessS3Role'
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - 'rds.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: !Sub '${NameTagPrefix}-${ENV}-AllowAuroraToReadS3'
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource:
                  - !Sub 'arn:aws:s3:::${ENV}-${AWS::AccountId}-bucket'
                  - !Sub 'arn:aws:s3:::${ENV}-${AWS::AccountId}-bucket/*'
                Effect: Allow
                Action:
                  - s3:ListBucket
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:AbortMultipartUpload
                  - s3:DeleteObject
                  - s3:GetObject
                  - s3:ListMultipartUploadParts
                  - s3:PutObject

  AuroraMySQL57DBClusterParameterGroup:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Family: 'aurora-mysql5.7'
      Description: 'Aurora-MySQL57 ClusterParameterGroup'
      Parameters:
        time_zone: 'Asia/Tokyo'
        aws_default_s3_role: !GetAtt S3AccessRole.Arn
  AuroraMySQL57DBOptionGroup:
    Type: 'AWS::RDS::OptionGroup'
    Properties:
      EngineName: 'aurora-mysql'
      MajorEngineVersion: '5.7'
      OptionGroupDescription: 'Aurora MysQL57 DB_RDS_OptionGroup'
      Tags:
        - Key: Name
          Value: Aurora-MySQL57DB-OptionGroup
  AuroraMySQL57DBInstanceParameterGroup:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Family: 'aurora-mysql5.7'
      Description: 'Aurora-MySQL57 InstanceParameterGroup'
  AuroraMySQL57DBCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      AssociatedRoles:
        - RoleArn: !GetAtt S3AccessRole.Arn
      BackupRetentionPeriod: !Ref BackupRetentionPeriod
      DBClusterIdentifier: !Sub '${NameTagPrefix}-${ENV}-mysql57cluster'
      DBClusterParameterGroupName: !Ref AuroraMySQL57DBClusterParameterGroup
      DBSubnetGroupName: !Ref DBSubnetGroupName
      Engine: aurora-mysql
      DatabaseName: !Ref DBName
      MasterUsername: !Ref DBMasterUserName
      MasterUserPassword: '{{resolve:ssm-secure:DBMasterPassword:1}}'
      Port: 3306
      PreferredBackupWindow: !Ref BackupWindow
      PreferredMaintenanceWindow: !Ref MaintenanceWindow
      StorageEncrypted: True
      EnableCloudwatchLogsExports:
        - error
        - general
        - slowquery
        - audit
      VpcSecurityGroupIds:
        - Fn::ImportValue: !Sub DB-sg
      Tags:
        - Key: Name
          Value: Aurora-MySQL57DB-Cluster
  AuroraMySQL57DBInstance:
    Type: 'AWS::RDS::DBInstance'
    Properties:
      AutoMinorVersionUpgrade: False
      AvailabilityZone: ap-northeast-1a
      DBInstanceClass: !Ref DBInstanceClass
      DBInstanceIdentifier: !Sub '${NameTagPrefix}-${ENV}-mysql57db'
      DBClusterIdentifier: !Ref AuroraMySQL57DBCluster
      DBSubnetGroupName: !Ref DBSubnetGroupName
      Engine: aurora-mysql
      OptionGroupName: !Ref AuroraMySQL57DBOptionGroup
      DBParameterGroupName: !Ref AuroraMySQL57DBInstanceParameterGroup
      PubliclyAccessible: False
      Tags:
        - Key: Name
          Value: AuroraMySQL57DBInstance
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
  DBEndpoint:
    Value:
      Fn::GetAtt:
        - AuroraMySQL57DBInstance
        - Endpoint.Address

2. LOAD DATA FROM S3をテストする

今回はLOAD DATA FROM S3をテストします。

(テンプレートではSELECT INTO OUTFILE S3の設定も可能にしています。)

こんなcsvデータを作成します。

$ cat .demodata.csv
No,Name,Age
1,Taro,30
2,Jiro,20
3,Hanako,23
4,Yoshiko,99
5,Masao,450%

まずこれをS3バケットにアップロードします。

$ aws s3 cp demodata.csv s3://${ENV}-${AWS::AccountId}-bucket/demodata.csv
upload: ./demodata.csv to s3://${ENV}-${AWS::AccountId}-bucket/demodata.csv
$ aws s3 ls s3://${ENV}-${AWS::AccountId}-bucket/
2019-09-06 18:27:30         76 demodata.csv

そしたら、Aurora MySQLに接続してSQLコマンドを入力します。 デモ用のテーブルを作成します。

MySQL [dbname]> create table demo(No int, Name text, Age int);
Query OK, 0 rows affected (0.03 sec)

LOAD DATA FROM S3 ステートメントを使ってデータをロードします。

列名を含む最初のヘッダー行をスキップするため、IGNORE 1 LINESを利用します。

構文はこちらを記載されております。

Amazon S3 バケットのテキストファイルから Amazon Aurora MySQL DB クラスターへのデータのロード

MySQL [dbname]> LOAD DATA FROM S3  's3://${ENV}-${AWS::AccountId}-bucket/demodata.csv'
    ->     INTO TABLE demo
    ->     FIELDS TERMINATED BY ','
    ->     IGNORE 1 LINES;
Query OK, 5 rows affected (0.09 sec)
Records: 5  Deleted: 0  Skipped: 0  Warnings: 0

確認します。

MySQL [dbname]> select * from demo;
+------+---------+------+
| No   | Name    | Age  |
+------+---------+------+
|    1 | Taro    |   30 |
|    2 | Jiro    |   20 |
|    3 | Hanako  |   23 |
|    4 | Yoshiko |   99 |
|    5 | Masao   |  450 |
+------+---------+------+
5 rows in set (0.01 sec)

ちゃんとロードされました。

3. オマケ

PubliclyAccessible: Falseの場合の利用のために、VPCエンドポイント作成のためのCFnテンプレートも記載しておきます。

AWSTemplateFormatVersion: 2010-09-09
Description: VPC S3Endpoint Create

# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
  NameTagPrefix:
    Type: String
    Default: test
    Description: Prefix of Name tags.
  ENV:
    Type: String
    Default: stg
    Description: Prefix of Env tags.
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
  VPCS3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - Fn::ImportValue: !Sub '${NameTagPrefix}-${ENV}-private-rtb'
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      VpcId:
        Fn::ImportValue: !Sub '${NameTagPrefix}-${ENV}-vpc'

# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
  VPCS3Endpoint:
    Value: !Ref VPCS3Endpoint
    Export:
      Name: !Sub '${NameTagPrefix}-${ENV}-VPC-S3endpoint'

最後に

ほぼ1週間前、ドキュメントに更新がかかっていたことを見過ごして、更新前のブログでは余計な手間と時間を費やしていました。

望む機能は本当にないか要確認、ですね。

いい勉強でした。

以上です。

どなたかのお役に立てば幸いです。

参考