AWS Fault Injection Simulatorでスポットインスタンスの中断ができるようになりました

2021.10.23

「グレースフルシャットダウンしてますか?」

中山です

AWS Fault Injection Simulator(以下、FIS)でスポットインスタンスの中断を再現できるようになりました。

AWS Fault Injection Simulator now injects Spot Instance Interruptions

これでスポットインスタンス導入時のテストが簡単に行えますね!(Auto Recoveryのテストもできるようになりたいなーチラッ)

ということで試してみます。

事前準備

まずは以下の事前準備を行います。

  • インスタンスプロファイルの作成(Run commandによるインスタンメタデータの確認を行うため)
  • スポットインスタンスの作成
  • SNS Topic / Subsctiptionの作成(Event Bridge経由で通知を受信するため)
  • Event ruleの作成

インスタンスプロファイルの作成

中断させるスポットインスタンスで利用するインスタンスプロファイルを作成します。 FISを利用するために必要なわけではなく、インスタンスメタデータで中断通知を受信できているかをSSM Run Commandで確認するためです。

ROLE_NAME="fis-instance-role"
TRUST_POLICY_FILE_NAME='Trust-Policy.json'

cat << EOF > ${TRUST_POLICY_FILE_NAME}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "ec2.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
EOF

aws iam create-role \
    --role-name ${ROLE_NAME} \
    --assume-role-policy-document file://${TRUST_POLICY_FILE_NAME}
{
    "Role": {
        "Path": "/",
        "RoleName": "fis-instance-role",
        "RoleId": "AROAXS3RGICA37W6L5MTY",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/fis-instance-role",
        "CreateDate": "2021-10-23T12:12:55+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": [
                            "ec2.amazonaws.com"
                        ]
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}

併せてインスタンスプロファイルを作成します。

ROLE_NAME="fis-instance-role"

aws iam create-instance-profile \
    --instance-profile-name ${ROLE_NAME}
{
    "InstanceProfile": {
        "Path": "/",
        "InstanceProfileName": "fis-instance-role",
        "InstanceProfileId": "AIPAXS3RGICAZNCQDIVOF",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:instance-profile/fis-instance-role",
        "CreateDate": "2021-10-23T12:13:20+00:00",
        "Roles": []
    }
}

インスタンスプロファイルにロールを割り当てます。

ROLE_NAME="fis-instance-role"

aws iam add-role-to-instance-profile \
    --role-name ${ROLE_NAME} \
    --instance-profile-name ${ROLE_NAME}

SSM Run commandを実行するための権限を付与します。

ROLE_NAME="fis-instance-role"

aws iam attach-role-policy \
    --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM \
    --role-name ${ROLE_NAME}

スポットインスタンスの作成

スポットインスタンスを作成します。 インスタンスはデフォルトVPC上に作成し、デフォルトのセキュリティグループを割り当てることにします(SSMのエンドポイントにアクセスできる想定)。

SPECIFICATION_FILE_NAME="specification.json"
IAM_INSTANCE_PROFILE_ARN="arn:aws:iam::XXXXXXXXXXXX:instance-profile/fis-instance-role"
DEFAULT_VPC_ID="vpc-0e3f6fef1f710c869"

cat << EOF > ${SPECIFICATION_FILE_NAME}
{
  "ImageId": $(aws ssm get-parameters --names /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 --query Parameters[0].Value),
  "KeyName": "tokyo-nakayama.nobuhiro",
  "SecurityGroupIds": [ $(aws ec2 describe-security-groups --filters Name=vpc-id,Values=${DEFAULT_VPC_ID} Name=group-name,Values=default --query SecurityGroups[0].GroupId) ],
  "InstanceType": "c5.large",
  "Placement": {
    "AvailabilityZone": $(aws ec2 describe-availability-zones --query AvailabilityZones[0].ZoneName)
  },
  "IamInstanceProfile": {
      "Arn": "${IAM_INSTANCE_PROFILE_ARN}"
  }
}
EOF

aws ec2 request-spot-instances \
    --spot-price "0.107" \
    --instance-count 1 \
    --type "one-time" \
    --launch-specification file://${SPECIFICATION_FILE_NAME}
{
    "SpotInstanceRequests": [
        {
            "CreateTime": "2021-10-23T12:23:27+00:00",
            "LaunchSpecification": {
                "SecurityGroups": [
                    {
                        "GroupName": "default",
                        "GroupId": "sg-0db2403949c9bdd77"
                    }
                ],
                "IamInstanceProfile": {
                    "Arn": "arn:aws:iam::XXXXXXXXXXXX:instance-profile/fis-instance-role"
                },
                "ImageId": "ami-0701e21c502689c31",
                "InstanceType": "c5.large",
                "KeyName": "tokyo-nakayama.nobuhiro",
                "Placement": {
                    "AvailabilityZone": "ap-northeast-1a"
                },
                "SubnetId": "subnet-09166ccaa28459f22",
                "Monitoring": {
                    "Enabled": false
                }
            },
            "ProductDescription": "Linux/UNIX",
            "SpotInstanceRequestId": "sir-w6kp8j2p",
            "SpotPrice": "0.107000",
            "State": "open",
            "Status": {
                "Code": "pending-evaluation",
                "Message": "Your Spot request has been submitted for review, and is pending evaluation.",
                "UpdateTime": "2021-10-23T12:23:27+00:00"
            },
            "Type": "one-time",
            "InstanceInterruptionBehavior": "terminate"
        }
    ]
}

スポットインスタンスのリクエストが完了したら、作成されたインスタンスIDを確認します。

SPOT_REQUEST_ID="sir-w6kp8j2p"

aws ec2 describe-spot-instance-requests \
    --filters Name=spot-instance-request-id,Values=${SPOT_REQUEST_ID} \
    --query SpotInstanceRequests[0].InstanceId
"i-0ab35af721ea70bc3"

SNS Topic / Subsctiptionの作成(Event Bridge経由で通知を受信するため)

SNS経由で中断通知を受信するためのSNS Topicを作成します。

TOPIC_NAME="fis-topic"

aws sns create-topic \
    --name ${TOPIC_NAME}
{
    "TopicArn": "arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:fis-topic"
}

今回は動作確認が目的なので、メールに通知します。

TOPIC_ARN="arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:fis-topic"

aws sns subscribe \
    --topic-arn ${TOPIC_ARN} \
    --protocol email \
    --notification-endpoint xxxxxxxxxxxxxxxx@classmethod.jp
{
    "SubscriptionArn": "pending confirmation"
}

Confirmationのメールを受信したら、リンクをクリックします。

Event ruleの作成

続けて、作成したSNS Topicに中断通知を送信するためにEvent ruleを作成します。

RULE_NAME="fis-rule"

aws events put-rule \
    --name ${RULE_NAME} \
    --event-pattern "{\"source\":[\"aws.ec2\"],\"detail-type\":[\"EC2 Spot Instance Interruption Warning\"]}"
{
    "RuleArn": "arn:aws:events:ap-northeast-1:XXXXXXXXXXXX:rule/fis-rule"
}

併せて、Targetに先ほど作成したSNS Topicを設定します。

RULE_NAME="fis-rule"
TOPIC_ARN="arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:fis-topic"

aws events put-targets \
    --rule ${RULE_NAME} \
    --targets "Id"="1","Arn"="${TOPIC_ARN}"
{
    "FailedEntryCount": 0,
    "FailedEntries": []
}

実験

前準備ができたので、FISの設定を行います。

  • IAM Roleの作成(Experiment template用)
  • Experiment templateの作成
  • 実験
  • 結果確認

IAM Roleの作成(Experiment template用)

まず、FISで利用するRoleを作成します。

ROLE_NAME="fis-experiment-role"
TRUST_POLICY_FILE_NAME='Trust-Policy.json'

cat << EOF > ${TRUST_POLICY_FILE_NAME}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "fis.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
EOF

aws iam create-role \
    --role-name ${ROLE_NAME} \
    --assume-role-policy-document file://${TRUST_POLICY_FILE_NAME}
{
    "Role": {
        "Path": "/",
        "RoleName": "fis-experiment-role",
        "RoleId": "AROAXS3RGICA22CS7B2ID",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/fis-experiment-role",
        "CreateDate": "2021-10-23T12:30:59+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": [
                            "fis.amazonaws.com"
                        ]
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}

ロールに割り当てるポリシーを作成します。 ロールにはスポットインスタンスの中断を実行するための権限を付与する必要があります。 詳細は公式ドキュメントを確認してください。

Create an IAM role for AWS FIS

ROLE_NAME="fis-experiment-role"
POLICY_FILE_NAME='FIS-Policy.json'

cat << EOF > ${POLICY_FILE_NAME}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowFISExperimentRoleEC2ReadOnly",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances"
            ],
            "Resource": "*"
        },
        {
            "Sid": "AllowFISExperimentRoleEC2Actions",
            "Effect": "Allow",
            "Action": [
                "ec2:RebootInstances",
                "ec2:StopInstances",
                "ec2:StartInstances",
                "ec2:TerminateInstances"
            ],
            "Resource": "arn:aws:ec2:*:*:instance/*"
        },
        {
            "Sid": "AllowFISExperimentRoleSpotInstanceActions",
            "Effect": "Allow",
            "Action": [
                "ec2:SendSpotInstanceInterruptions"
            ],
            "Resource": "arn:aws:ec2:*:*:instance/*"
        }
    ]
}
EOF

POLICY_NAME="fis-experiment-policy"

aws iam create-policy \
    --policy-name ${POLICY_NAME} \
    --policy-document file://${POLICY_FILE_NAME}
{
    "Policy": {
        "PolicyName": "fis-experiment-policy",
        "PolicyId": "ANPAXS3RGICASON7PJJM7",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:policy/fis-experiment-policy",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2021-10-23T12:33:15+00:00",
        "UpdateDate": "2021-10-23T12:33:15+00:00"
    }
}

作成したポリシーをロールにアタッチします。

ROLE_NAME="fis-experiment-role"
POLICY_ARN="arn:aws:iam::XXXXXXXXXXXX:policy/fis-experiment-policy"

aws iam attach-role-policy \
    --policy-arn ${POLICY_ARN} \
    --role-name ${ROLE_NAME}

Experiment templateの作成

Experiment templateを作成します。

TEMPLATE_FILE_NAME="Experiment.json"
ROLE_ARN="arn:aws:iam::XXXXXXXXXXXX:role/fis-experiment-role"
INSTANCE_ID="i-0ab35af721ea70bc3"

cat << EOF > ${TEMPLATE_FILE_NAME}
{
    "description": "spot-instance-interruptions",
    "targets": {
        "SpotInstances-Target-1": {
            "resourceType": "aws:ec2:spot-instance",
            "resourceArns": [
                "arn:aws:ec2:ap-northeast-1:$(aws sts get-caller-identity --query Account | jq -r):instance/${INSTANCE_ID}"
            ],
            "selectionMode": "ALL"
        }
    },
    "actions": {
        "interruption-1": {
            "actionId": "aws:ec2:send-spot-instance-interruptions",
            "parameters": {
                "durationBeforeInterruption": "PT2M"
            },
            "targets": {
                "SpotInstances": "SpotInstances-Target-1"
            }
        }
    },
    "stopConditions": [
        {
            "source": "none"
        }
    ],
    "roleArn": "${ROLE_ARN}"
}
EOF

aws fis create-experiment-template \
    --cli-input-json file://${TEMPLATE_FILE_NAME}
{
    "experimentTemplate": {
        "id": "EXT3xwiqLyKP8MkPi",
        "description": "spot-instance-interruptions",
        "targets": {
            "SpotInstances-Target-1": {
                "resourceType": "aws:ec2:spot-instance",
                "resourceArns": [
                    "arn:aws:ec2:ap-northeast-1:XXXXXXXXXXXX:instance/i-0ab35af721ea70bc3"
                ],
                "selectionMode": "ALL"
            }
        },
        "actions": {
            "interruption-1": {
                "actionId": "aws:ec2:send-spot-instance-interruptions",
                "parameters": {
                    "durationBeforeInterruption": "PT2M"
                },
                "targets": {
                    "SpotInstances": "SpotInstances-Target-1"
                }
            }
        },
        "stopConditions": [
            {
                "source": "none"
            }
        ],
        "creationTime": "2021-10-23T21:52:31.400000+09:00",
        "lastUpdateTime": "2021-10-23T21:52:31.400000+09:00",
        "roleArn": "arn:aws:iam::XXXXXXXXXXXX:role/fis-experiment-role",
        "tags": {}
    }
}

実験

ということで設定が完了しました。 スポットインスタンスの中断を実行します。

EXPERIMENT_TEMPLATE_ID="EXT3xwiqLyKP8MkPi"

aws fis start-experiment \
    --experiment-template-id ${EXPERIMENT_TEMPLATE_ID}
{
    "experiment": {
        "id": "EXP8MgdkMRZNtbvCDx",
        "experimentTemplateId": "EXT3xwiqLyKP8MkPi",
        "roleArn": "arn:aws:iam::XXXXXXXXXXXX:role/fis-experiment-role",
        "state": {
            "status": "initiating",
            "reason": "Experiment is initiating."
        },
        "targets": {
            "SpotInstances-Target-1": {
                "resourceType": "aws:ec2:spot-instance",
                "resourceArns": [
                    "arn:aws:ec2:ap-northeast-1:XXXXXXXXXXXX:instance/i-0ab35af721ea70bc3"
                ],
                "selectionMode": "ALL"
            }
        },
        "actions": {
            "reboot": {
                "actionId": "aws:ec2:send-spot-instance-interruptions",
                "parameters": {
                    "durationBeforeInterruption": "PT2M"
                },
                "targets": {
                    "SpotInstances": "SpotInstances-Target-1"
                },
                "state": {
                    "status": "pending",
                    "reason": "Initial state"
                }
            }
        },
        "stopConditions": [
            {
                "source": "none"
            }
        ],
        "creationTime": "2021-10-23T21:59:20.871000+09:00",
        "startTime": "2021-10-23T21:59:21.297000+09:00",
        "tags": {}
    }
}

結果確認

最後に結果を確認します。

まず、インスタンスメタデータの確認を行います。 この確認はインスタンスが中断される前に実施してください。

INSTANCE_ID="i-0ab35af721ea70bc3"

aws ssm send-command \
    --document-name "AWS-RunShellScript" \
    --parameters 'commands=["curl http://169.254.169.254/latest/meta-data/spot/instance-action"]' \
    --targets "Key=instanceids,Values=${INSTANCE_ID}"
{
    "Command": {
        "CommandId": "743f468c-2330-4013-a3c0-19f6f531b80a",
        "DocumentName": "AWS-RunShellScript",
        "DocumentVersion": "$DEFAULT",
        "Comment": "",
        "ExpiresAfter": "2021-10-23T23:59:58.451000+09:00",
        "Parameters": {
            "commands": [
                "curl http://169.254.169.254/latest/meta-data/spot/instance-action"
            ]
        },
        "InstanceIds": [],
        "Targets": [
            {
                "Key": "instanceids",
                "Values": [
                    "i-0ab35af721ea70bc3"
                ]
            }
        ],
        "RequestedDateTime": "2021-10-23T21:59:58.451000+09:00",
        "Status": "Pending",
        "StatusDetails": "Pending",
        "OutputS3Region": "ap-northeast-1",
        "OutputS3BucketName": "",
        "OutputS3KeyPrefix": "",
        "MaxConcurrency": "50",
        "MaxErrors": "0",
        "TargetCount": 0,
        "CompletedCount": 0,
        "ErrorCount": 0,
        "DeliveryTimedOutCount": 0,
        "ServiceRole": "",
        "NotificationConfig": {
            "NotificationArn": "",
            "NotificationEvents": [],
            "NotificationType": ""
        },
        "CloudWatchOutputConfig": {
            "CloudWatchLogGroupName": "",
            "CloudWatchOutputEnabled": false
        },
        "TimeoutSeconds": 3600
    }
}

出力を確認すると、インスタンスが停止する時刻を確認できました。

COMMAND_ID="743f468c-2330-4013-a3c0-19f6f531b80a"
INSTANCE_ID="i-0ab35af721ea70bc3"

aws ssm get-command-invocation \
    --command-id ${COMMAND_ID} \
    --instance-id ${INSTANCE_ID} \
    --query StandardOutputContent
"{\"action\":\"terminate\",\"time\":\"2021-10-23T13:01:33Z\"}"

SNS経由で受信したメッセージは以下の通りでした。 こちらは、停止する時刻ではなく通知を受信した時刻を確認できます。

{
  "version": "0",
  "id": "aa7febd7-3e42-e19b-e029-beff4e8d9011",
  "detail-type": "EC2 Spot Instance Interruption Warning",
  "source": "aws.ec2",
  "account": "XXXXXXXXXXXX",
  "time": "2021-10-23T12:59:33Z",
  "region": "ap-northeast-1",
  "resources": [
    "arn:aws:ec2:ap-northeast-1a:instance/i-0ab35af721ea70bc3"
  ],
  "detail": {
    "instance-id": "i-0ab35af721ea70bc3",
    "instance-action": "terminate"
  }
}

まとめ

今回はFISを利用してスポットインスタンスの中断を実行してみました。 スポットインスタンスのハンドリングを失敗するとサービスの中断につながるのでしっかりテストをしたいものですが、今回のアップデートを利用して納得のいくまでテストしていきましょう。

なお、今回はターゲットをリソースIDで指定しましたがタグで指定することも可能ですし、FISではその他いろいろな条件を設定できます。 想定されるシナリオは可能な限り試しておきましょう。