AWS Step Functions を Terraform 管理してみる

開幕宣言

この記事は estie 夏のブログ祭りの1日目の記事です。

8月ですね 🍉

今年は梅雨明けが早く、でもそのあとに雨が続いたり、またご時世もあったりしますが皆様いかがお過ごしでしょうか。

本格的な夏が始まったということで、estie 夏のブログ祭りを開催します!! 🏄🏄‍♀️🏄‍♂️

エンジニア、非エンジニア含め平日毎日ブログを更新していきます。テクニカルな話、カルチャーな話などなど更新されていきますので、皆さんチェックしてくださいね!

はじめに

普段はRuby on Rails や Python を使って開発をしている、アプリケーションエンジニアのえりりんです。

以前 Terraform でアプリの設定を変更する記事を書きました。

記事はこちら

アプリケーションエンジニアが Terraform 触ってみた|えりりん|note

あれから半年強経ってだいぶ Terraform に慣れてきたので1サービス管理できるようになった記事を書いて見ます。

管理対象の作成

AWS Step Functions というサービスはご存知でしょうか?

そうです。ワークフローサービスです。

処理の順番や条件を定義しておくと、再実行やタイムアウトを管理しながら実行してくれるものです。

aws.amazon.com

AWS Management Console 上のGUIから以下のように処理したいサービスのパーツを順番に組み立てて設定ができます。

AWS Step Functions ワークフロー

この組み立てた内容は JSON で定義されます。

AWS Step Functions JSON

では、これを Terraform で書いたらどうなるのでしょうか。

Terraform 編

今回書くコードは以下に push してありますので、よろしければ参考にしてみてください。

github.com

まずは repository を作成して Terraform が使える準備をします。

Terraform Registry

こちらを参考に書いていきます。

main.tf

erraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region = "ap-northeast-1"
}

terraform {
  backend "s3" {
    bucket = "stepfunction-tf-state"
    key    = "test.tfstate"
    region = "ap-northeast-1"
  }
}

初期化します。

terraform init

AWS Step Functions を定義するリソースを用意します (23行目〜25行目)。

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region = "ap-northeast-1"
}

terraform {
  backend "s3" {
    bucket = "stepfunction-tf-state"
    key    = "test.tfstate"
    region = "ap-northeast-1"
  }
}

resource "aws_sfn_state_machine" "sfn_state_machine" {

}

続いて AWS Management Console で行った設定を import します。

リソースによって import の仕方が違うので注意です。

AWS Step Functions の場合はどうかというと、以下のページを参照します。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sfn_state_machine

一番下に import 時のコマンドが書いてありますね。

State Machines can be imported using the arn

とのことなので arn を指定して import します。

terraform import aws_sfn_state_machine.sfn_state_machine arn:aws:states:ap-northeast-1:XXXXXX:stateMachine:HelloWorld

import 成功です!🎉

terraform import success

main.tf の中身を埋めていきます。

state file から中身を参照しましょう。

terraform state show aws_sfn_state_machine.sfn_state_machine

# aws_sfn_state_machine.sfn_state_machine:
resource "aws_sfn_state_machine" "sfn_state_machine" {
    arn           = "arn:aws:states:ap-northeast-1:
    ...
    ...
    ...
    ...
    ...
    definition    = jsonencode(
        {
            Comment = "A Hello World example demonstrating various state types of the Amazon States Language"
            StartAt = "Pass"
            States  = {
                "Hello World"          = {
                    End  = true
                    Type = "Pass"
                }
                "Hello World example?" = {
                    Choices = [
                        {
                            BooleanEquals = true
                            Next          = "Yes"
                            Variable      = "$.IsHelloWorldExample"
                        },
                        {
                            BooleanEquals = false
                            Next          = "No"
                            Variable      = "$.IsHelloWorldExample"
                        },
                    ]
                    Comment = "A Choice state adds branching logic to a state machine. Choice rules can implement 16 different comparison operators, and can be combined using And, Or, and Not"
                    Default = "Yes"
                    Type    = "Choice"
                }
                No                     = {
                    Cause = "Not Hello World"
                    Type  = "Fail"
                }
                "Parallel State"       = {
                    Branches = [
                        {
                            StartAt = "Hello"
                            States  = {
                                Hello = {
                                    End  = true
                                    Type = "Pass"
                                }
                            }
                        },
                        {
                            StartAt = "World"
                            States  = {
                                World = {
                                    End  = true
                                    Type = "Pass"
                                }
                            }
                        },
                    ]
                    Comment  = "A Parallel state can be used to create parallel branches of execution in your state machine."
                    Next     = "Hello World"
                    Type     = "Parallel"
                }
                Pass                   = {
                    Comment = "A Pass state passes its input to its output, without performing work. Pass states are useful when constructing and debugging state machines."
                    Next    = "Hello World example?"
                    Type    = "Pass"
                }
                "Wait 3 sec"           = {
                    Comment = "A Wait state delays the state machine from continuing for a specified time."
                    Next    = "Parallel State"
                    Seconds = 3
                    Type    = "Wait"
                }
                Yes                    = {
                    Next = "Wait 3 sec"
                    Type = "Pass"
                }
            }
        }
    )
  }

と表示されるのでこの内容をそのまま main.tf に貼ります (24行目〜109行目)。

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region = "ap-northeast-1"
}

terraform {
  backend "s3" {
    bucket = "stepfunction-tf-state"
    key    = "test.tfstate"
    region = "ap-northeast-1"
  }
}

resource "aws_sfn_state_machine" "sfn_state_machine" {
  definition    = jsonencode(
    {
      Comment = "A Hello World example demonstrating various state types of the Amazon States Language"
      StartAt = "Pass"
      States  = {
        "Hello World"          = {
          End  = true
          Type = "Pass"
        }
        "Hello World example?" = {
          Choices = [
            {
              BooleanEquals = true
              Next          = "Yes"
              Variable      = "$.IsHelloWorldExample"
            },
            {
              BooleanEquals = false
              Next          = "No"
              Variable      = "$.IsHelloWorldExample"
            },
          ]
          Comment = "A Choice state adds branching logic to a state machine. Choice rules can implement 16 different comparison operators, and can be combined using And, Or, and Not"
          Default = "Yes"
          Type    = "Choice"
        }
        No                     = {
          Cause = "Not Hello World"
          Type  = "Fail"
        }
        "Parallel State"       = {
          Branches = [
            {
              StartAt = "Hello"
              States  = {
                Hello = {
                  End  = true
                  Type = "Pass"
                }
              }
            },
            {
              StartAt = "World"
              States  = {
                World = {
                  End  = true
                  Type = "Pass"
                }
              }
            },
          ]
          Comment  = "A Parallel state can be used to create parallel branches of execution in your state machine."
          Next     = "Hello World"
          Type     = "Parallel"
        }
        Pass                   = {
          Comment = "A Pass state passes its input to its output, without performing work. Pass states are useful when constructing and debugging state machines."
          Next    = "Hello World example?"
          Type    = "Pass"
        }
        "Wait 3 sec"           = {
          Comment = "A Wait state delays the state machine from continuing for a specified time."
          Next    = "Parallel State"
          Seconds = 3
          Type    = "Wait"
        }
        Yes                    = {
          Next = "Wait 3 sec"
          Type = "Pass"
        }
      }
    }
  )
  name          = "HelloWorld"
  tags          = {}
  tags_all      = {}
  type          = "STANDARD"

  logging_configuration {
    include_execution_data = false
    level                  = "OFF"
  }

  tracing_configuration {
    enabled = false
  }
}

これで AWS Step Functions を Terraform 管理することができました!

しかし、AWS Step Functions の定義の JSON 部分もうちょっと綺麗に書けないかなと考えました。

リファクタ編

Terraform にはいくつかの function がありますが、その中に templatefile という function があります。

公式ドキュメントはこちら

これを使って JSON 部分を別ファイルに切り出してみましょう。

JSON部分を別ファイルにします。

definition.tftpl

{
  "Comment": "A Hello World example demonstrating various state types of the Amazon States Language",
  "StartAt": "Pass",
  "States": {
    "Pass": {
      "Comment": "A Pass state passes its input to its output, without performing work. Pass states are useful when constructing and debugging state machines.",
      "Type": "Pass",
      "Next": "Hello World example?"
    },
    "Hello World example?": {
      "Comment": "A Choice state adds branching logic to a state machine. Choice rules can implement 16 different comparison operators, and can be combined using And, Or, and Not",
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.IsHelloWorldExample",
          "BooleanEquals": true,
          "Next": "Yes"
        },
        {
          "Variable": "$.IsHelloWorldExample",
          "BooleanEquals": false,
          "Next": "No"
        }
      ],
      "Default": "Yes"
    },
    "Yes": {
      "Type": "Pass",
      "Next": "Wait 3 sec"
    },
    "No": {
      "Type": "Fail",
      "Cause": "Not Hello World"
    },
    "Wait 3 sec": {
      "Comment": "A Wait state delays the state machine from continuing for a specified time.",
      "Type": "Wait",
      "Seconds": 3,
      "Next": "Parallel State"
    },
    "Parallel State": {
      "Comment": "A Parallel state can be used to create parallel branches of execution in your state machine.",
      "Type": "Parallel",
      "Next": "Hello World",
      "Branches": [
        {
          "StartAt": "Hello",
          "States": {
            "Hello": {
              "Type": "Pass",
              "End": true
            }
          }
        },
        {
          "StartAt": "World",
          "States": {
            "World": {
              "Type": "Pass",
              "End": true
            }
          }
        }
      ]
    },
    "Hello World": {
      "Type": "Pass",
      "End": true
    }
  }
}

main.tf で上記のファイルを読むようにします (24行目〜27行目)。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region = "ap-northeast-1"
}

terraform {
  backend "s3" {
    bucket = "stepfunction-tf-state"
    key    = "test.tfstate"
    region = "ap-northeast-1"
  }
}

resource "aws_sfn_state_machine" "sfn_state_machine" {
  definition    = templatefile(
    "definition.tftpl",
    {}
  )
  name          = "HelloWorld"
  role_arn      = "arn:aws:iam::XXX:role/service-role/StepFunctions-HelloWorld-role"
  tags          = {}
  tags_all      = {}
  type          = "STANDARD"

  logging_configuration {
    include_execution_data = false
    level                  = "OFF"
  }

  tracing_configuration {
    enabled = false
  }
}

外部ファイルに変数を渡すこともできます。

main.tf(抜粋)

resource "aws_sfn_state_machine" "sfn_state_machine" {
  definition    = templatefile(
    "definition.tftpl",
    {
      wait_sec = 5
    }
  )
  name          = "HelloWorld"
  role_arn      = "arn:aws:iam::XXX:role/service-role/StepFunctions-HelloWorld-role"
  tags          = {}
  tags_all      = {}
  type          = "STANDARD"

  logging_configuration {
    include_execution_data = false
    level                  = "OFF"
  }

  tracing_configuration {
    enabled = false
  }
}

definition.tftpl(抜粋)

"Yes": {
  "Type": "Pass",
  "Next": "Wait ${wait_sec} sec"
},

上記のように templatefile を用いることにより wait_sec の値だけ変えた AWS Step Functions を複数作成するなど、同一ファイルを用いて複数のインフラを用意する保守性の高い Terraform コードが書けます!

まとめ

AWS Management Console で作成した AWS Step Functions を Terraform で管理し、種類を増やしたい要望にも耐えられるコードが書けました!

2日目は『estieの開発チームは、ビジネスメンバーと共に価値創出する』です。 お楽しみに✨✨✨

© 2019- estie, inc.