Kubernetes 上で HTTP 通信障害をシミュレートするカオステストをしてみよう

本記事は「[Kubernetes Advent Calendar 2022](https://qiita.com/advent-calendar/2022/kubernetes)」7 日目の記事です。
本日 2022/12/07 は [KubeDay Japan](https://events.linuxfoundation.org/kubeday-japan/) でしたね！今回は現地参加出来なかったのですが、資料や参加レポートなどとても楽しみです。

さて、本記事では、Kubernetes 上で実行されるアプリケーションに対して、HTTP 障害を注入することで、カオスエンジニアリングを実践する方法をいくつか紹介したいと思います。

# カオスエンジニアリング?
カオスエンジニアリングは、不確実な状況を持つ本番環境に対して、回復力のあるシステムを実現するために取り入れられる手法です。
特に分散システムにおいては、各システムがそれぞれ正常な状態を保っていても、相互に影響しあい、予期せぬ不安定な状態を生み出すことがあります。このように内在する予測不可能な混沌(カオス)に耐えるため、実験と観察を行い、分散システムの動作を理解し、高い信頼性につなげていくことをカオスエンジニアリングと呼びます。
より深く知りたい方は、[O'Reilly Japan「カオスエンジニアリング」](https://www.oreilly.co.jp/books/9784873119885/)などをお読みいただくと良いと思います。

# 具体的なモチベーション
本番環境のアプリケーションに問題が発生し、以下のような想定外の挙動に悩まされ、夜も眠れない日はありませんか？

- 適切なアラートが発砲されず、障害発見が遅くなった(あるいは、気付かなかった)
- 自動回復されなかった(あるいは、復旧手順書に基づき作業をしたが回復しなかった)
- [カスケード障害](http://oreilly.com/library/view/sre-google/9784873117911/ch22.xhtml)により、想定以上の複数サービスに障害が波及した
- サーキットブレーカーやレートリミットなど、耐障害性を高めるための設定が想定通りに機能しなかった

恐らく皆さん、こういった状態を避けるため、障害試験として様々な手段でアプリケーションに擬似障害を起こして、結果のエビデンスをまとめるというようなことを、本番リリース前に実施されているのではないかと思います。
Kubernetes を利用されている場合、そのコアコンセプトである"Reconciliation Loop"により、多くの状態は自動回復し、安全な状態を保ってくれますし、根本的なリソース不足に対しても、[Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) などの自動拡張により動的に対処出来ます。
しかし一方で、Pod としては問題なく正常に起動できたとしても、限定的な条件で発生するような Pod 上のアプリケーション特有のエラーをすべて Kubernetes 環境上で網羅的にテストしていくのは非常に骨が折れます。
私個人の経験としては、API に任意のエラーステータスコードを返してほしいために、API の仕様を基に、特定の path に不正な条件でテストリクエストをして、擬似障害を引き起こし、その条件を網羅しようとして苦悩したことがあります。

近年では、様々なカオスエンジニアリングツールで HTTP 障害を注入することが比較的容易になったので、いくつか紹介していきます。
なお、本記事では各ツールのインストール方法や、ダッシュボード・管理画面での実行方法などには触れません。実際にご利用される場合は、各ツールの Documentation を参照いただければと思います。

# Chaos Mesh
Chaos Mesh では [v2.0.0](https://github.com/chaos-mesh/chaos-mesh/releases/tag/v2.0.0) (2021/07/23 リリース) から [HTTPChaos](https://chaos-mesh.org/docs/simulate-http-chaos-on-kubernetes/) が導入され、以下のような HTTP 障害を注入できます。
- abort ... TCP接続を中断し、接続障害をシミュレートする
- delay ... レイテンシ追加によりレスポンス遅延をシミュレートする
- replace ... HTTPリクエストまたはレスポンスのコンテンツ一部を置き換え、API障害をシミュレートする
- patch ... HTTPリクエストまたはレスポンスにコンテンツを追加し、API障害をシミュレートする

ここでは HTTPChaos というカスタムリソースを用いた abort と delay の例を見ていきます。

## abort
以下のようなマニフェストを apply することで、接続障害がシミュレートできます。
```
apiVersion: chaos-mesh.org/v1alpha1
kind: HTTPChaos
metadata:
  name: test-http-abort
spec:
  mode: all # すべてのPodを対象とする
  selector:
    labelSelectors:
      app: nginx
  target: Request
  port: 80
  method: GET
  path: /api
  abort: true
  duration: 5m
```
`app: nginx`ラベルが付与されている すべての Pod の`/api` path に対して、ポート 80 番経由でリクエストされる GET リクエストは 5 分間 TCP 接続が中断されます。

## delay
以下のようなマニフェストを apply することで、レイテンシ遅延がシミュレートできます。
```
apiVersion: chaos-mesh.org/v1alpha1
kind: HTTPChaos
metadata:
  name: test-http-delay
spec:
  mode: one # ランダムなPodを1つ選択して、対象とする
  selector:
    labelSelectors:
      app: nginx
  target: Request
  port: 80
  method: GET
  path: /api/*
  delay: 10s 
  duration: 5m
```
`app: nginx`というラベルが付与されている Pod のうち 1つのランダムな Pod の`/api`パスに対して、ポート 80 番経由でリクエストされる GET リクエストは 5 分間、10 秒の遅延が発生します。
`abort`の例と異なり、ここでは`mode`の値を変えることで、注入範囲をコントロールしています。
`mode`も含め、その他のパラメータについては[こちら](https://chaos-mesh.org/docs/simulate-http-chaos-on-kubernetes/#description-for-common-fields)をご覧ください。


# Litmus Chaos
Litmus Mesh では [2.10.0](https://github.com/litmuschaos/litmus/releases/tag/2.10.0) (2022/06/15 リリース) から HTTP Chaos experiment が導入され、以下のような HTTP 障害を注入できます。

- [Pod HTTP Reset Peer](https://litmuschaos.github.io/litmus/experiments/categories/pods/pod-http-reset-peer/) ... peer error による接続リセットを行うことで、TCP 接続を中断し、接続障害をシミュレートする
- [Pod HTTP Latency](https://litmuschaos.github.io/litmus/experiments/categories/pods/pod-http-latency/) ... レイテンシ追加によりレスポンス遅延をシミュレートする
- [Pod HTTP Status Code](https://litmuschaos.github.io/litmus/experiments/categories/pods/pod-http-status-code/) ... レスポンスのステータスコード及び Body を変更し、API 障害をシミュレートする
- [Pod HTTP Modify Body](https://litmuschaos.github.io/litmus/experiments/categories/pods/pod-http-modify-body/) ... リクエスト/レスポンスボディを変更し、API 障害をシミュレートする
- [Pod HTTP Modify Header](https://litmuschaos.github.io/litmus/experiments/categories/pods/pod-http-modify-header/) ... リクエスト/レスポンスヘッダを変更・追加・削除し、API 障害をシミュレートする

Chaos Mesh とできることは概ね同じですね。実際に例を見てみましょう。

## Pod HTTP Reset Peer
以下のようなマニフェストを apply することで、接続障害がシミュレートできます。
```
apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: test-http-reset
spec:
  engineState: "active"
  annotationCheck: "false"
  appinfo:
    appns: "testns"
    applabel: "app=httpbin"
    appkind: "deployment"
  chaosServiceAccount: pod-http-reset-peer-sa
  experiments:
  - name: pod-http-reset-peer
    spec:
      components:
        env:
        # reset timeout specifies after how much duration to reset the connection
        - name: RESET_TIMEOUT #in ms
          value: '2000'
        # provide the port of the targeted service
        - name: TARGET_SERVICE_PORT
          value: "80"
```
上記は以下の条件を持つリソースに対して、80番ポートへのリクエストが来たら、TCP 接続をリセットし HTTP 障害を注入します。
- `namespace`が`testns`である
- `app=httpbin`というラベルを持つ
- `Deployment`リソースである

`RESET_TIMEOUT`の値は接続をリセットする期間を示し、この例では2秒間接続がリセットされ続けます。

また、`spec.engineState`や`annotationCheck`はカオステストの実行状態を管理するために必要なフラグです。詳しくは [ChaosEngine](https://docs.litmuschaos.io/docs/concepts/chaos-engine/#chaosengine) の定義をご覧いただければと思います。

## Pod HTTP Latency
以下のようなマニフェストを apply することで、レイテンシ遅延がシミュレートできます。
```
apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: test-http-delay
spec:
  engineState: "active"
  annotationCheck: "false"
  appinfo:
    appns: "testns"
    applabel: "app=httpbin"
    appkind: "deployment"
  chaosServiceAccount: pod-http-latency-sa
  experiments:
  - name: pod-http-latency
    spec:
      components:
        env:
        # provide the latency value
        - name: LATENCY
          value: '2000'
        # toxicity is the probability of the request to be affected
        # provide the percentage value in the range of 0-100
        # 0 means no request will be affected and 100 means all request will be affected
        - name: TOXICITY
          value: "100"
        # provide the port of the targeted service
        - name: TARGET_SERVICE_PORT
          value: "80"
        - name: TOTAL_CHAOS_DURATION
          VALUE: '60'
```
上記は先程と同条件のリソースに対して、80番ポートへのリクエストが来たら、`LATENCY`の設定通り、レイテンシに2秒追加し、遅延を引き起こします。
この例で指定している`TOXICITY`は、`ChaosEngine`リソースが行うカオステストにより、HTTP のリクエストが影響を受ける割合(毒性)を示す値です。0-100 で ％ を示すような設定となっています。
また、`TOTAL_CHAOS_DURATION`は、`ChaosEngine`リソースによるカオステストが継続する期間を示しています。

# Istio
Istio が導入されている環境では、[v0.1 から`Fault Injection`という機能](https://istio-releases.github.io/v0.1/docs/tasks/fault-injection.html)が実装されており、サイドカーに注入された istio-proxy (envoy) コンテナを制御するカスタムリソースの設定によって、HTTP 障害を引き起こすことができます。
具体的には、 [Virtual Service](https://istio.io/latest/docs/reference/config/networking/virtual-service/) の設定により、以下のような HTTP 障害を注入できます。

- [HTTPFaultInjection.Abort](https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPFaultInjection-Abort) ... レスポンスのステータスコードを変更し、API障害をシミュレートする
- [HTTPFaultInjection.Delay](https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPFaultInjection-Delay) ... レイテンシ追加によりレスポンス遅延をシミュレートする

## HTTPFaultInjection.Abort
以下のようなマニフェストを apply することで、任意のステータスコードを返却する API 障害がシミュレートできます。
```
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: test-http-abort
spec:
  hosts:
  - httpbin.testns.svc.cluster.local
  http:
  - route:
    - destination:
        host: httpbin.testns.svc.cluster.local
        subset: v1
    fault:
      abort:
        percentage:
          value: 100
        httpStatus: 500

```
この例では、`httpbin.testns.svc.cluster.local`宛の 100％ すべてのリクエストがステータスコード 500 で返却されます。
`httpStatus`でステータスコードを指定し、`percentage`でステータスコードの割合を指定するシンプルな作りです。

## HTTPFaultInjection.Delay
以下のようなマニフェストを apply することで、レイテンシ遅延がシミュレートできます。
```
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: test-http-delay
spec:
  hosts:
  - httpbin.testns.svc.cluster.local 
  http:
  - route:
    - destination:
        host: httpbin.testns.svc.cluster.local 
        subset: v1
    fault:
      delay:
        percentage:
          value: 50
        fixedDelay: 3s
```
この例では、`httpbin.testns.svc.cluster.local`宛の 50 ％のリクエストに対するレスポンスが 3 秒間遅延します。
こちらも`fixedDelay`で遅延時間を指定し、`percentage`で遅延させるリクエストの割合を指定するシンプルな作りです。

また、ここでは深く触れませんが、Istio は以下のようなサービスメッシュの通信制御機能を用いて、HTTP リクエスト・レスポンスの操作が出来ます。

- [http.Headers](https://istio.io/latest/docs/reference/config/networking/virtual-service/#Headers) ... リクエスト/レスポンスヘッダを変更・追加・削除できる
- [http.HTTPDirectResponse](https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPDirectResponse) ... 任意のステータスコード、ボディを指定したレスポンスを返す

通信制御を目的とした機能ではありますが、結果的に任意の HTTP 障害のシミュレートには応用できるかと思います。

# Enjoy Chaos!
本記事では、HTTP 障害を引き起こすカオステストの実現するための手段をいくつか紹介しました。
Istio を既に導入済みの環境で、HTTP 障害実験だけが目的であれば、Istio の機能で十分かもしれませんし、CPU / Memory 使用量の高騰や、ノード障害、コンテナ障害など、より複雑なカオステストをしたい場合は、Chaos Mesh や Litmus Mesh などを試していただいても良いかもしれません。
また、本記事では触れていませんが、Chaos Mesh / Litmus Mesh 共にカオス実験の管理画面やダッシュボードなど豊富な UI を持っているので、そういた機能差なども検討の余地があると思います。

もし興味があれば、様々なカオステストを通じて、不確実性をコントロールし、安心して眠れる日々を獲得しましょう！
いきなり本番環境で利用いただくのは非常に怖いので、ご自身で十分に検証していただいて、ご利用は計画的に。