2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CloudFormationからLambdaを実行してテーブル情報の入ったRDSを自動作成

Last updated at Posted at 2019-08-26

はじめに

CloudFormationはAWSサービスの外側を作るものですが、
最初からテーブルの入ったRDSを作れないかと考え以下の方法で作成しました。

① CloudFormationでRDSの作成
② CloudFormationからLambdaの実行
③ LambdaでRDSにテーブル情報の投入
012.PNG

CloudFormation用yamlファイルの中身と解説

まず、CloudFormationで実行されるyamlファイルについてですが、
ファイルの中身は以下のようになっております。

Resources:
  DBInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: "RDSName" #この名前を使ってLambdaはエンドポイント名を取得します
      DBName: jimbot3rds
      Engine: MySQL
      MasterUsername: jimbot3
      DBInstanceClass: db.t2.micro 
      AllocatedStorage: '20'
      MasterUserPassword: xxxxxxxx
      VPCSecurityGroups:
      - !GetAtt DBSecurityGroup.GroupId

  Mylambda: 
    Type: "Custom::ExcuteLambda"
    Version: "1.0"
    Properties: 
      ServiceToken: "arn:aws:lambda:ap-northeast-1:XXXX~~~~" #ここにLambdaのARNを入力します
      RequestType: Create
    DependsOn: DBInstance

  WebServerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: 22only
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp: "0.0.0.0/0"

  DBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Open database for access
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '3306'
        ToPort: '3306'
        CidrIp: 0.0.0.0/0

解説1
DBInstanceIdentifier:の箇所で作成するRDSの名前を決めます。
この名前から後に記載しているLambdaでRDSのエンドポイントを拾います。

解説2
Mylambda: ~の箇所がカスタムリソースの定義で、Lambdaの実行を定義しています。
また、RDSが作成された後にLambdaを実行する必要があるためDependsOn: DBInstanceとしました。

※ファイルはこちらのAmazon RDS テンプレートスニペットを参考にしています。

Lambdaの事前準備

今回のLambdaは、pymysqlがインストール状態である環境でなければ実行できないため、
以下の手順でCloud9上でpymysqlをインストールし、そこからLambdaのコンソールに環境をデプロイしました。
※インストールをしていないデフォルトのLmbdaではpymysqlが利用できないエラーが以下のように出力されます。

 "errorMessage": "Unable to import module 'lambda_function': No module named 'pymysql'",
  1. Cloud9で「λ」のボタンを押下
001.PNG
  1. Lambda名を入力しNext
002.PNG

3.runtimeでPython3.6とテンプレート無しのemptyを選択しNext
003.PNG

4.以降はNextとFinish
004.PNG

5.以下のコマンドを入力しpymysqlのインストール

$ cd rdslambda
$ pip install pymysql -t ./

6.ディレクトリ構成が反映されていることを確認
005.PNG

7.Deployボタンを押下
<006.PNG

8.Lambdaコンソールに同じディレクトリ構成でデプロイされたことを確認
これでLambdaからRDSに繋ぐための"pymysql.connect~"などが使えます
007.PNG

Lambdaの中身と解説

↑で準備したLambdaに以下のソースを記載します。

import boto3
import pymysql.cursors
from botocore.vendored import requests
import json

SUCCESS = "SUCCESS"
FAILED = "FAILED"

def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False):
    responseUrl = event['ResponseURL']

    print(responseUrl)

    responseBody = {}
    responseBody['Status'] = responseStatus
    responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name
    responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name
    responseBody['StackId'] = event['StackId']
    responseBody['RequestId'] = event['RequestId']
    responseBody['LogicalResourceId'] = event['LogicalResourceId']
    responseBody['NoEcho'] = noEcho
    responseBody['Data'] = responseData

    json_responseBody = json.dumps(responseBody)

    print("Response body:\n" + json_responseBody)

    headers = {
        'content-type' : '',
        'content-length' : str(len(json_responseBody))
    }

    try:
        response = requests.put(responseUrl,
                                data=json_responseBody,
                                headers=headers)
        print("Status code: " + response.reason)
    except Exception as e:
        print("send(..) failed executing requests.put(..): " + str(e))


def lambda_handler(event, context):
    dbinstance = 'RDSName' #↑のCloudFormation用のyamlファイルに記載したDBInstanceIdentifierを入力
    rds = boto3.client('rds')
    myendpoint = rds.describe_db_instances(DBInstanceIdentifier=dbinstance)

    rds_host  = myendpoint['DBInstances'][0]['Endpoint']['Address']
    db_user = "jimbot3"
    password = "xxxxxxxx"
    db_name = "jimbot3rds"

    conn = pymysql.connect(host=rds_host, user=db_user, passwd=password, db=db_name, connect_timeout=5)
    with conn.cursor() as cur:
        cur.execute("create table jimbots (id int, name varchar(10),PRIMARY KEY (id))")
        cur.execute('insert into jimbots (id, name) values(1, "jimbot1")')
        cur.execute('insert into jimbots (id, name) values(2, "jimbot2")')
        conn.commit()
        cur.execute("select * from jimbots")
        for row in cur:
            print(row)
    send(event, context, SUCCESS, {})
    

解説1
from botocore.vendored import requests から print("send(..) failed executing requests.put(..): " + str(e)) までと、
1番最後にある send(event, context, SUCCESS, {}) は、CloudFormationからLambdaがたたかれた場合に、
CloudFormationにLambdaからレスポンスを返すためのcfn-responseモジュールのソースコードです。
これが無ければCloudFormationにレスポンスが返されないためCloudFormationのスタックの作成が終わりません。
cfn-responseモジュールはLambdaのコンソール画面上に"import cfnresponse"と記載しても使えないためソースコードの記載です。
※cfn-responseモジュールのソースコードはこちら

解説2
myendpoint = rds.describe_db_instances(DBInstanceIdentifier=dbinstance)
はRDSのエンドポイントの取得を行っています。
CloudFormationでRDSを作るときにRDSの名前は指定できますがRDSのエンドポイントは指定できないため、
こちらで指定した名前からエンドポイントを取得し、そのまま、そのエンドポイントを使用してRDSに繋ぎに行けるようにしています。

その他
・LambdaにはRDSFullAccessのロールが必要です。
・selectとprint(row)はデータが投入されたことの確認用で本来は不要です。

実行結果

① CloudFormationの実行ログです。すべて成功しているし、ちゃんとRDSができた後にLambdaが実行されています。
009.PNG

② Lambdaの実行結果のログです。(こっちは+09:00が日本時間です)
 赤枠の"Status": "SUCCESS"の箇所がLambdaが成功したことをCloudFormationに返しています。
010.PNG

③ RDSに繋ぎ中にデータが入っていることの確認です。
 ちゃんとJimbotsテーブルがあります。
011.PNG

終わりに

LambdaにSQL文書いてるし結局、自動化っていうよりコード化というのかなぁという感じ。。。
残念ながらあまり利用できるケースが思い浮かばないので、普通に作成してデータ入れたほうが良いと思う。←
良かった点は「どうやったらできるんだろう?」と考えるのが楽しかったところくらいかな。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?