AWS Step FunctionsをAWS SDK For Pythonから使ってみる #reinvent

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWS re:Invent 2016 Keynoteで発表された、ビジュアライズなワークフローを使用して、分散アプリケーションと Microservicesのコンポーネントを簡単にコーディネート出来るサービス『Step Functions』をAWS SDK for Python から使ってみます。

チュートリアル "Getting Started with AWS Step Functions" にある no-op な Pass ステートを利用したステートマシンを AWS SDK For Python から作成し、実行します。

Getting Started with AWS Step Functions - Developer Guide

AWS CLI向けは次の記事を参照下さい。

AWS Step FunctionsをAWS CLIから使ってみる #reinvent

1.ステートマシンの作成

ステートマシンの定義

"Type" が "Pass" の no-op なステートマシンを作成します。

step-functions-state-machine

JSON ベースの Amazon States Language でステートマシンを定義します。

{
  "Comment": "A Hello World example of the Amazon States Language using an AWS Lambda Function",
  "StartAt": "HelloWorld",
  "States": {
    "HelloWorld": {
      "Type": "Pass",
      "Result": "Hello World!",
      "End": true
    }
  }
}

ステートマシンの実行用 IAM ロール

ステートマシンの実行用に自動生成されたIAM Roleを指定します。 IAM Role の ARN を控えておいて下さい。

自動生成されたロールは以下のとおりです。 Step Function が Lambda を呼び出せるようになっていますが、今回は Lambda 連携は行いません。

Permissions

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": "*"
        }
    ]
}

Trust Relationships

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "states.eu-west-1.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

ステートマシンの作成

最後の CreateStateMachine API でステートマシンを作成します。

引数 意味
Name ステートマシン名。リージョン毎にユニーク
Definition ステートマシンのAmazon States Languageによる定義
RoleArn ステートマシンが利用するIAMロールのARN
import boto3

client = boto3.client('stepfunctions')
client.create_state_machine(
        name="foo",
        definition=open("definition.json").read(),
        roleArn="arn:aws:iam::123456789012:role/service-role/StatesExecutionRole-eu-west-1"
)

RESPONSE

{'ResponseMetadata': {'HTTPHeaders': {'content-length': '109',
   'content-type': 'application/x-amz-json-1.0',
   'x-amzn-requestid': 'd72f1e08-c500-11e6-a0eb-016b0ac9b948'},
  'HTTPStatusCode': 200,
  'RequestId': 'd72f1e08-c500-11e6-a0eb-016b0ac9b948',
  'RetryAttempts': 0},
 u'creationDate': datetime.datetime(2016, 12, 18, 10, 3, 28, 295000, tzinfo=tzlocal()),
 u'stateMachineArn': u'arn:aws:states:eu-west-1:123456789012:stateMachine:foo'}

作成したステートマシンを DescribeStateMachine API で確認しましょう。

describe 対象は ステートマシンの ARN を指定します。

client.describe_state_machine(stateMachineArn='arn:aws:states:eu-west-1:123456789012:stateMachine:foo')

RESPONSE

{'ResponseMetadata': {'HTTPHeaders': {'content-length': '518',
   'content-type': 'application/x-amz-json-1.0',
   'x-amzn-requestid': '1cf27650-c501-11e6-a642-43c1a53708be'},
  'HTTPStatusCode': 200,
  'RequestId': '1cf27650-c501-11e6-a642-43c1a53708be',
  'RetryAttempts': 0},
 u'creationDate': datetime.datetime(2016, 12, 18, 10, 2, 17, 132000, tzinfo=tzlocal()),
 u'definition': u'{\n  "Comment": "A Hello World example of the Amazon States Language using an AWS Lambda Function",\n  "StartAt": "HelloWorld",\n  "States": {\n    "HelloWorld": {\n      "Type": "Pass",\n      "Result": "Hello World!",\n      "End": true\n    }\n  }\n}\n',
 u'name': u'foo',
 u'roleArn': u'arn:aws:iam::123456789012:role/service-role/StatesExecutionRole-eu-west-1',
 u'stateMachineArn': u'arn:aws:states:eu-west-1:123456789012:stateMachine:foo',
 u'status': u'ACTIVE'}

ListSateMachines API を使うと、ステートマシン一覧を取得できます。

import boto3

client = boto3.client('stepfunctions')
client.list_state_machines()

RESPONSE

{'ResponseMetadata': {'HTTPHeaders': {'content-length': '265',
   'content-type': 'application/x-amz-json-1.0',
   'x-amzn-requestid': 'c60e66be-c4a1-11e6-a0c5-453e0e1f9c8f'},
  'HTTPStatusCode': 200,
  'RequestId': 'c60e66be-c4a1-11e6-a0c5-453e0e1f9c8f',
  'RetryAttempts': 0},
 u'stateMachines': [{u'creationDate': datetime.datetime(2016, 12, 17, 22, 41, 2, 537000, tzinfo=tzlocal()),
   u'name': u'foo',
   u'stateMachineArn': u'arn:aws:states:eu-west-1:123456789012:stateMachine:foo'},
  {u'creationDate': datetime.datetime(2016, 12, 17, 22, 37, 14, 22000, tzinfo=tzlocal()),
   u'name': u'foo2',
   u'stateMachineArn': u'arn:aws:states:eu-west-1:123456789012:stateMachine:foo'}]}

2.ステートマシンの実行

作成したステートマシンを実行します。

実行用入力データを JSON で用意します。

{
    "Comment": "Insert your JSON here"
}

ステートマシンは StartExecution API で呼び出します。

import boto3

client = boto3.client('stepfunctions')
client.start_execution(
**{
  'input' : open('input.json').read(),
  'stateMachineArn' : 'arn:aws:states:eu-west-1:123456789012:stateMachine:foo'
  }
)

RESPONSE

{u'startDate': datetime.datetime(2016, 12, 18, 10, 8, 10, 135000, tzinfo=tzlocal()), 'ResponseMetadata': {'RetryAttempts': 0, 'HTTPStatusCode': 200, 'RequestId': '7f297002-c501-11e6-aba3-974b2c19db6e', 'HTTPHeaders': {'x-amzn-requestid': '7f297002-c501-11e6-aba3-974b2c19db6e', 'content-length': '136', 'content-type': 'application/x-amz-json-1.0'}}, u'executionArn': u'arn:aws:states:eu-west-1:123456789012:execution:foo:28c88d3b-9d58-4d23-840d-34329fe1951e'}

本当は client.start_execution(input = open('input.json').read(), 'stateMachineArn' = 'XXX') のようにしたいところですが、input は Python のキーワードのため、引数で利用しようとするとシンタックスエラーになります。そのワークアラウンドとして、辞書形式で引数を渡します。

なお、インプットデータが不要なときは

client.start_execution(stateMachineArn='arn:aws:states:eu-west-1:123456789012:stateMachine:foo')

とスッキリ書けます。

引数 意味
StateMachineArn ステートマシンのARN
Name 実行名。リージョン毎にユニーク。省略すると、サーバー側がユニークな名前を付与。
Input ステートマシン実行用のインプット

実行結果を確認します。

import boto3

client = boto3.client('stepfunctions')
client.describe_execution(
  executionArn='arn:aws:states:eu-west-1:123456789012:execution:foo:1ef5b54a-e3d5-44c4-a83f-cdf8dd87059e'
)

RESPONSE

{'ResponseMetadata': {'HTTPHeaders': {'content-length': '346',
   'content-type': 'application/x-amz-json-1.0',
   'x-amzn-requestid': 'e916cbbc-c4a2-11e6-9ffd-2567445d8c02'},
  'HTTPStatusCode': 200,
  'RequestId': 'e916cbbc-c4a2-11e6-9ffd-2567445d8c02',
  'RetryAttempts': 0},
 u'executionArn': u'arn:aws:states:eu-west-1:123456789012:execution:foo:1ef5b54a-e3d5-44c4-a83f-cdf8dd87059e',
 u'input': u'{}',
 u'name': u'1ef5b54a-e3d5-44c4-a83f-cdf8dd87059e',
 u'output': u'"Hello World!"',
 u'startDate': datetime.datetime(2016, 12, 17, 22, 48, 37, 190000, tzinfo=tzlocal()),
 u'stateMachineArn': u'arn:aws:states:eu-west-1:123456789012:stateMachine:foo',
 u'status': u'SUCCEEDED',
 u'stopDate': datetime.datetime(2016, 12, 17, 22, 48, 37, 312000, tzinfo=tzlocal())}

"status": "SUCCEEDED" と実行が成功したことがわかります。 "output": "\"Hello World!\"" と JSON で定義したとおりのアウトプットとなっています。

AMC の下図にあるようなステートの遷移は GetExecutionHistory API で取得します。

step-functions-state-history

import boto3

client = boto3.client('stepfunctions')
client.get_execution_history(
  executionArn='arn:aws:states:eu-west-1:123456789012:execution:foo:1ef5b54a-e3d5-44c4-a83f-cdf8dd87059e',
  reverseOrder=False)
)

RESPONSE

{'ResponseMetadata': {'HTTPHeaders': {'content-length': '893',
   'content-type': 'application/x-amz-json-1.0',
   'x-amzn-requestid': '4cfc5fae-c4a3-11e6-8f47-99e5d3d2e683'},
  'HTTPStatusCode': 200,
  'RequestId': '4cfc5fae-c4a3-11e6-8f47-99e5d3d2e683',
  'RetryAttempts': 0},
 u'events': [{u'executionStartedEventDetails': {u'input': u'{}',
    u'roleArn': u'arn:aws:iam::123456789012:role/service-role/StatesExecutionRole-eu-west-1'},
   u'id': 1,
   u'previousEventId': 0,
   u'timestamp': datetime.datetime(2016, 12, 17, 22, 48, 37, 190000, tzinfo=tzlocal()),
   u'type': u'ExecutionStarted'},
  {u'id': 2,
   u'previousEventId': 0,
   u'stateEnteredEventDetails': {u'input': u'{}', u'name': u'HelloWorld'},
   u'timestamp': datetime.datetime(2016, 12, 17, 22, 48, 37, 312000, tzinfo=tzlocal()),
   u'type': u'PassStateEntered'},
  {u'id': 3,
   u'previousEventId': 2,
   u'stateExitedEventDetails': {u'name': u'HelloWorld',
    u'output': u'"Hello World!"'},
   u'timestamp': datetime.datetime(2016, 12, 17, 22, 48, 37, 312000, tzinfo=tzlocal()),
   u'type': u'PassStateExited'},
  {u'executionSucceededEventDetails': {u'output': u'"Hello World!"'},
   u'id': 4,
   u'previousEventId': 3,
   u'timestamp': datetime.datetime(2016, 12, 17, 22, 48, 37, 312000, tzinfo=tzlocal()),
   u'type': u'ExecutionSucceeded'}]}

ListExecutions API を使うと、ステートマシンの実行一覧を取得できます。

import boto3

client = boto3.client('stepfunctions')
client.list_executions(
  stateMachineArn='arn:aws:states:eu-west-1:123456789012:stateMachine:foo'
)

RESPONSE

{'ResponseMetadata': {'HTTPHeaders': {'content-length': '323',
   'content-type': 'application/x-amz-json-1.0',
   'x-amzn-requestid': '06dad61b-c502-11e6-962a-27b1fe024b21'},
  'HTTPStatusCode': 200,
  'RequestId': '06dad61b-c502-11e6-962a-27b1fe024b21',
  'RetryAttempts': 0},
 u'executions': [{u'executionArn': u'arn:aws:states:eu-west-1:123456789012:execution:foo:28c88d3b-9d58-4d23-840d-34329fe1951e',
   u'name': u'28c88d3b-9d58-4d23-840d-34329fe1951e',
   u'startDate': datetime.datetime(2016, 12, 18, 10, 8, 10, 135000, tzinfo=tzlocal()),
   u'stateMachineArn': u'arn:aws:states:eu-west-1:123456789012:stateMachine:foo',
   u'status': u'SUCCEEDED',
   u'stopDate': datetime.datetime(2016, 12, 18, 10, 8, 10, 182000, tzinfo=tzlocal())}]}

3. ステートマシンの削除

最後に作成したステートマシンを DeleteStateMachine API で削除します。

import boto3

client = boto3.client('stepfunctions')
client.delete_state_machine(
  stateMachineArn='arn:aws:states:eu-west-1:123456789012:stateMachine:foo'
)

RESPONSE

{'ResponseMetadata': {'HTTPHeaders': {'content-length': '2',
   'content-type': 'application/x-amz-json-1.0',
   'x-amzn-requestid': '578a70f2-c4a4-11e6-8779-db475dfb3ad2'},
  'HTTPStatusCode': 200,
  'RequestId': '578a70f2-c4a4-11e6-8779-db475dfb3ad2',
  'RetryAttempts': 0}}

削除したステートマシンを describe すると "status" : "DELETING" となっています。

import boto3

client = boto3.client('stepfunctions')
client.describe_state_machine(
  stateMachineArn='arn:aws:states:eu-west-1:123456789012:stateMachine:foo'
)

RESPONSE

{'ResponseMetadata': {'HTTPHeaders': {'content-length': '520',
   'content-type': 'application/x-amz-json-1.0',
   'x-amzn-requestid': '82cce048-c502-11e6-b890-d946f76fd0d2'},
  'HTTPStatusCode': 200,
  'RequestId': '82cce048-c502-11e6-b890-d946f76fd0d2',
  'RetryAttempts': 0},
 u'creationDate': datetime.datetime(2016, 12, 18, 10, 14, 58, 828000, tzinfo=tzlocal()),
 u'definition': u'{\n  "Comment": "A Hello World example of the Amazon States Language using an AWS Lambda Function",\n  "StartAt": "HelloWorld",\n  "States": {\n    "HelloWorld": {\n      "Type": "Pass",\n      "Result": "Hello World!",\n      "End": true\n    }\n  }\n}\n',
 u'name': u'foo',
 u'roleArn': u'arn:aws:iam::123456789012:role/service-role/StatesExecutionRole-eu-west-1',
 u'stateMachineArn': u'arn:aws:states:eu-west-1:123456789012:stateMachine:foo',
 u'status': u'DELETING'}

しばらく待つと、ステートマシンの削除が完了します。

削除完了後に DescribeStateMachine API を実行すると、次のように StateMachineDoesNotExist エラーが発生します。

import boto3

client = boto3.client('stepfunctions')
client.describe_state_machine(
  stateMachineArn='arn:aws:states:eu-west-1:123456789012:stateMachine:foo'
)

RESPONSE

ClientError: An error occurred (StateMachineDoesNotExist) when calling the DescribeStateMachine operation: State Machine Does Not Exist: 'arn:aws:states:eu-west-1:123456789012:stateMachine:foo'

まとめ

AWS Step Functions を AWS SDK for Python から使う方法を紹介しました。 何かのイベントをトリガーに Lambda 関数を呼び出し、Lambda 内から AWS Step Functions を呼び出すようなケースでは有用ではないかと思います。

参考リンク