【ハンズオン】AWS Fault Injection Simulator(AWS FIS)でAZ障害を再現する【カオスエンジニアリング】

この記事は[AWS Advent Calendar 2022](https://qiita.com/advent-calendar/2022/aws)の記事です。

# はじめに
[AWS Fault Injection Simulator](https://aws.amazon.com/jp/fis/)（略してFIS）は、フォールト（≒システム障害）を挿入するサービスです。2021年3月に登場しました。
これを使うとEC2の停止やRDSのフェールオーバーといった障害を発生させたり、EC2に対してCPU・メモリ・ネットワークの負荷を与えるなど、システムの高負荷状態を再現することができます。

2022年10月に[FISの機能アップデート](https://aws.amazon.com/jp/about-aws/whats-new/2022/10/aws-fault-injection-simulator-network-connectivity-disruption/)が行われ、**「ネットワーク接続中断」** を再現することができるようになりました。これによって特定のサブネットへの接続を中断（≒ネットワークを遮断）させることができます。
障害時の影響を考える際に **「AZ障害が起こったらどうなる？サービス継続できる？」** ということはよく話題にあがります。ですので、障害試験においてAZ障害に近いケースは実施しておきたいところです。

今まではAZに属するEC2インスタンスを全台停止したり、NACLやSGで特定AZ向けの通信を遮断することで再現する必要がありました。
今回のアップデートにより、FISを使ってネットワークを遮断することでAZ障害相当の状況を再現することができます。
この記事ではそのやり方をハンズオン形式で紹介します。

# FISでAZ障害を再現するハンズオン

ここからはハンズオンとして手順を書いていきます

## IAMロールの作成（下準備①）

さっそくFISを触りたいところですが、まずは下準備が必要です。
FISでネットワーク障害を起こすためのIAMロールを作成していきます。

ここでは **「AWSアカウント」** を選んでロールを作成していきます。
![role01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/3dc02db7-54de-17aa-6ccd-0d6a6736c042.png)


ロール名は任意のもので構いませんが、このハンズオンでは **fis_test_role** としておきます。

作成時にポリシーの許可を行う必要がありますが、ここではAWS管理のポリシーから選択することにします。
 **AWSFaultInjectionSimulatorNetworkAccess** だけではFISを実行するときにログ作成権限が足りずに失敗するので **CloudWatchLogsFullAccess** も与えておきます。
![role02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/3ec7d790-e926-bdfb-45bf-4f4f09b60d31.png)

## IAMロールに信頼関係の追加（下準備②）

作成したIAMロールをFISから使えるようにするために、信頼されたエンティティにFISを追加する必要があります。
**「信頼関係」** のタブから **「信頼ポリシーを編集」** して、Principalに1行追記しましょう。

![role03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/7bdcff78-7ec1-8379-6715-98b8f0d1cb72.png)

↓コピペ用
```
"Service": "fis.amazonaws.com",
```

## FISでテンプレート作成

ここからFISの操作になります。

マネージメントコンソールから[AWS FIS](https://ap-northeast-1.console.aws.amazon.com/fis/home?region=ap-northeast-1#Home)を開き、 **「実験テンプレートを作成」** をクリックします。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/e011d823-39e0-e8b2-8f05-8ffe6e4785f3.png)

### 説明と名前

こちらは任意のものを付けてください。
「名前」がNameタグに相当します。

### アクション

次はアクションを作成するのですが、ここでアクションタイプで **aws:network:disrupt-connectivity** を選択します。これが今回実施したいAZ障害相当を再現させるアクションです。

![fis02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/6431a8b7-3d4a-72a8-2fb8-c53a0309cac2.png)

アクションは以下の画面のように設定します。
**ターゲット** で **Subnets-Target-1** となってますが、障害を発生させたい対象のサブネットはこのあと指定します。
**Duration** は障害を発生させる時間です。今回は10分間障害を発生させることにします。
（EC2のメトリクスの取得間隔がデフォルト5分なので、それ以上の時間を指定するのが望ましいと **[こちらの記事](https://qiita.com/yoshimi0227/items/ee86141f5b67c6d378c1)** に書いてありました）
**Scope** は **all** を選択します。これによって **指定したサブネットに対するすべての通信を遮断する** ことになります。

編集できたら「保存」ボタンをクリックします。

![fis03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/c5c8a70d-8b2c-b144-94e9-a81fa30a8291.png)

ちなみにScoreで **availability-zone** を選択すると、**指定したサブネットに対するAZ間のVPC内トラフィックを遮断する** という動作になるので注意が必要です。例えば、外部からELBを経由した通信の場合、AZ間の通信ではないため許可されることになります。

Scopeについてもっと知りたい方は **[AWSユーザーガイド(英語)のここ](https://docs.aws.amazon.com/fis/latest/userguide/fis-actions-reference.html#disrupt-connectivity)** を参照するとよいです。

### ターゲット

次にターゲットで障害を発生させる対象を選択します。
ここではリソースIDで障害を発生させたいサブネットを指定します（複数のサブネットを指定することが可能です）

選択モードは **「すべて」** にします。
これによって選択したすべてのサブネットで障害を発生させることができます。

![fis04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/2dfc75fe-bd8f-a3cb-a0e3-62eaa68f634e.png)

ちなみに、選択モードで **「数」** を選択すると、選択したターゲットの中で指定の値のターゲットをランダムに選んで障害を発生させることができます。
例えば、サブネットを3つ選択した状態で、リソースの数を「1」にすると、ランダムで1つのサブネットに障害を発生させることができます。
これにより **カオスエンジニアリング** のような用途で使ったりすることも可能です。

選択モードについてもっと知りたい方は **[AWSユーザーガイドのここ](https://docs.aws.amazon.com/ja_jp/fis/latest/userguide/targets.html#target-selection-mode)** を参照してください。

### サービスアクセス

サービスアクセスのところで、さきほど作成したIAMロールを選択します。
もし作成したIAMロールが出て来ない場合は、 **「IAMロールに信頼関係の追加（下準備②）」** を見直しましょう。

![fis05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/45c4b835-3383-38b8-c693-99b8e3a63a6e.png)

### 停止条件

ここでは指定しないことにします（アクションで10分間と指定しているので）

### ログ

ログに関しては、 **S3バケットおよびCloudWatch Logsに出力が可能** です。
必要な場合は、S3バケットやロググループを作成しておいて指定しましょう。

### タグ

ここでは指定しないことにします。指定しなくても実行は可能です

ここまででできたらテンプレートを作成して完了です。

## 実験

FISのテンプレートが出来たので、これを実行します。
Nameタグをつけておくと、複数回実施したときに区別が付きやすくなります。

### 実験開始

実行しようとすると以下の画面が出てちょっと躊躇しますが、テンプレートの設定に問題がなければ勇気を出して実行しましょう
![fis06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/e42531fe-8f4e-baae-3563-64def457c121.png)

うまく実行できていれば、状態が **Initiating** から **Running** に変化すると思います。
![fis08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/4be8fb99-8865-d230-efa6-0ff1959991e4.png)

Runningの最中は対象サブネットへの通信が遮断され、AZ障害相当の状態となっているはずです。
ここで、外部からリクエストを投げて該当のサブネット内のインスタンスに到達できないことを確認したり、AZ間の通信ができないことを確認したり、色々とAZ障害時の挙動を試してみましょう

### 実験終了

10分経つと状態が **Completed** となります。
![fis09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/c71ec4df-32ba-892c-8f8d-590fbc74f8a3.png)

これでネットワークは復旧したはずなので、今度は復旧後の動作を確認してみましょう。


### （参考）失敗した場合

なお、Failedが出た場合はクリックすると失敗した原因が表示されます。
以下はIAMロールにログ作成の権限が足りない場合のエラーです（CloudWatchLogsFullAccessのポリシーをあてると解決します）
![fis10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/addf342d-fecd-51e3-a314-cbaba7ce147e.png)

## 出力されたログの確認

FISのテンプレートで、S3バケットやCloudWatch Logsにログを出力するように設定していた場合、ログが出ているので確認しましょう。

### CloudWatchロググループ
以下はCloudWathのロググループに出力されたものです。

![log01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/2f9dc2d1-07a3-59d2-7c3e-5d86b763282c.png)

### S3バケット

S3にもgzで圧縮されて出力されます。
![log02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/dce8c3dc-7c7d-4e46-0b1f-3ef77eaeb79f.png)

### （参考）メトリクスで確認

DatadogでNLBに対するアクティブフローを取得するようにあらかじめ設定していました。
障害発生中はAZ-1aのNLBエンドポイントにリクエストが入ってきてないことがわかると思います。

![nlb01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/3637a7b0-d55c-17c7-6261-0fd2e0c1225b.png)

## （追記）マネージドサービスのAZ障害時の挙動

2023年8月にマネージドサービスであるAmazon MQ（ActiveMQ）とECS on Fargateについても、FISを使ってAZ障害時（対象サブネットのネットワーク断絶時）の挙動を確認してみました。
結果としては、当初想定していたような動きにはなりませんでした。

### Amazon MQ（ActiveMQ）の挙動

AZ-1a側のブローカーがアクティブ、AZ-1c側のブローカーがスタンバイの状態で、AZ-1a側を障害にしてみました。
結果、フェールオーバーは行われませんでした。

サポートに確認したところ、FISでの試験だとネットワークACLでトラフィックを遮断する形になるため、 MQがフェールオーバーする条件を満たしておらず、フェールオーバーしないのが仕様のようです。
FISでAZ 障害を擬似的に発生させ MQ ブローカーのフェイルオーバーを行うという試験は現時点ではできないようです。
意図的にフェールオーバーをさせるには、**ブローカーの再起動**を行なう必要があるとの回答がありました。
AZ障害発生中にマネージメントコンソールからブローカー再起動を実施したところ、AZ-1cのブローカーがアクティブになりました。

![コメント 2023-08-22 100551.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272753/36d4f0fd-a0a6-6c87-23f9-472d3504ea22.png)

### ECSの挙動

AZ-1aとAZ-1cでECSタスクが稼働する状態を作って、ALBのターゲットグループにECSタスク（に割り当てたNIC）を指定し、AZ-1a側を障害にしてみました。
AZ-1a側で稼働しているECSタスクはステータスがUnhealthyになってALBから切り離される動作を期待していたのですが、そうはならずhealthyのままでした。

こちらもサポートに確認したところ、同一AZ内で稼働しているALBからのヘルスチェックは成功するため、タスクは停止されず、healthyのままになるのが仕様のようです。
FISでの試験のやり方として、 **aws:ecs:stop-task アクション** を使用することを推奨されました。


# さいごに

今回はFISでAZ障害に相当するネットワーク遮断を行う手順を紹介しました。

実際にはAZ障害といっても、キレイにAZ内のすべてのサービスが障害になるとは考えにくく、特定のサービス（EC2、RDSなど）だけが影響を受けたり、そのサービスの中でも一部のインスタンスだけが影響を受けたりと状況は様々です。
FISで障害が再現できるサービスはまだまだ限られていますが、今後も機能の拡充やサービス適用範囲の拡張が期待されます。
FISでしかできないようなAWSマネージドな部分の障害再現にも使えるようになっていくかもしれません。

ということで、AWSを使ったシステムで障害テストに関わるエンジニアは、ぜひFISの使い方を抑えておきましょう！

# 参考にしたドキュメント
https://dev.classmethod.jp/articles/aws-fault-injection-simulator-network-connectivity-disruption/

https://docs.aws.amazon.com/fis/latest/userguide/getting-started-iam-service-role.html#network-policy

https://docs.aws.amazon.com/ja_jp/fis/latest/userguide/targets.html

https://docs.aws.amazon.com/fis/latest/userguide/fis-actions-reference.html

https://qiita.com/hirosys-biz/items/16002428f87c08c0a637

https://qiita.com/yoshimi0227/items/ee86141f5b67c6d378c1

https://qiita.com/leomaro7/items/27219e1873695f637c07