CloudFormationがStep FunctionsをサポートしたのでServerless Frameworkから使ってみた

AWS CloudFormation

はじめに

こんにちは、中山です。

米国時間2017/02/10、CloudFormation(以下CFn)がStep Functionsをサポートしました。今回導入されたリソースは以下の2つです。プロパティに指定可能な値はドキュメントを参照してください。

早速使ってみたので本エントリでご紹介させていただきます。単純に使うだけだとあまり面白くないのでServerless Frameworkからこの機能を利用してみます。なお、Serverless FrameworkとStep Functionsの連携はプラグインを利用することでも実現可能です。ご興味があれば以下のエントリを参照してください。

本エントリを執筆する上で検証に利用した主要なツールのバージョンは以下の通りです。バージョンによって結果が変更される可能性があるので、その点ご了承ください。

  • Serverless Framework: 1.6.1

使ってみる

以下のファイルを用意してください。

  • serverless.yml
service: aws-python

frameworkVersion: ">=1.6.0"

custom:
  config: ${file(config.${opt:stage}.yml)}

provider:
  name: aws
  runtime: python2.7
  stage: ${opt:stage}
  region: ap-northeast-1


functions:
  hello:
    handler: handler.hello

resources: ${file(resource.yml)}

Serverless Frameworkは resources プロパティでCFnテンプレートを指定できます。 serverless.yml に直接記述することも可能ですが、上記のように ${file(path/to/template.yml)} の形で別ファイルから読み込むことも可能です。

  • resource.yml
---
AWSTemplateFormatVersion: "2010-09-09"
Description: Step Functions Test

Resources:
  InvokeLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: StepFunctionsAssumeRolePolicy
            Effect: Allow
            Principal:
              Service:
                Fn::Join: [ ".", [ states, Ref: "AWS::Region", amazonaws, com ] ]
            Action: sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaRole
  TestStateMachine:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      RoleArn:
        Fn::GetAtt: [ InvokeLambdaRole, Arn ]
      DefinitionString: |-
        {
          "StartAt": "HelloWorld",
          "States": {
            "HelloWorld": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:${self:provider.region}:${self:custom.config.accountId}:function:${self:service}-${opt:stage}-hello",
              "End": true
            }
          }
        }

Outputs:
  StateMachineArn:
    Value:
      Ref: TestStateMachine
  StateMachineName:
    Value:
      Fn::GetAtt: [ TestStateMachine, Name ]

CFnテンプレートの内容は単純です。 AWS::Iam::Role リソースでState Machine用のIAM Roleを作成し、 AWS::StepFunctions::StateMachine でState Machineを作成しています。

AWS::StepFunctions::StateMachine リソースでは DefinitionString プロパティでAmazon State Languageを記述できます。 Task に指定している Resource にはServerless FrameworkでデプロイしたLambda関数のARNを指定する必要がるのですが、今回は各種値(リージョンやAWSアカウントIDなど)にServerless Frameworkの強力な変数展開機能を利用しました。もし、CFn単体でState Machineを作成するのであれば、以下のようにFn::Sub組み込み関数を利用してLambda関数のARNを取得するとよいと思います。

  TestStateMachine:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      RoleArn: !GetAtt InvokeLambdaRole.Arn
      DefinitionString: !Sub |-
        {
          "StartAt": "HelloWorld",
          "States": {
            "HelloWorld": {
              "Type": "Task",
              "Resource": "${SomeLambdaFunc.Arn}",
              "End": true
            }
          }
        }

なお、残念ながらServerless Framework 1.6.1時点ではCFnの組み込み関数の短縮機能がサポートされていないようです。また、例えば AWSTemplateFormatVersion: "2010-09-09" の日付部分を引用符で囲まないとエラーが出てしまいます。恐らくCFnではなくServerless Framework独自のバリデーションがテンプレート作成の前に働いているのかと推測しますが、この辺りもう少し改善してほしいですね。

スタックの作成は以下のコマンドで実行します。今回は説明を省略しましたが、利用しているLambda関数は sls create で出力されるテンプレートをそのまま使っています。

$ sls deploy -v -s dev
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
<snip>

スタックが作成されたらState Machineの動作を確認してみます。

  • State Machineが作成されたことを確認
$ aws stepfunctions list-state-machines
{
    "stateMachines": [
        {
            "creationDate": 1486760475.332,
            "stateMachineArn": "arn:aws:states:ap-northeast-1:************:stateMachine:TestStateMachine-4VMKMDOODSCT",
            "name": "TestStateMachine-4VMKMDOODSCT"
        }
    ]
}
  • State Machineの内容が意図したものになっていることを確認
$ aws stepfunctions describe-state-machine \
  --state-machine-arn "$(aws stepfunctions list-state-machines \
    --query 'stateMachines[?name==`TestStateMachine-4VMKMDOODSCT`].stateMachineArn' \
    --output text)"
{
    "status": "ACTIVE",
    "definition": "{\n  \"StartAt\": \"HelloWorld\",\n  \"States\": {\n    \"HelloWorld\": {\n      \"Type\": \"Task\",\n      \"Resource\":\"arn:aws:lambda:ap-northeast-1:************:function:aws-python-dev-hello\",\n      \"End\": true\n    }\n  }\n}",
    "name": "TestStateMachine-4VMKMDOODSCT",
    "roleArn": "arn:aws:iam::************:role/aws-python-dev-InvokeLambdaRole-1P0ARBB2HRIS6",
    "stateMachineArn": "arn:aws:states:ap-northeast-1:************:stateMachine:TestStateMachine-4VMKMDOODSCT",
    "creationDate": 1486760475.332
}
  • State Machineの実行
$ aws stepfunctions start-execution \
  --state-machine-arn "$(aws stepfunctions list-state-machines \
    --query 'stateMachines[?name==`TestStateMachine-4VMKMDOODSCT`].stateMachineArn' \
    --output text)" \
  --input "$(jo hoge=fuga)"
{
    "startDate": 1486762893.681,
    "executionArn": "arn:aws:states:ap-northeast-1:************:execution:TestStateMachine-4VMKMDOODSCT:d79f8996-07f1-493e-99c1-4a679ce97071"
}
  • 実行結果が意図したものになったことを確認
$ aws stepfunctions describe-execution \
  --execution-arn "arn:aws:states:ap-northeast-1:************:execution:TestStateMachine-4VMKMDOODSCT:d79f8996-07f1-493e-99c1-4a679ce97071"
{
    "status": "SUCCEEDED",
    "startDate": 1486762893.681,
    "name": "d79f8996-07f1-493e-99c1-4a679ce97071",
    "executionArn": "arn:aws:states:ap-northeast-1:************:execution:TestStateMachine-4VMKMDOODSCT:d79f8996-07f1-493e-99c1-4a679ce97071",
    "stateMachineArn": "arn:aws:states:ap-northeast-1:************:stateMachine:TestStateMachine-4VMKMDOODSCT",
    "stopDate": 1486762894.946,
    "output": "{\"body\": \"{\\\"input\\\": {\\\"hoge\\\": \\\"fuga\\\"}, \\\"message\\\": \\\"Go Serverless v1.0! Your function executed successfully!\\\"}\", \"statusCode\": 200}",
    "input": "{\"hoge\":\"fuga\"}"
}

まとめ

いかがだったでしょうか。

CloudFormationがStep Functionsをサポートした点、またServerless Frameworkからこの機能を利用する方法をご紹介しました。 DefinitionString のYAMLサポート早く来てくれ!!!!1111

本エントリがみなさんの参考になれば幸いに思います。

AWS Cloud Roadshow 2017 福岡