目次
はじめに
こんにちは、ハヤシです。
今回は、冗長化された各インスタンスを順番にLBから切り離してスペック変更する一連の作業を
手動で実行するには手間もかかるしヒューマンエラーも発生しかねない、、、
ということでStep Functionsを用いて自動化させてみました。
Step Functionsとは
複数のAWSサービスや処理を順番に自動実行(ワークフロー化)できるサービスです。
処理の流れを状態機械(State Machine)として定義し、実行時は各ステップの成功/失敗や入力/出力をトレースすることが可能です。
待機・分岐・並列などの制御が標準で使え、AWS SDK統合により多くのAWS APIを直接呼べるので、ちょっとした自動化はLambda不要で完結します。
やってみた

上の画像のようにロードバランサーによって冗長化されたEC2を以下の流れでStep Functionsで自動で実行させます。
1.EC2①をLBから切り離す
2.EC2①を停止させ、指定したスペックへ変更させた後に起動
3.EC2①をLBへ登録
4.EC2②をLBから切り離す
5.EC2②を停止させ、指定したスペックへ変更させた後に起動
6.EC2②をLBへ登録
ステートマシンの作成
では、早速始めます。
まずStep Functionsを新たに作成します。

「自分で作成する」を選択してください。

適宜名前を設定しステートマシンを作成します。

ワークフローの作成
ワークフローの作成には「デザインモード」と「コードモード」があります。
-
デザインモード
- 画面でポチポチ操作しながら直感的に設定が可能 -
コードモード
- JSON形式でより詳細なフローを作成可能
今回私は生成AIを活用しながら作成したので、コードモードを使用しました。コードの内容は以下です。
{
"Comment": "Sequentially change EC2 instance type with LB drain and health checks (no ASG)",
"StartAt": "ForEachInstance",
"States": {
"ForEachInstance": {
"Type": "Map",
"ItemsPath": "$.InstanceIds",
"MaxConcurrency": 1,
"Parameters": {
"InstanceId.$": "$$.Map.Item.Value",
"TargetGroupArn": "{対象TGのARN}",
"NewInstanceType.$": "$.NewInstanceType",
"AutomationAssumeRole.$": "$.AutomationAssumeRole",
"PollSeconds.$": "$.PollSeconds"
},
"Iterator": {
"StartAt": "Deregister",
"States": {
"Deregister": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:elasticloadbalancingv2:deregisterTargets",
"Parameters": {
"TargetGroupArn.$": "$.TargetGroupArn",
"Targets": [
{ "Id.$": "$.InstanceId" }
]
},
"Next": "WaitDraining"
},
"WaitDraining": {
"Type": "Wait",
"SecondsPath": "$.PollSeconds",
"Next": "CheckUnused"
},
"CheckUnused": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:elasticloadbalancingv2:describeTargetHealth",
"Parameters": {
"TargetGroupArn.$": "$.TargetGroupArn",
"Targets": [
{ "Id.$": "$.InstanceId" }
]
},
"ResultPath": "$.TH",
"Next": "IsUnused"
},
"IsUnused": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.TH.TargetHealthDescriptions[0].TargetHealth.State",
"StringEquals": "unused",
"Next": "StartChangeType"
}
],
"Default": "WaitDraining"
},
"StartChangeType": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:ssm:startAutomationExecution",
"Parameters": {
"DocumentName": "AWS-ChangeInstanceType",
"Parameters": {
"InstanceId.$": "States.Array($.InstanceId)",
"InstanceType.$": "States.Array($.NewInstanceType)",
"AutomationAssumeRole.$": "States.Array($.AutomationAssumeRole)"
}
},
"ResultPath": "$.Automation",
"Next": "WaitAutomation"
},
"WaitAutomation": {
"Type": "Wait",
"SecondsPath": "$.PollSeconds",
"Next": "GetAutomation"
},
"GetAutomation": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:ssm:getAutomationExecution",
"Parameters": {
"AutomationExecutionId.$": "$.Automation.AutomationExecutionId"
},
"ResultPath": "$.AutomationDetail",
"Next": "AutomationDone?"
},
"AutomationDone?": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.AutomationDetail.AutomationExecution.Status",
"StringEquals": "Success",
"Next": "Register"
},
{
"Variable": "$.AutomationDetail.AutomationExecution.Status",
"StringEquals": "Failed",
"Next": "Fail"
}
],
"Default": "WaitAutomation"
},
"Register": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:elasticloadbalancingv2:registerTargets",
"Parameters": {
"TargetGroupArn.$": "$.TargetGroupArn",
"Targets": [
{ "Id.$": "$.InstanceId" }
]
},
"Next": "WaitHealthy"
},
"WaitHealthy": {
"Type": "Wait",
"SecondsPath": "$.PollSeconds",
"Next": "CheckHealthy"
},
"CheckHealthy": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:elasticloadbalancingv2:describeTargetHealth",
"Parameters": {
"TargetGroupArn.$": "$.TargetGroupArn",
"Targets": [
{ "Id.$": "$.InstanceId" }
]
},
"ResultPath": "$.TH2",
"Next": "IsHealthy"
},
"IsHealthy": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.TH2.TargetHealthDescriptions[0].TargetHealth.State",
"StringEquals": "healthy",
"Next": "DoneOne"
}
],
"Default": "WaitHealthy"
},
"DoneOne": { "Type": "Succeed" },
"Fail": { "Type": "Fail", "Error": "ChangeInstanceTypeFailed" }
}
},
"ResultPath": "$.Result",
"End": true
}
}
}
設定タブでは適宜実行ロールを設定してください。

実行してみる
それでは早速作成したワークフローを実行してみます。

以下の通り入力して実行してみると...
{
"InstanceIds": [
"{EC2①のインスタンスID}",
"{EC2②のインスタンスID}"
],
"NewInstanceType": "{変更したいインスタンスタイプ}",
"AutomationAssumeRole": "{対象ロールのARN}",
"PollSeconds": 10
}

こんな感じで今何をやっているのかが視覚化されて、想定通りに片方ずつLBから切り離して順番にスペック変更が実行されました!

Step Functionsでは各フローで「ヘルスチェックが問題なければ」「インスタンスが起動したら」などと状態のチェックを挟むことができるので、安全に進めることができて嬉しいですね。
最後に
ということで、初めてStep Functionsを触ってみましたがうまく活用すればより効率UPに繋がりそうで、かなり好みのサービスでした。
より複雑なワークフローも簡単に自動化できそうでいいですね。
以上、最後までありがとうございました。
新米エンジニア