
プルリクエストを作ったタイミングで、AWS SAM のサンドボックス環境を作成する
こんにちは!クラウド事業本部コンサルティング部のたかくに(@takakuni_)です。
Step Functions のデプロイ管理、みなさんどのように運用していますでしょうか。
プルリクエストを作ったタイミングで、もう1つ環境一式が欲しくなったケースないでしょうか。
事の発端
とあるプロダクトで Step Functions を使うことになったのですが、ほとんどの Actions に Lambda を使うことが想定される
/sam local といったローカルでのテストが便利
/AWS Toolkit を使ったワークフローの定義のしやすさ
の観点から、AWS SAM を採用することにしました。
ステートマシンのイメージは次のとおりで、並列で動かす関数と集計関数の 2 種類に分かれます。
新機能の追加は並列処理内に関数を増やしていく運用をとっており、私は次の課題にぶつかりました。(主に 1 つのステートマシンを共有している部分に課題がありました。)
- 新機能の開発に次のステップを踏む必要がある
- 並列処理内の関数を開発し、ステートマシンにデプロイする必要がある
- 並列処理を実装した後にステートマシンを実行、テストデータを取得してから集計関数の実装が必要
- 1 つのステートマシンを複数人で共有している
- 互いの関数に依存があるわけではないが、心理的に統合テストした状態でステートマシンの反映をしたい
- 並列関数から、集計関数まで一気通貫で実装したい
そこでプルリクエストを作成したタイミングで、サンドボックス用ステートマシンを作成するワークフローファイルを作成しました。
ワークフロー
作成したワークフローは以下のとおりです。
name: Deploy-PR-Environment
on:
pull_request:
branches:
- develop
types: [opened, reopened, synchronize, closed]
paths:
- 'functions/**/*'
- 'layers/**/*'
- 'statemachine/statemachine.asl.json' # 適宜変更
- 'samconfig.toml'
- 'template.yaml'
env:
TEMPLATE_FILE: template.yaml
SAM_CLI_TELEMETRY: 0
permissions:
id-token: write
contents: write
pull-requests: write
jobs:
deploy:
runs-on: ubuntu-22.04
permissions:
id-token: write
contents: write
if: github.event.action != 'closed'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install AWS SAM CLI
uses: aws-actions/setup-sam@v2
with:
use-installer: true
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
aws-region: ap-northeast-1
role-session-name: SamDeploy
- name: SAM Deploy
run: |
# Replace invalid characters with hyphens in branch name for valid stack name
# CloudFormation stack name must match pattern: [a-zA-Z][-a-zA-Z0-9]*
STACK_NAME="$(echo ${{ github.head_ref }} | sed -E 's/[^a-zA-Z0-9]/-/g')"
# Check if stack exists and is in ROLLBACK_COMPLETE state
if aws cloudformation describe-stacks --stack-name "$STACK_NAME" 2>/dev/null | grep -q "ROLLBACK_COMPLETE"; then
echo "Stack is in ROLLBACK_COMPLETE state. Deleting it before redeploying..."
aws cloudformation delete-stack --stack-name "$STACK_NAME"
# Wait for stack deletion to complete
aws cloudformation wait stack-delete-complete --stack-name "$STACK_NAME"
fi
sam build
sam deploy \
--stack-name "$STACK_NAME" \
--no-confirm-changeset \
--no-fail-on-empty-changeset \
--capabilities CAPABILITY_NAMED_IAM
cleanup:
runs-on: ubuntu-22.04
permissions:
id-token: write
contents: write
if: github.event.action == 'closed'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install AWS SAM CLI
uses: aws-actions/setup-sam@v2
with:
use-installer: true
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
aws-region: ap-northeast-1
role-session-name: SamDelete
- name: Empty S3 Buckets and Delete Stack
run: |
# Replace invalid characters with hyphens in branch name for valid stack name
# CloudFormation stack name must match pattern: [a-zA-Z][-a-zA-Z0-9]*
STACK_NAME="$(echo ${{ github.head_ref }} | sed -E 's/[^a-zA-Z0-9]/-/g')"
# Get bucket names from CloudFormation outputs 適宜変更
BUCKET=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text)
# Empty the buckets if they exist and are not empty
if [ ! -z "$BUCKET" ]; then
echo "Emptying billing input bucket: $BUCKET"
aws s3 rm s3://$BUCKET --recursive
fi
# Delete the stack
aws cloudformation delete-stack --stack-name "$STACK_NAME"
# Wait for stack deletion to complete
aws cloudformation wait stack-delete-complete --stack-name "$STACK_NAME"
echo "Stack $STACK_NAME deleted successfully."
スタック名
スタック名はブランチの内容をもとに作成します。!Sub arn:aws:s3:::${AWS::StackName}
のように、リソースの命名規則にスタック名を埋め込む際に、あまりにも長いブランチ名の場合、文字数超過でエラーが発生しますので、ご注意ください。
- name: SAM Deploy
run: |
# Replace invalid characters with hyphens in branch name for valid stack name
# CloudFormation stack name must match pattern: [a-zA-Z][-a-zA-Z0-9]*
STACK_NAME="$(echo ${{ github.head_ref }} | sed -E 's/[^a-zA-Z0-9]/-/g')"
ROLLBACK_COMPLETE なスタックの存在をチェック
スタックをデプロイする前に ROLLBACK_COMPLETE
なスタックが存在するかどうかをチェックします。
構文エラーなど、何かしらのエラーが発生した場合、スタックはロールバックを試みます。初回デプロイ時にロールバックが完了したスタックは ROLLBACK_COMPLETE
状態になります。(ちなみに2回目以降のロールバックが完了したスタックは UPDATE_ROLLBACK_COMPLETE
状態になります。)
ROLLBACK_COMPLETE
なスタックは、再度スタックを削除するまたは、自動ロールバックを無効にしたデプロイが求められます。今回は、冪等性を担保したいため、前者のスタックを削除したデプロイを採用しました。
- name: SAM Deploy
run: |
# Check if stack exists and is in ROLLBACK_COMPLETE state
if aws cloudformation describe-stacks --stack-name "$STACK_NAME" 2>/dev/null | grep -q "ROLLBACK_COMPLETE"; then
echo "Stack is in ROLLBACK_COMPLETE state. Deleting it before redeploying..."
aws cloudformation delete-stack --stack-name "$STACK_NAME"
# Wait for stack deletion to complete
aws cloudformation wait stack-delete-complete --stack-name "$STACK_NAME"
fi
S3 バケットの削除(オプション)
S3 バケットや ECR リポジトリなどは中身(オブジェクトやコンテナイメージ)が入っている場合、削除できません。
そこで、スタックを削除する前に S3 の中身を空にする処理を組み込んでいます。S3 バケットとやりとりする必要があるアプリケーションの場合は、必要に応じて以下の処理を入れておきましょう。(バケットの情報は Output を通じてやりとりしています)
- name: Empty S3 Buckets and Delete Stack
run: |
# Replace invalid characters with hyphens in branch name for valid stack name
# CloudFormation stack name must match pattern: [a-zA-Z][-a-zA-Z0-9]*
STACK_NAME="$(echo ${{ github.head_ref }} | sed -E 's/[^a-zA-Z0-9]/-/g')"
# Get bucket names from CloudFormation outputs 適宜変更
BUCKET=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text)
# Empty the buckets if they exist and are not empty
if [ ! -z "$BUCKET" ]; then
echo "Emptying billing input bucket: $BUCKET"
aws s3 rm s3://$BUCKET --recursive
fi
S3 バケット内の処理を空にする Actions として cls3 もあげられます。集計処理など大量のオブジェクトを扱う部分ではこちらの方が、Actions の実行時間を短縮できるためオススメです。
CloudFormation の Delete Stack
sam delte では無くあえて、CloudFormation の delete-stack を採用しています。
これは、sam delete の場合、キャッシュ領域のコードまで削除されてしまうため、マージ後の sam deploy が遅くなってしまうのを防ぎたかったからです。
# Delete the stack
aws cloudformation delete-stack --stack-name "$STACK_NAME"
# Wait for stack deletion to complete
aws cloudformation wait stack-delete-complete --stack-name "$STACK_NAME"
echo "Stack $STACK_NAME deleted successfully."
キャッシュを利用した高速化は非常に有効ですが、不要なファイルが溜まり続けないよう、ライフサイクルルールを設定しておきましょう。
まとめ
「プルリクエストを作ったタイミングで、AWS SAM のサンドボックス環境を作成する」でした。
ステートマシンの変更量が少ない場合は、あまり刺さらないかもですが参考になれば幸いです。
クラウド事業本部コンサルティング部のたかくに(@takakuni_)でした!