本連載では、AWSリソース基盤構築の自動化を実践しています。現在は、第2回から解説してきた継続的インテグレーション環境をCloudFormationを使って自動構築する方法について解説しています。

構成図

前回は、CloudFormationのカスタムリソースを使ってRDS構築後のデータベースにユーザーを追加する処理をLambdaファンクションとして実装し、S3バケットへアップロードしました。続く今回は、LambdaをデプロイするCloudFormationや前回実装したファンクションを実行するカスタムリソーステンプレートを作成します。

なお、実際のソースコードはGitHub上にコミットしています。以降のソースコードでは本質的でない記述を一部省略しているので、実行コードを作成する場合は、必要に応じて適宜GitHub上のソースコードも参照してください。

Lambdaをデプロイするテンプレート

それではまず、LambdaファクションをデプロイするCloudFormationテンプレートを実装します。

AWSTemplateFormatVersion: '2010-09-09'

Description: Lambda for create user in RDS template with YAML - Depends On base/1-vpc-cfn.yml, base/2-sg-cfn.yml.

// omit

Resources:
  LambdaForCreatingTableInRds:
    Type: AWS::Lambda::Function                                              # (A)
    Properties:
      Code:
        S3Bucket:
          Fn::ImportValue: !Sub ${VPCName}-Lambda-S3Bucket                   # (B)
      S3Key: mynavi-sample-sonarqube-initdb-0.0.1-SNAPSHOT-aws.jar           # (C)
      Handler: org.debugroom.mynavi.sample.sonarqube.initdb.app.handler.LambdaTriggerHandler::handleRequest
                                                                             # (D)
      FunctionName: mynavi-sonarqube-cfn-rds-dbinit-function
      Environment:
        Variables:
          FUNCTION_NAME: initDBFunction                                      # (E)
      MemorySize: 1024                                                       # (F)
      Runtime: java8
      Timeout: 120
      VpcConfig:                                                             # (G)
        SecurityGroupIds:
          - Fn::ImportValue: !Sub ${VPCName}-SecurityGroupLambda             # (H)
        SubnetIds:                                                           # (I)
          - Fn::ImportValue: !Sub ${VPCName}-PrivateSubnet1
          - Fn::ImportValue: !Sub ${VPCName}-PrivateSubnet2
      Role: !GetAtt LambdaRDSAccessRole.Arn                                  # (J)

  LambdaRDSAccessRole:                                                       # (K)
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
                                                                             # (L)

  CloudFormationAccessPolicy:                                                # (M)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: ma-Lambda-CloudFormationAccessPolicy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "cloudformation:*"
            Resource: "*"
      Roles:
        - !Ref LambdaRDSAccessRole

  SSMAccessPolicy:                                                           # (N)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: ma-lambda-SSMAccessPolicy
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
            - "cloudwatch:PutMetricData"
            # omit
      Roles:
        - !Ref LambdaRDSAccessRole

Outputs:                                                                     # (O)
  LambdaForCreateUserInRdsArn:
    Description: Lambda function for creating table in rds
    Value: !GetAtt LambdaForCreatingTableInRds.Arn
    Export:
      Name: !Sub ${VPCName}-LamdaForCreateUserInRdsArn

テンプレートのポイントになる箇所は以下の通りです。

項番 説明
A Lambdaファンクションとしてのリソースを定義します。定義の詳細は「AWS::Lambda::Function 」も参照してください
B 前々回作成したデプロイ用S3テンプレートのOutput要素で出力したバケット名をクロススタックリファレンスを使って取得します
C 前回作成したシェルスクリプト内でMavenビルド/S3へアップロードしたLambdaファンクションのJarファイル名を指定します
D 前回実装したHandlerクラスのFQCNとハンドラメソッドを指定します
E 連載「AWSで作るクラウドネイティブアプリケーションの基本」の第2回 と同様、環境変数にFUNCTION_NAMEとして、ファンクションクラスとなるBean名を指定しておきます(Spring Cloud Functionでは、実行する関数を環境変数FUNCTION_NAMEで指定したBean名で取得するためです)
F ファンクション実行時のメモリサイズを指定します。SpringアプリケーションをLambdaで実行する場合1024MB以上のメモリサイズを確保しなければ、起動時間が大幅にかかってしまうケースがあります
G Lambdaが接続するVPCの設定を行います
H Lambdaに割り当てるセキュリティグループを指定します。前々回セキュリティグループのテンプレートで実装した通り、このセキュリティグループはRDSの接続が許可されています
I Lambdaに割り当てるサブネットを指定します。ここではプライベートサブネットを指定しておきます
J Lambdaに設定するIAMロールのARNを指定します。ここではKで定義したARNを設定します
K Lambdaに設定するIAMロールを定義します。ファンクション内の実装では、CloudFormationのスタック参照、SystemsManager Parameter Storeの参照を行いますが、M、Nで定義したポリシーからアタッチして、このロールを参照するように設定します
L LambdaがVPCにアクセスするために必要な、AWSLambdaVPCAccessExecutionRoleをアタッチします。詳細はAWS Lambdaの開発者ガイドの「VPC内のリソースにアクセスするためのLambda関数の設定」も参照してください
M CloudFormationスタックのアクセスを可能にするIAMポリシーを定義し、Kのロールへアタッチします
N SystemsManager Parameter Storeへのアクセスを可能にするIAMポリシーを定義し、Kのロールへアタッチします
O 後述するカスタムリソーステンプレートのイベント設定で参照するため、LambdaファンクションのARNをOutput要素として出力します

作成したテンプレートをCLIなどで実行し、Lambdaファンクションをデプロイします。

Lambdaを起動するカスタムリソーステンプレート

次に、このLambdaを起動するカスタムリソーステンプレートを作成します。ServiceTokenには、上述のコードの0で出力したLambdaファンクションのARNを指定します。

# omit

Resources:
  LambdaTrigger:
    Type: Custom::LambdaTrigger
    Properties:
      ServiceToken:
        Fn::ImportValue: !Sub ${VPCName}-LamdaForCreateUserInRdsArn
       Region: !Ref "AWS::Region"

このテンプレートを実行すると、Lambdaが起動して、CloudWatch LogsにデプロイしたLambdaファンクションの実行ログが出力されます。

Lambdaファンクションの実行ログ

もし実行時にエラーが発生し、Lambdaファンクション内でResponseURLにシグナルを送れずに終わってしまうと、CloudFormationのスタックのステータスがProgress中で止まったままとなります。その場合は、AWSナレッジセンターのサポートページを参考にCloudWatch Logsに出力された ResponseURL、StackId、RequestIdなどのパラメータを指定し、直接リクエストを送信してステータスを完了させるほうが簡単です(そのままにしておいても1時間後にエラーになって終わるだけです)。

* * *

今回はLambdaをデプロイするCloudFormationと、前回実装したファンクションを実行するカスタムリソーステンプレートを作成し、RDSへの初期化処理を行うLambdaファンクションを実行しました。次回は、SonarQubeServerを起動するCloudFormationテンプレートを実装します。

著者紹介


川畑 光平(KAWABATA Kohei) - NTTデータ

金融機関システム業務アプリケーション開発・システム基盤担当、ソフトウェア開発自動化関連の研究開発を経て、デジタル技術関連の研究開発・推進に従事。

Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professional等の資格を持ち、アプリケーション基盤・クラウドなど様々な開発プロジェクト支援にも携わる。AWS Top Engineers & Ambassadors選出。

本連載の内容に対するご意見・ご質問は Facebook まで。