AWS FISでシナリオライブラリ利用時には、設定済みの AWS FIS SSM ドキュメントでOSがサポートされているか確認してほしい

AWS FISでシナリオライブラリから実験テンプレートを作成する場合、設定済みのAWS FIS SSM ドキュメントでエラーになってしまった時のTipsとしてご活用ください。
2024.05.01

こんにちは!AWS事業本部のおつまみです。

みなさん、AWS Fault Injection Simulator (AWS FIS) 使ったことありますか?私は今回初めて使いました。

AWS FIS は、AWS 上でカオスエンジニアリングを行うためのマネージドサービスです。
ちょっとした負荷試験やりたいなという時にとても便利なサービスですね。

AWS FIS には、シナリオライブラリという機能が用意されており、様々なユースケースを想定したシナリオを AWS が事前に準備してくれています。

このシナリオライブラリを初めて使った際にエラーとなり、上手く実験をすることができませんでした。
同じように悩んでいる方もいると思い、対処方法を共有します!

いきなり結論

  • 20240501時点で設定済みの AWS FIS SSM ドキュメントを使用すると、OSによってはエラーとなり、実験に失敗する。(今回はRHEL9.2)
  • 失敗した場合はSSMドキュメントを確認し、事前に使用するコマンド(今回の場合であれば、stress-ng)をインストールする必要がある。
  • AWS FISでシナリオライブラリから実験テンプレートを作成する場合、設定済みの AWS FIS SSM ドキュメントでOSがサポートされているか確認してほしい。

設定済みの AWS FIS SSM ドキュメントとは?

FISには、事前に定義された一連の障害注入アクションが含まれている「設定済みの AWS FIS SSM ドキュメント」が用意されています。
これらのドキュメントは、Amazonが所有しているAWS Systems Manager (SSM) のドキュメントとして提供されています。

こちらが設定済みのAWS FIS SSMドキュメント例です:

  1. AWSFaultInjectingStopEC2Instance
    • Amazon EC2インスタンスを停止するためのドキュメント
  2. AWSFaultInjectingTerminateEC2Instance
    • Amazon EC2インスタンスを終了するためのドキュメント
  3. AWSFaultInjectingStopRDSInstance
    • Amazon RDS DBインスタンスを停止するためのドキュメント
  4. AWSFaultInjectingStopECSService
    • Amazon ECS サービスを停止するためのドキュメント
  5. AWSFaultInjectingStopEKSNode
    • Amazon EKS ノードを停止するためのドキュメント

これらを使用すると、手動で障害アクションを定義する必要がなく、すぐに実験を開始できます!
ただし、使用する前に、これらのドキュメントがターゲットのOSをサポートしているかどうかを確認する必要があります。詳細は下記ドキュメントをご参考下さい。

参考:Systems Manager SSM ドキュメントを FIS AWS と共に使用する - AWS フォールト・インジェクション・サービス

検証

今回は1つのEC2インスタンスのメモリに負荷をかけるシナリオを実施してみます。

対象環境

事前準備として、下記スペックのEC2インスタンスを1台起動させておきます。

  • インスタンスタイプ:t2.micro
  • OS:RHEL9.2
  • AMI:ami-04fdeff70b7359022
  • SSM Agent, CloudWatch Agentインストール済

やってみた

Fault Injection Serviceのコンソールを開き、[シナリオからテストを作成]を選択します。

今回は[EC2ストレス:メモリ]を選択し、[シナリオを利用してテンプレートを作成]を選択します。

アカウントターゲティングでは、[このAWSアカウント:(アカウントID)]を選択し、[確認]を選択します。

[実験テンプレートを作成]の画面に遷移するので、設定を確認し、[実験テンプレートを作成]を選択します。

注意点としては、このままだとターゲットは指定されたタグがついたEC2インスタンスのみとなります。タグ付けをするか、ターゲットメソッドを変更し、インスタンスIDで指定するようにしましょう。

このように実験テンプレートが作成されます。

テンプレートが作成された、[実験を開始]を選択します。

そのまま[実験を開始]を選択します。

注意喚起画面が表示されるので、[開始]と入力し、[実験を開始]を選択します。

すると、実験が開始されるので5分ほど待ちます。

5分ほど経過後に確認してみると、このように状態が失敗になっていました。
1つ目のアクションで失敗しているみたいですね。

失敗原因の特定

原因を確認してみます。
[アクション]タブから状態のFailedを選択し、表示されるRun CommandのIDを選択します。

インスタンスIDを選択します。

Run Commandのエラーメッセージをみると、stress-ngコマンドがないためエラーになっているようでした。

ここで、設定済みの AWS FIS SSM ドキュメントの中身を確認してみます。
Run Commandのコマンド説明からAWSFIS-Run-CPU-Stressという名前のドキュメントであることがわかります。

Systems Manager の左ペインから[ドキュメント]を選択します。

表示されるドキュメントを選択します。

このようにドキュメントで定義されているコンテンツが表示されます。

2024/5/1時点のドキュメントは以下でした。

description: |-
  ### Document name - AWSFIS-Run-Memory-Stress

  ## What does this document do?
  It runs memory stress on an instance via stress-ng tool. If stress-ng is not already installed on the instance, this SSM document will install it, unless InstallDependencies parameter is set to False.
  This SSM document supports Amazon Linux and Ubuntu operating systems only.

  ## Dependencies installed by this SSM Document
  * stress-ng
  Note: This SSM document does not uninstall dependencies once installed. If you don't want this SSM document to install any dependencies, set InstallDependencies to False and bake the dependencies into your EC2 instance. For example, by using image-builder (https://aws.amazon.com/image-builder/).

  ## Input Parameters
  * DurationSeconds: (Required) The duration - in seconds - of the memory stress.
  * Workers: The number of virtual memory stressors (default: 1).
  * Percent: The percentage of virtual memory to use (required).
  * InstallDependencies: If set to True, Systems Manager installs the required dependencies on the target instances. (default: True).

  ## Output Parameters
  None.

schemaVersion: '2.2'
parameters:
  DurationSeconds:
    type: String
    description: (Required) The duration - in seconds - of the memory stress.
    allowedPattern: ^[0-9]+$
  Workers:
    type: String
    description: 'The number of virtual memory stressors (default: 1).'
    default: '1'
    allowedPattern: ^[0-9]+$
  Percent:
    type: String
    description: 'The percentage of virtual memory to use (default: 80).'
    default: '80'
    allowedPattern: ^([1-9][0-9]?|100)$
  InstallDependencies:
    type: String
    description: 'If set to True, Systems Manager installs the required dependencies
      on the target instances. (default: True).'
    default: 'True'
    allowedValues:
    - 'True'
    - 'False'
mainSteps:
- action: aws:runShellScript
  name: InstallDependencies
  precondition:
    StringEquals:
    - platformType
    - Linux
  description: |
    ## Parameter: InstallDependencies
    If set to True, this step installs the required dependecy via operating system's repository. It supports both
    Debian (apt) and CentOS (yum) based package managers.
  inputs:
    onFailure: exit
    runCommand:
    - |
      #!/bin/bash


      if [[ "$( command -v stress-ng 2>/dev/null )" ]]; then
          if [[ -n "" ]] ; then
              depmod -a
              if modprobe sch_netem; then
                exit
              fi
          else
              echo Dependency is already installed. ; exit ;
          fi
      fi
      if  [[ "{{ InstallDependencies }}" == True ]] ; then
        echo "Installing required dependencies"
        if [ -f  "/etc/system-release" ] && grep -i 'Amazon Linux' /etc/system-release  ; then
          if ! grep -Fiq 'VERSION_ID="2023"' /etc/os-release ; then
            # Use amazon-linux-extras if available (Amazon Linux 2). Don't need it otherwise (Amazon Linux 1)
            command -v amazon-linux-extras 2>/dev/null 1>&2 && amazon-linux-extras install testing
            yum -y install stress-ng
          elif grep -Fiq 'ID="amzn"' /etc/os-release && grep -Fiq 'VERSION_ID="2023"' /etc/os-release ; then
            
            yum -y install stress-ng
          else
            echo "Exiting - This SSM document supports: Amazon Linux, Ubuntu, CentOS (7, Stream 8, and 9) and RHEL (7, 8, 9) operating systems"
            exit 1
          fi

        elif grep -Fiq 'ID="centos"' /etc/os-release  || grep -Fiq 'ID="rhel"' /etc/os-release ; then
          # Fetch OS Version
          os_version_number=$(grep -oP '(?<=^VERSION_ID=).+' /etc/os-release | tr -d '"')
          # if the version has a decimal, this line will remove it
          os_major_version_number=${os_version_number%.*}
          # Replace with version number in the url if required
          if ! rpm --quiet -q epel-release &&  [ -n "https://dl.fedoraproject.org/pub/epel/epel-release-latest-VERSION.noarch.rpm" ] ; then
            epel_dl_url="https://dl.fedoraproject.org/pub/epel/epel-release-latest-VERSION.noarch.rpm"
            epel_with_version="${epel_dl_url/VERSION/$os_major_version_number}"
            yum -y install $epel_with_version
          fi
          rhel_package="stress-ng"
          
          yum -y install $rhel_package

        elif grep -i "Ubuntu" /etc/issue ; then
          apt-get update -y
          # when installing, sometimes ubuntu has stderr that are not breaking errors.
          install_error=$(apt-get install -y stress-ng) 2>&1
          if [[ -n "$install_error" ]]  ; then
              echo "$install_error"
          fi
          ubuntu_commands=( stress-ng )
          for dependency_command in "${ubuntu_commands[@]}"
          do
             if ! command -v $dependency_command >/dev/null 2>&1 ; then
                  echo "Exiting - $dependency_command not installed"
                  exit 1
             fi
          done
        else
          echo "Exiting - This SSM document supports: Amazon Linux, Ubuntu, CentOS (7, Stream 8, and 9) and RHEL (7, 8, 9) operating systems"
          exit 1
        fi
        if [[ -n "" ]] ; then
          if ! systemctl is-enabled atd || ! systemctl is-active atd; then
              echo "Enabling and starting atd"
              systemctl enable atd
              systemctl start atd
          fi
        fi
      else
        echo "Dependencies are not installed - Please set InstallDependencies to True."
        exit 1
      fi
- action: aws:runShellScript
  name: ExecuteStressNg
  precondition:
    StringEquals:
    - platformType
    - Linux
  description: |
    ## Parameters: DurationSeconds, Workers and Percent
    This step will run a memory stress test on the instance for the specified DurationSeconds time.
    It will start `Workers` number of workers, using `Percent` of the total available memory.
  inputs:
    maxAttempts: 1
    timeoutSeconds: 43200
    runCommand:
    - |-
      #!/bin/bash

      #################################
      # General pre fault-execution logic #
      #################################

      # To track how long the fault command took to run
      start_time=$(date +%s)


      ########################
      # Fault-specific logic #
      ########################

      if [ {{ DurationSeconds }} -lt 1 ] || [ {{ DurationSeconds }} -gt 43200 ] ; then echo DurationSeconds parameter value must be between 1 and 43200 && exit 1; fi
      if [ {{ Percent }} -lt 1 ] || [ {{ Percent }} -gt 100 ] ; then echo Percent parameter value must be between 1 and 100 && exit 1; fi
      pgrep stress-ng && echo Another stress-ng command is running, exiting... && exit 1
      echo Initiating memory stress for {{ DurationSeconds }} seconds, {{ Workers }} workers, using {{ Percent }} percent of total available memory...
      stress-ng --vm {{ Workers }} --vm-bytes {{ Percent }}% -t {{ DurationSeconds }}s
      echo Finished memory stress.


      #################################
      # General post fault-execution logic #
      #################################
      DURATION={{ DurationSeconds }}


      if [[ -z $start_time ]];then
          >&2 echo "start_time is not defined"
          exit 1;
      fi

      elapsed_time=$(( $(date +%s) - start_time ))

      # Fail if the fault command exits succssfully but the execution duration is less than the expected duration.
      # This happens when Stress-ng is killed prematurely using SIGTERM or SIGINT.
      if [[ "$elapsed_time" -lt "$DURATION" ]]; then
          >&2 echo "Fault took $elapsed_time seconds to execute, which is less than expected duration $DURATION"
          exit 1;
      fi

ハイライトをかけた部分を翻訳してみます。

これは、stress-ng ツールを介してインスタンスにメモリ ストレスを実行します。インスタンスにストレス ng がまだインストールされていない場合は、InstallDependency パラメータが False に設定されていない限り、この SSM ドキュメントによってインストールされます。この SSM ドキュメントは、Amazon Linux および Ubuntu オペレーティング システムのみをサポートしています。

ということで、RHELは今回サポート対象外でした。
なお前述した参考ドキュメントではRHELはサポート対象になっていたので、こういうこともあるようです。。

修復方法

自動でインストールがされなかったので、EC2インスタンスに手動でstress-ngのパッケージを導入します。

dnf -y install stress-ng

再度実験を開始します。

無事成功していることがわかりました!

CloudWatch Agentでメモリ使用率を出力するように設定していたので、メトリクスを確認してみると、実験中に負荷がかかっていることもわかりました。

さいごに

今回はAWS FISでシナリオライブラリ利用時には、注意する点をご紹介しました。

最後までお読みいただきありがとうございました!
どなたかのお役に立てれば幸いです。

以上、おつまみ(@AWS11077)でした!

参考資料

設定済みの AWS FIS SSM ドキュメント