見出し画像

[AWS]Step FunctionsをIaC管理ByTerraform


概要

以前の記事でStep Functionsの管理をIaC化(terraform)したことについてになります。AWS上で動いていたStep Functionsは当時50以上存在していました。ただし各Step Functionsは本番環境と検証環境で分かれておらず、ACLに変更を加える場合は全環境に影響がある状態でした。変更方法や新規作成方法もStep FunctionsのWorkflowStudioで直接変更や新規作成していたので変更履歴を追うことも難しく、デグレしたこともあったそうです。そのため要件としてはASLのリポジトリ管理とIaC化に伴っての安全なデプロイが求めらていました。

既存のStep Functionsのリストを抽出

まずはTerraform管理下におくため、既存で稼働しているStep Functionsをリスト化する必要がありました。AWS CLIでリストファイルに出力します。

aws stepfunctions list-state-machines --query 'stateMachines[].[name,stateMachineArn]' --output text > work/stepfunctions.list

全てのStep Functionsをjsonで抽出 

リストファイルをインプットとしてjsonファイルに出力します。実行前に../../src/awsディレクトリを作成しておきます。

while read Target  
do
_STATENAME=$(echo $Target | awk '{print $1}')
_DEFINITION=$(aws stepfunctions describe-state-machine --state-machine-arn arn:aws:states:ap-northeast-1:XXXXXXXXX:stateMachine:${_STATENAME} --query definition --output text) 
echo -n ${_DEFINITION} > ../../src/aws/${_STATENAME}.json

done < work/stepfunctions.list

ここまででTerraformにインプットする準備としては完了です。

Terraform管理化にする

tfファイルを準備

サンプルコード stepfunctionsの公式モジュールを利用します。


data "aws_caller_identity" "current" {}

locals {
  src_dir = "../../src/aws"
}


module "step_function" {
   source = "terraform-aws-modules/step-functions/aws"
  
  for_each = {
    "HelloWorld" = {
      role_arn = "arn:aws:iam::XXXXXXXXXXXX:role/service-role/StepFunctions-role"
    }
    }
    "TestStateMachine" = {
      role_arn = "arn:aws:iam::XXXXXXXXXXXX:role/service-role/StepFunctions-role"
    }

  }

  name =  each.key 
  definition = templatefile("${local.src_dir}/${each.key}.json", {
  })
  type = "STANDARD"


  # IAMロールは作成しない。
  create_role       = false
  use_existing_role = true # すでにあるIAMロールを利用する。
  role_arn          = each.value.role_arn

  tags = {
    Module = "Stepfunctionsv1"
  }


}

output "step_function-info" {
  value = ({ for k, v in module.step_function : k => {
    "01_ARN" = try(v.state_machine_arn, null)
  } })
}

このまま実行するとHelloWorldとTestStateMachineというStep Functionsが新規作成されるだけなのでインポートします。

Terraform Import

Terraform 1.5からリリースされたimport blockを使ってもよかったのですが、今回は一つ一つ確認しながら進めたかったので手動で実行しました。(とはいえスクリプト化してましたが。)

  • import コマンドサンプル 対象分繰り返す

terraform import module.step_function["HelloWorld"].aws_sfn_state_machine.this[0] arn:aws:states:ap-northeast-1:XXXXXXXX:stateMachine:HelloWorld

差分がないか確認

DryRunを実行して差分がないかを確認します。差分があれば定義ファイルを修正して合わせるか差分内容が影響ないものであれば差分なしとします。

terraform plan


CI/CD設定

ローカルからのTerraform実行だと開発メンバーの環境 Verison差異などの理由によってよからぬことが発生する可能性があります。そのためGitHubActionsでDryRunとApplyを実行できるようにします。

OIDC設定

GitHubActionsからAWS権限で実行できるようにIAMロールを作成します。これもTerraformで作成します。github_org、github_repositories、iam_role_nameは環境に合わせて変更します。thumbprintの箇所は値入れなくても良くなったかもしれません。


module "oidc-with-github-actions" {
  source = "thetestlabs/oidc-with-github-actions/aws"

  github_org = "GitHubのオーガニゼーション名"
  github_repositories = [
    "対象リポジトリ名1",
    "対象リポジトリ名2"
  ]

  iam_role_name        = "Sample_OIDC_Role"
  iam_role_description = "Enable GitHub OIDC access"
  max_session_duration = 7200 // 2 hours
  iam_role_policy      = "AWSStepFunctionsFullAccess"
  iam_role_path        = "/security/"
  thumbprint_list = [
    "6938fd4d98bab03faadb97b34396831e3780aea1",
    "1c58a3a8518e8759bf075b76b750d4f2df264fcd"
  ]

}


発行したIAMロールをGitHubで設定

  1. 対象のリポジトリのURLにアクセス

  2. Setting → Actions → [New repository secret]

  3. Name: AWS_ROLE_ARN
    Secret: OIDCで発行したIAMのARN


GitHubActionsのymlを準備

  • mainブランチへのPullRequestが作成されたらDryRunが実行されて内容をコメント追記します。

  • mainブランチへマージされたらApplyが実行され実行内容をコメント追記されます。

  • Slack通知するために事前にImcommingWebhookを発行しIAM Role同様シークレットに登録しておきます。

  • Step Functionsのmain.tfファイルは terraform/environments/stepfunctions に配置してある前提になっています。

  • 他ファイルが変更になっても無駄にGitHubActionsが実行されないように明示的にstepfunctions用のファイルが変更された場合というトリガーを組み込んでいます。

name: "StepfunctionsByTerraform"

on:
  pull_request:
    branches:
      - main

    types: [opened, synchronize, reopened, closed]
    paths:
      - "terraform/environments/stepfunctions/main.tf"
      - "terraform/src/aws/*.json"
  workflow_dispatch:
#-------------------------#
# 環境変数
#-------------------------#
env:
  SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
  SLACK_USERNAME: GitHubActions
  SLACK_CHANNEL: terraform_notifications
  SLACK_ICON: https://octodex.github.com/images/Robotocat.png
  ENV_DIRCTORY: stepfunctions



permissions:
  id-token: write
  contents: write
  pull-requests: write

jobs:
  terraform:
    name: "TerraformCI(stepfunctions)"
    runs-on: ubuntu-latest
    environment: stepfunctions

    # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
    defaults:
      run:
        shell: bash

    steps:
      # Checkout the repository to the GitHub Actions runner
      - name: Checkout
        uses: actions/checkout@v3

      - name: Configure AWS credentials from stepfunctions account
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ap-northeast-1
      - name: setup tfnotify
        run: |
          sudo curl -fL -o tfnotify.tar.gz https://github.com/mercari/tfnotify/releases/download/v0.7.0/tfnotify_linux_amd64.tar.gz
          sudo tar -C /usr/bin -xzf ./tfnotify.tar.gz

      - name: setup tfcmt
        env:
          TFCMT_VERSION: v3.4.1
        run: |
          wget "https://github.com/suzuki-shunsuke/tfcmt/releases/download/${TFCMT_VERSION}/tfcmt_linux_amd64.tar.gz" -O /tmp/tfcmt.tar.gz
          tar xzf /tmp/tfcmt.tar.gz -C /tmp
          mv /tmp/tfcmt /usr/local/bin
          tfcmt --version

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.5.5
      # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.
      - name: Terraform Init
        run: terraform -chdir=terraform/environments/$ENV_DIRCTORY init

      # Checks that all Terraform configuration files adhere to a canonical format
      - name: Terraform Format
        run: terraform -chdir=terraform/environments/$ENV_DIRCTORY fmt -check

      # Generates an execution plan for Terraform
      - name: Terraform Plan
        run: |
          tfcmt plan -patch -- terraform -chdir=terraform/environments/$ENV_DIRCTORY plan -no-color -input=false
        # On push to main, build or change infrastructure according to Terraform configuration files
        # Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks
      - name: Terraform Apply
        if: github.event.pull_request.merged == true
        run: |
          tfcmt plan -patch -- terraform -chdir=terraform/environments/$ENV_DIRCTORY apply -auto-approve -no-color -input=false



      #-- Slack通知 --#
      # 成功

      - name: Slack Notification on Success
        if: ${{ success() }}
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_TITLE: Deploy Success
          SLACK_COLOR: good
          # 失敗
      - name: Slack Notification on Failure
        if: ${{ failure() }}
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_TITLE: Deploy Failure
          SLACK_COLOR: danger

実際の運用

Step Functionsで利用する定義ファイルはASLというjsonライクなファイルですが、ASLファイルはわかりにくいため、検証環境のStep Functionsをworkflow Studio で編集、稼働確認後にその定義ファイルをコピペしPullRequestを作成するという運用フローにしています。二度手間感は否めないのですが、環境を分離することができいきなり本番に手をいれるということがなく事故率低減にもつながっているのではと考えています。



この記事が気に入ったらサポートをしてみませんか?