Serverless FrameworkでStep Functionsを扱ってみる

この記事はServerless Advent Calendar 2017の24日目に遅刻した記事です。

AWS Step Functions

AWS Lambda 1つで完了できないようなタスク・ワークフローをいい感じに処理できるようにするやつ(という適当な認識)なAWS Step Functions。

使ってみたいなと思いつつなかなか使う機会がなかったので、これを機に触ってみました。

GUIで設定はしたくない

チュートリアルなどは基本的にGUIでポチポチやるスタイルですが、terraformやCloudFormation、Serverless Frameworkに慣れてくるとコードで定義したくなります。

ということで堀家さんのプラグイン、Serverless Step FunctionsをServerless Frameworkに入れて立ち上げるようにします。

プロジェクトのセットアップ

とりあえずServerless Frameworkを使ったプロジェクトでやるいつもの手順。

$ serverless create -t aws-nodejs -p step-functions
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/dc_hideokamoto/develop/node/sls/step-functions"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.21.1
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"

$ git init && npm init -y
Initialized empty Git repository in /node/sls/step-functions/.git/
Wrote to /node/sls/step-functions/package.json:

{
  "name": "step-functions",
  "version": "1.0.0",
  "description": "",
  "main": "handler.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Serverless Step Functionsプラグインのインストール

Serverless Frameworkのプラグインとして配布されていますので、インストールとセットアップを進めます。

$ npm install --save-dev serverless-step-functions

serverless.ymlにもプラグインを使用することを明記しましょう。

service: sls-step-functions-example

provider:
  name: aws
  runtime: nodejs6.10

functions:
  hello:
    handler: handler.hello

plugins:
  - serverless-step-functions

これで準備OKです。

プロジェクトをデプロイする

ドキュメントを見る限り、StateMachineの定義でLambdaのARNが必要になる様子です。

serverless.ymlの中で値を引き回す方法があるのかもですが、ちょっと調べきれてないので今回は一旦Lambdaを先にデプロイして決めうちで書くやり方をとります。

$ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (3.49 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..................
Serverless: Stack update finished...
Service Information
service: sls-step-functions-example
stage: dev
region: us-east-1
stack: sls-step-functions-example-dev
api keys:
  None
endpoints:
  None
functions:
  hello: sls-step-functions-example-dev-hello
Serverless StepFunctions OutPuts
endpoints:

ちゃんと動作してくれています。

$ sls invoke -f hello
{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}"
}

LambdaのARNはAWS-CLIからとりました。

$ aws lambda list-functions | grep sls-step-functions-example-dev-hello
            "FunctionName": "sls-step-functions-example-dev-hello", 
            "FunctionArn": "arn:aws:lambda:us-east-1:9999999999:function:sls-step-functions-example-dev-hello",

FunctionArnの値を控えたら、serverless.ymlに以下の項目を追加します。

stepFunctions:
  stateMachines:
    hellostepfunc1:
      name: myStateMachine
      definition:
        Comment: "A Hello World example of the Amazon States Language using an AWS Lambda Function"
        StartAt: HelloWorld1
        States:
          HelloWorld1:
            Type: Task
            Resource: arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:${self:service}-${opt:stage}-hello
            End: true

この状態でデプロイすると、最初のStep Functionsが作成されます。

$ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (3.49 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
............
Serverless: Stack update finished...
Service Information
service: sls-step-functions-example
stage: dev
region: us-east-1
stack: sls-step-functions-example-dev
api keys:
  None
endpoints:
  None
functions:
  hello: sls-step-functions-example-dev-hello
Serverless StepFunctions OutPuts
endpoints:

シンプルなStep functions

State Machineを追加したりする

これだけでは普通のLambdaでええやんって話になるので、Step Functionsっぽいワークフローにしてみます。

2つめのLambdaを追加する

handler.jshello()と違うメッセージを返す関数を追加します。
ワークフローの中で違うLambdaをよんだりするテストをするために使います。

'use strict';

module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless v1.0! Your function executed successfully!',
      input: event,
    }),
  };

  callback(null, response);
};

module.exports.final = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Passed final states',
      input: event,
    }),
  };
  callback(null, response);
};

serverless.ymlの更新

追加した関数をLambdaにデプロイできるようにします。

functions:
  hello:
    handler: handler.hello
  final:
    handler: handler.final

stateMachineはこんな感じにしてみました。

stepFunctions:
  stateMachines:
    hellostepfunc1:
      name: myStateMachine
      definition:
        Comment: "A Hello World example of the Amazon States Language using an AWS Lambda Function"
        StartAt: HelloWorld1
        States:
          HelloWorld1:
            Type: Task
            Resource: "arn:aws:lambda:us-east-1:99999:function:sls-step-functions-example-dev-hello"
            Next: waitUsingSeconds
            Retry:
              - ErrorEquals:
                - HandledError
                IntervalSeconds: 1
                MaxAttempts: 2
                BackoffRate: 2
          waitUsingSeconds:
            Type: Wait
            Seconds: 10
            Next: Parallel
          Parallel:
            Type: Parallel
            Next: FinalState
            Branches:
            - StartAt: Wait 20s
              States:
                Wait 20s:
                  Type: Wait
                  Seconds: 20
                  End: true
            - StartAt: Pass
              States:
                Pass:
                  Type: Pass
                  Next: Wait 10s
                Wait 10s:
                  Type: Wait
                  Seconds: 10
                  End: true
          FinalState:
            Type: Task
            Resource: "arn:aws:lambda:us-east-1:999999:function:sls-step-functions-example-dev-final"
            End: true

これでデプロイすると、ちょっと複雑なワークフローに変わりました。

実行すると、ちゃんとfinalのLambdaの戻り値が出力されていることがわかります。

API Gatewayと連携させる

stateMachinesのname / definitionと同じ階層にeventsを記述することで、Step Functionsを起動するイベントを指定できます。

stepFunctions:
  stateMachines:
    hellostepfunc1:
      name: myStateMachine
      events:
        - http:
            path: hello
            method: GET
      definition:
        Comment: "A Hello World example of the Amazon States Language using an AWS Lambda Function"

デプロイされたAPIをコールすると、Step Functionsの実行が始まったことがレスポンスで返ってきます。

$ curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/hello | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   147  100   147    0     0    175      0 --:--:-- --:--:-- --:--:--   175
{
  "executionArn": "arn:aws:states:us-east-1:999999:execution:myStateMachine:de9e30fd-e989-11e7-b1e4-db09d1e378c9",
  "startDate": 1514216401.371
}

cronイベントなども設定可能ですので、ドキュメントをみていろいろトライしてみてください。

そのほか

Readmeにstate machineのサンプルがいろいろ紹介されてますので、そちらもぜひ触ってみてください。

  • ワークフローをMarkdownなどで書き出して整理
  • serverless.ymlでStateMachineにする
  • デプロイして使用する

というワークフローが個人的にしっくりきそうですので、機会があれば導入してみたいなと思います。

Comment