ブログ

AWS Fault Injection Service (FIS) | FIS を使ってみた

エンジニアの横田です。 クラウドインフラの信頼性を確保することは、多くの企業にとって重要な課題です。AWS Fault Injection Simulator(FIS)は、こうした信頼性の向上を支援する強力なツールです。

今回はこの AWS Fault Injection Simulator(FIS)を使ってみました。せっかくなので、 CloudFormation テンプレート化したので本ブログで紹介します。
決して CloudFormation での AWS Fault Injection Simulator(FIS)作成を試したかっただけではありません。断じて違います。

CloudFormation って何?という方は以前に ブログ記事 でご紹介していますので、そちらも是非ご確認ください。

※ 本ブログ記事の内容は 2024/10/30 時点の情報です。

AWS Fault Injection Simulator(FIS)とは?

AWS Fault Injection Simulator(FIS)は、カオスエンジニアリングの概念をクラウド環境に導入するためのマネージドサービスです。AWS Fault Injection Simulator(FIS) を使うことで、システムの弱点を発見し、実際の障害に強いアーキテクチャを構築することができます。
具体的には、仮想的に障害を引き起こし、システムの耐障害性やリカバリー能力をテストします。

例えば、特定の EC2 インスタンスの停止やネットワーク遅延のシミュレーションを行い、その結果を分析することで、システムがどのように反応するかを確認できます。

このプロセスを通じて、未然に問題を発見し、迅速に対策を取ることが可能です。

AWS Fault Injection Simulator(FIS)のメリットとユースケース

AWS Fault Injection Simulator(FIS) の最大のメリットは、リアルな障害シナリオを安全にテストできることです。開発チームや運用チームは、障害が発生したときのシステムの動作を事前に確認することで、問題解決のスピードと精度を向上させることができます。

いくつかのユースケースを挙げると、以下のようなものがあります:

  • システム全体の回復力テスト:特定のサービスがダウンしたときに、他のサービスがどのように対応するかを検証します。
  • スケーラビリティの確認:トラフィックが急増したときの負荷テストを実施し、インフラのスケール対応力を評価します。
  • 障害対応プロセスの訓練:オペレーションチームが障害発生時の手順をスムーズに実行できるかを訓練する機会を提供します。

例えば、ALB(Application Load Balancer)と EC2 インスタンスのオートスケーリング構成で、AWS Fault Injection Simulator(FIS) を利用して負荷を意図的に上げることで、インスタンスが自動的にスケーリングされるかを確認することができます。これにより、スケーリングポリシーが適切に設定されているかどうかをテストすることが可能です。

AWS Fault Injection Simulator(FIS)を CloudFormation で実装する手順

本ブログでは、CloudFormation テンプレートを使って、 ALB と EC2(オートスケーリング)構成で AWS Fault Injection Simulator(FIS)を使って負荷テストを実施する具体的な手順をご紹介します。

今回も rain でデプロイします。
rain について知らないよ〜って方は こちら で紹介しておりますのでぜひご確認ください。

1. CloudFormation テンプレートのデプロイ

下記のコードブロックの内容をお好みの ファイル名.yaml で保存してください。
本ブログでは blog-fis.yaml として使用します。

AWSTemplateFormatVersion: 2010-09-09

Parameters:
  Project:
    Type: String
    Default: blog-fis
  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
  InstanceType:
    Type: String
    Default: t3.micro
  DesiredCapacity:
    Type: Number
    Default: 2
  MaxCapacity:
    Type: Number
    Default: 3

Mappings:
  Map:
    CIDR:
      VPC: 10.0.0.0/16
      SubnetA: 10.0.0.0/24
      SubnetC: 10.0.1.0/24

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !FindInMap [Map, CIDR, VPC]
      EnableDnsSupport: true
      EnableDnsHostnames: true
  
  InternetGateway:
    Type: AWS::EC2::InternetGateway

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: !FindInMap [Map, CIDR, SubnetA]
      MapPublicIpOnLaunch: true

  PublicSubnetC:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: !FindInMap [Map, CIDR, SubnetC]
      MapPublicIpOnLaunch: true
  
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref PublicRouteTable
  
  SubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTable

  SubnetCRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetC
      RouteTableId: !Ref PublicRouteTable
    
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ALB security group
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0

  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      Subnets:
        - !Ref PublicSubnetA
        - !Ref PublicSubnetC
      SecurityGroups:
        - !Ref ALBSecurityGroup

  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VPC
      Port: 80
      Protocol: HTTP
      TargetType: instance
      HealthCheckProtocol: HTTP
      HealthCheckPort: 80
      HealthCheckPath: /

  SSMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM

  SSMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref SSMRole

  LaunchConfiguration:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      ImageId: !Ref LatestAmiId
      InstanceType: !Ref InstanceType
      SecurityGroups: [!Ref InstanceSecurityGroup]
      IamInstanceProfile: !Ref SSMInstanceProfile
      UserData:
        Fn::Base64: |
          #!/bin/bash
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
          INSTANCE_ID=`curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id`
          AZ=`curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/placement/availability-zone`
          echo "<h1>This is EC2 in $AZ</h1>" > /var/www/html/index.html

  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
        - !Ref PublicSubnetA
        - !Ref PublicSubnetC
      LaunchConfigurationName: !Ref LaunchConfiguration
      MinSize: !Ref DesiredCapacity
      MaxSize: !Ref MaxCapacity
      DesiredCapacity: !Ref DesiredCapacity
      TargetGroupARNs:
        - !Ref TargetGroup
      Tags:
      - Key: AutoScalingGroupName
        Value: !Ref Project
        PropagateAtLaunch: true
      - Key: Name
        Value: !Ref Project
        PropagateAtLaunch: true
  
  CPUAlarmHigh:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: "Alarm when CPU exceeds 75%"
      Namespace: AWS/EC2
      MetricName: CPUUtilization
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      Statistic: Average
      Period: 60
      EvaluationPeriods: 1
      Threshold: 75
      ComparisonOperator: GreaterThanOrEqualToThreshold
      AlarmActions:
        - !Ref ScaleOutPolicy

  CPUAlarmLow:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: "Alarm when CPU is below 30%"
      Namespace: AWS/EC2
      MetricName: CPUUtilization
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      Statistic: Average
      Period: 60
      EvaluationPeriods: 1
      Threshold: 30
      ComparisonOperator: LessThanThreshold
      AlarmActions:
        - !Ref ScaleInPolicy

  ScaleOutPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      PolicyType: SimpleScaling
      ScalingAdjustment: 1
      AdjustmentType: ChangeInCapacity
      Cooldown: 300

  ScaleInPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      PolicyType: SimpleScaling
      ScalingAdjustment: -1
      AdjustmentType: ChangeInCapacity
      Cooldown: 300

  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow ALB access only
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref ALBSecurityGroup

  FISExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: FISExecutionRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: fis.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: FISSSMPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ssm:DescribeDocument
                Resource: arn:aws:ssm:*:*:document/AWSFIS-Run-CPU-Stress
              - Effect: Allow
                Action:
                  - ec2:DescribeInstances
                  - ec2:DescribeInstanceStatus
                  - ssm:ListCommandInvocations
                  - ssm:ListCommands
                  - ssm:SendCommand
                Resource: "*"
              - Effect: Allow
                Action:
                  - "iam:PassRole"
                Resource: "*"

  FISExperiment:
    Type: AWS::FIS::ExperimentTemplate
    Properties:
      Description: Test Auto Scaling by stress cpu
      RoleArn: !GetAtt FISExecutionRole.Arn      
      Actions:
        StressCPU:
          ActionId: aws:ssm:send-command
          Parameters:
            documentArn: !Sub arn:aws:ssm:${AWS::Region}::document/AWSFIS-Run-CPU-Stress
            documentParameters: "{\"LoadPercent\":\"100\", \"DurationSeconds\":\"290\", \"InstallDependencies\":\"True\"}"
            duration: PT5M
          Targets:
            Instances: oneRandomInstance
      Targets:
        oneRandomInstance:
          ResourceType: aws:ec2:instance
          ResourceTags:
            AutoScalingGroupName: !Ref Project
          SelectionMode: COUNT(2)
      StopConditions:
        - Source: none
      Tags: 
        Name: fisStressCPU

Outputs:
  LoadBalancerDNSName:
    Value: !GetAtt ALB.DNSName
  
  FISExperimentId:
    Value: !Ref FISExperiment

保存したディレクトリで下記のコマンドを実行し、デプロイします。

rain deploy <保存したファイル名>.yaml <お好みのスタック名>

本ブログではスタック名は blog-fis としてデプロイします。
パラメータの確認が入りますが、変更は特に必要ありませんので、何も入力せずに Enter を押してください。

# rain deploy blog-fis.yaml blog-fis
Enter a value for parameter 'Project' (default value: blog-fis): 
Enter a value for parameter 'LatestAmiId' (default value: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64): 
Enter a value for parameter 'InstanceType' (default value: t3.micro): 
Enter a value for parameter 'DesiredCapacity' (default value: 2): 
CloudFormation will make the following changes:
Stack blog-fis:
  + AWS::ElasticLoadBalancingV2::Listener ALBListener
  + AWS::EC2::SecurityGroup ALBSecurityGroup
  + AWS::ElasticLoadBalancingV2::LoadBalancer ALB
  + AWS::EC2::VPCGatewayAttachment AttachGateway
  + AWS::AutoScaling::AutoScalingGroup AutoScalingGroup
  + AWS::CloudWatch::Alarm CPUAlarmHigh
  + AWS::CloudWatch::Alarm CPUAlarmLow
  + AWS::IAM::Role FISExecutionRole
  + AWS::FIS::ExperimentTemplate FISExperiment
  + AWS::EC2::SecurityGroup InstanceSecurityGroup
  + AWS::EC2::InternetGateway InternetGateway
  + AWS::AutoScaling::LaunchConfiguration LaunchConfiguration
  + AWS::EC2::RouteTable PublicRouteTable
  + AWS::EC2::Route PublicRoute
  + AWS::EC2::Subnet PublicSubnetA
  + AWS::EC2::Subnet PublicSubnetC
  + AWS::IAM::InstanceProfile SSMInstanceProfile
  + AWS::IAM::Role SSMRole
  + AWS::AutoScaling::ScalingPolicy ScaleInPolicy
  + AWS::AutoScaling::ScalingPolicy ScaleOutPolicy
  + AWS::EC2::SubnetRouteTableAssociation SubnetARouteTableAssociation
  + AWS::EC2::SubnetRouteTableAssociation SubnetCRouteTableAssociation
  + AWS::ElasticLoadBalancingV2::TargetGroup TargetGroup
  + AWS::EC2::VPC VPC
Do you wish to continue? (Y/n) 
Deploying template 'blog-fis.yaml' as stack 'blog-fis' in ap-northeast-1.

デプロイ中は ターミナルからも進行状況を確認できますが、 AWS マネジメントコンソールの CloudFormation からもご確認いただけます。

デプロイが完了すると、 CREATE_COMPLETE と表示されます。
Outputs の FISExperimentId に表示される ID は後ほど使用するのでメモ帳などに控えておいてください。

Stack blog-fis: CREATE_COMPLETE
  Outputs:
    LoadBalancerDNSName: blog-fis-ALB-qcELvTVgdML6-949204421.ap-northeast-1.elb.amazonaws.com
    FISExperimentId: EXT2AKuaB4k5fDVi
Successfully deployed blog-fis

Apache が動作しているか確認しましょう。

ブラウザを開き、Outputs の LoadBalancerDNSName に表示されている DNS を検索バーに入力してみます。

デプロイされる ALB には 2 台の EC2( ap-northeast-1a と ap-northeast-1c )が登録され、ラウンドロビンで振り分けられるようになっているので、ブラウザをリロードすると表示が切り替わることが確認できます。

AWS マネジメントコンソールの AutoScaling でもデプロイしたリソースを確認してみます。
デプロイした状態だと 2 台のインスタンスが起動している状態です。

2. AWS Fault Injection Simulator(FIS)で実験実行

それでは実際に AWS Fault Injection Simulator(FIS)で実験を実行してみましょう。
まずは AWS マネジメントコンソールで確認します。

ここで一つ落とし穴があります。
検索ボックスには “Fault Injection Simulator” ではなく、 “FIS” や “AWS FIS” と入力しないとサービスを見つけることができないのでご注意ください。

AWS マネジメントコンソールからでも実行できるのですが、今回は AWS CLI で実行します。

aws fis start-experiment --experiment-template-id <スタックデプロイ完了後に表示されるFISExperimentId> | jq -r '.experiment.id'

正常に実験が開始されました。本実験は 5 分間 AutoScalingGroup 内のインスタンス 2 台の CPU 負荷を上昇させます。

# aws fis start-experiment --experiment-template-id EXT2AKuaB4k5fDVi | jq -r '.experiment.id'
EXP8KURDeW4xBph3vA

出力される ID を AWS マネジメントコンソールの AWS FIS 画面の 回復力のテスト > 実験 で検索すると、実行中の実験を確認できます。

しばらくすると、スケールアウトのスケーリングポリシーが機能し、3 台目のインスタンスが起動しました。

ブラウザで確認するとラウンドロビンで ap-northeast-1a が 2 回表示されます。

実験完了後、しばらくするとスケールインのスケーリングポリシーが機能し、再び AutoScaling で起動しているインスタンスは 2 台の状態に戻ります。

本ブログの実験では、 AutoSacling が機能していることを確認することで完了としますが、実際に業務で利用する場合には、必要に応じてスケーリングポリシーを調整することで、システムのレジリエンスを向上させることができます。

AWS Fault Injection Simulator(FIS)を安全に導入するには

障害を意図的に起こすというとリスクが高いように感じるかもしれませんが、AWS Fault Injection Simulator(FIS)はそのプロセスを安全に管理するためのツールも備えています。

例えば、AWS Fault Injection Simulator(FIS)は実験を行う際に事前に「ガードレール」を設定することができます。これにより、影響範囲を最小限に抑え、重要なリソースやプロダクション環境に悪影響が及ばないようにコントロールできます。

本ブログでの説明は割愛しますが、ご興味があれば AWS 公式ドキュメント*1をご確認ください。

AWS Fault Injection Simulator(FIS)を使ってみた(まとめ)

AWS Fault Injection Simulator(FIS)は、システムの信頼性を確保し、障害に強いインフラを構築するための強力なツールです。企業にとって、意図的な障害をシミュレーションし、その対応力を高めることは、サービスの質の向上に繋がります。AWS Fault Injection Simulator(FIS)を使って、より堅牢で信頼性の高いクラウドインフラを実現してみませんか?

今回ご紹介した AWS Fault Injection Simulator(FIS)に限らず、「AWS でこんなことできないかな?」「自社サーバーを AWS に引っ越したいな。」等々ございましたら、右上の[お問い合わせ]からお気軽にご相談ください。

ご参考:ユーザーガイド(AWS)

*1「AWS FIS の停止条件」
https://docs.aws.amazon.com/ja_jp/fis/latest/userguide/stop-conditions.html

【筆者実行環境】
PC:MacBook Air (M1, 2020)
OS:macOS Sequoia 15.1
rain:Rain v1.17.0 darwin/arm64
aws cli:aws-cli/2.18.14 Python/3.12.7 Darwin/24.1.0 source/arm64
jq:jq-1.7.1

元記事発行日: 2025年01月08日、最終更新日: 2025年01月17日