SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

クラウドネイティブ時代の実践カオスエンジニアリング

Kubernetesでカオスエンジニアリング ~サーキットブレーカーパターンで回復性の高いシステムを構築する

クラウドネイティブ時代の実践カオスエンジニアリング 第6回

Chaos Toolkitによるカオス挿入

 これで、AKSクラスタ上にサンプルのアプリケーションが動きましたので、Chaos Toolkitを使ってこのシステムにカオスを挿入します。

実験の環境構築

 次のコマンドを実行し、Chaos Toolkitの環境を作成します。詳細な手順については、Chaos Toolkitでカオスエンジニアリングを体験しよう~Azure上のVMにカオス挿入を参考にしてください。

 ここでは、追加でChaos ToolkitのKubernetes拡張機能をインストールします。また、環境変数KUBECONFIGにKubernetesクラスタへの接続情報ファイルのパスを設定します。

pip install chaostoolkit-kubernetes
export KUBECONFIG=~/.kube/config

1. Podの削除

 Kubernetesには自己修復機能が備わっています。そのためPodが強制終了されても自動で復旧します。これを実際に確認してみましょう。

 まず、動作しているfrontendアプリのPodを削除し、システムの状態を確認します。

 次の実験ファイル(experiment.pod.json)を作成します。

experiment.pod.json
{
    "title": "System is resilient to backend failures",
    "description": "Can our frontend service gracefully a backend failure?",
    ...
    "steady-state-hypothesis": {
        "title": "Services are all available and healthy",
        "probes": [
            {
                "type": "probe",
                "name": "all-services-are-healthy",
                "tolerance": true,
                "provider": {
                    "type": "python",
                    "module": "chaosk8s.probes",
                    "func": "all_pods_healthy"
                }
            },
            {
                "type": "probe",
                "name": "front-service-must-still-respond",
                "tolerance": 200,
                "provider": {
                    "type": "http",
                    "url": "http://<your frontend Extarnal IP>/"
                }
            }
        ]
    },
    "method": [
        {
            "type": "action",
            "name": "stop-frontend-pod",
            "provider": {
                "type": "python",
                "module": "chaosk8s.pod.actions",
                "func": "delete_pods",
                "arguments": {
                    "name": "front"
                }
            },
...
}

 ここで、Chaos ToolkitのKubernetes拡張機能のchaosk8s.pod.actionsdelete_podsを使います。ここではfrontendアプリのPodを削除したあと、エンドポイントにアクセスして問題なく稼働していれば、仮説通りの動きになります。

 次のコマンドで実験を実行します。

chaos run experiment.pod.json
[2021-10-25 10:15:24 INFO] Validating the experiment's syntax
[2021-10-25 10:15:44 INFO] Experiment looks valid
....
[2021-10-25 10:15:47 INFO] Playing your experiment's method now...
[2021-10-25 10:15:47 INFO] Action: stop-backend-service
[2021-10-25 10:15:47 INFO] Pausing after activity for 10s...
[2021-10-25 10:15:57 INFO] Probe: all-services-are-healthy
[2021-10-25 10:15:57 INFO] Steady state hypothesis: Services are all available and healthy
[2021-10-25 10:15:57 INFO] Probe: all-services-are-healthy
[2021-10-25 10:15:58 INFO] Probe: front-service-must-still-respond
[2021-10-25 10:15:58 INFO] Steady state hypothesis is met!
[2021-10-25 10:15:58 INFO] Let's rollback...
[2021-10-25 10:15:58 INFO] No declared rollbacks, let's move on.
[2021-10-25 10:15:58 INFO] Experiment ended with status: completed

 ここでPodの状態を確認します。

 ChaosToolkitによって、動作しているfront-54758ddbc9-vxk7qが削除されていますが、それを検知したKubernetesのReplicaSet Controllerが新たなPodであるfront-54758ddbc9-rj2c9を起動しています。

front-54758ddbc9-vxk7q   1/1     Terminating         0          44s
front-54758ddbc9-rj2c9   0/1     Pending             0          0s
front-54758ddbc9-rj2c9   0/1     Pending             0          0s
front-54758ddbc9-rj2c9   0/1     ContainerCreating   0          0s
front-54758ddbc9-vxk7q   0/1     Terminating         0          47s
front-54758ddbc9-rj2c9   1/1     Running             0          3s
front-54758ddbc9-vxk7q   0/1     Terminating         0          49s
front-54758ddbc9-vxk7q   0/1     Terminating         0          49s

 このようにKubernetesのもつ自己修復機能によりPodがただちに復旧し、frontendアプリケーションがエラーなくサービスを継続できていることが分かります。

2. Deploymentの削除

 次にChaos Toolkitを使ってDeploymentを削除して、システムの状態を確認します。Deploymentが喪失するケースとしては、CI/CD Pipeline実行時のトラブルや、オペレーションミスなどが想定されます。

 次の実験ファイル(experiment.deployment.json)を作成します。

experiment.deployment.json
"method": [
    {
        "type": "action",
        "name": "stop-backend-service",
        "provider": {
            "type": "python",
            "module": "chaosk8s.deployment.actions",
            "func": "delete_deployment",
            "arguments": {
                "name": "back"
            }
        },

 moduleはchaosk8s.deployment.actionsを使います。Deploymentを削除したいので、funcにはdelete_deploymentを指定します。今回はbackendアプリがデータを返せなくなるケースを想定しています。

サーキットブレーカーのないケース

 Chaos Toolkitを使ってDeploymentリソースを削除して、システムの状態がどうなるかを確認します。今回のサンプルではfrontendアプリが内部でbackendアプリを呼び出しデータを取得していますが、例えばfrontendアプリがクラスタ外で提供されている外部APIを呼び出しているものの、対向サービスのネットワークエラー/システム障害/メンテナンスなどで、一時的に応答を返せなくなったケースなどもあてはまります。

public Mono<String> getApiData() {
    return webClient.get()
            .uri("http://backend:8081/message")
            .retrieve()
            .bodyToMono(String.class);

 まず、現在のPodの状態を確認します。

kubectl get pod

NAME                     READY   STATUS    RESTARTS   AGE
back-6cc87c6f6f-g2ck4    1/1     Running   0          19m
front-54758ddbc9-rj2c9   1/1     Running   0          18m

 次のコマンドを実行し、Chaos Toolkitを使って動作しているfrontend Deploymentを削除し、システムの状態を確認します。

chaos run experiment.deployment.json
[2021-10-25 10:37:52 INFO] Validating the experiment's syntax
[2021-10-25 10:38:10 INFO] Experiment looks valid
[2021-10-25 10:38:10 INFO] Running experiment: System is resilient to backend failures
....
[2021-10-25 10:38:10 INFO] Playing your experiment's method now...
[2021-10-25 10:38:10 INFO] Action: stop-backend-service
[2021-10-25 10:38:10 INFO] Pausing after activity for 10s...
[2021-10-25 10:38:20 INFO] Probe: all-services-are-healthy
[2021-10-25 10:38:21 INFO] Steady state hypothesis: Services are all available and healthy
[2021-10-25 10:38:21 INFO] Probe: all-services-are-healthy
[2021-10-25 10:38:21 INFO] Probe: front-service-must-still-respond
[2021-10-25 10:38:22 CRITICAL] Steady state probe 'front-service-must-still-respond' is not in the given tolerance so failing this experiment
[2021-10-25 10:38:22 INFO] Experiment ended with status: deviated
[2021-10-25 10:38:22 INFO] The steady-state has deviated, a weakness may have been discovered

 ここでPodを確認すると、backend Deploymentが削除されたため、現在frontendのPodのみが動作している状態となります。

kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
front-54758ddbc9-rj2c9   1/1     Running   0          23m

 実験の結果をみても、frontendアプリが通信先のbackendサービスからデータを取得できなくなり、クライアントにステータスコード200の応答を返せず、サービスが継続できていないことが分かります。

[2021-10-25 10:38:22 CRITICAL] Steady state probe 'front-service-must-still-respond' is not in the given tolerance so failing this experiment

サーキットブレーカーのあるケース

 このサンプルの実装では、Deploymentリソースの喪失でシステムが停止することがわかりました。これを是正するためサーキットブレーカーを導入します。

 今回のサンプルは2つのサービスからなるシンプルな構成ですが、本番環境で稼働するマイクロサービスでは、システムは自律的に動作する複数のサブシステムによって構成されます。そのため、あるサービスに障害が発生してもそれ以外のシステムは正常に動作する必要があります。

サーキットブレーカーを導入したシステム構成
サーキットブレーカーを導入したシステム構成

 まず、実験を続けるためbackendアプリのDeploymentをいったん復旧します。

kubectl apply -f manifest/backend/deployment.yaml
kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
back-6cc87c6f6f-x27fd    1/1     Running   0          4s
front-54758ddbc9-rj2c9   1/1     Running   0          39m

 frontendアプリのバージョン2を確認します。サーキットブレーカーの実装にはResilience4jを使います。これはJavaによるフォールトトレランスライブラリで、サーキットブレーカーだけでなくリトライ処理やハルクヘッドなどをサポートしています。

 サーキットブレーカーは、最近発生した障害の数を監視して、処理を続行するか、例外を返すだけにするかを決定します。

 具体的にはサーキットブレーカーは CLOSED/OPEN/HALF-OPENという3つの状態があります。

 システムが正常な時はCLOSEDで、一定数以上処理が失敗した場合には、OPENになりアクセスを遮断します。OPENから一定時間経過するとHALF-OPENとなり、さらにHALF-OPENで一定数以上処理が失敗しなければ、正常状態であるCLOSEDに戻ります。

サーキットブレーカーの3つの状態
サーキットブレーカーの3つの状態

 サーキットブレーカーについての詳細はMicrosoft Azureのサーキット ブレーカー パターンにまとまっています。

 Resilience4jの設定は、Javaコードまたはプロパティファイルでできます。例えばslidingWindowで設定したcallの成功/失敗を保存し、失敗の確率がfailureRateThresholdに達すると、サーキットブレーカーをOPENにし、アプリケーションに例外を返します。

 この例では、10回中の80%のリクエストに失敗すると、例外を返します。

@Configuration
public class CircuitConfig {

    @Bean
    public CircuitBreakerRegistry circuitBreakerRegistry() {
        return CircuitBreakerRegistry.of(
                CircuitBreakerConfig.custom()
                    .slidingWindowSize(10)
                    .failureRateThreshold(80)
                    .build()
        );
    }
}

 circuitBreakerメソッドは、引数で指定したサーキットブレーカーが存在しない場合は新しく作成し、既に存在する場合はそのオブジェクトを返します。

 ここではレジストリを介してfrontbreakerという名前のサーキットブレーカーを作成します。

@Bean
public CircuitBreaker circuitBreaker(CircuitBreakerRegistry circuitBreakerRegistry) {
    return circuitBreakerRegistry.circuitBreaker("frontbreaker");
}

 backendのサービスを呼び出すときにサーキットブレーカーを有効にするには、@CircuitBreakerアノテーションを使用するか、Operetorを使って指定します。

 Operetorの場合、transformメソッドチェーンを利用してCircuitBreakerOperator.of(circuitBreaker)を利用すると、サーキットブレーカーが機能します。

 このサンプルコードではapi.getApiDataの処理でサーキットブレーカーがOPENになると、onErrorResumeで指定したfallbackメソッドを呼び出します。

public Mono<String> getData() {
    CircuitBreaker circuit = circuitBreakerRegistry.circuitBreaker("frontbreaker");

    return api.getApiData()
            .transform(CircuitBreakerOperator.of(circuit))
            .onErrorResume(this::fallback);
}

public Mono<String> fallback(Throwable t) {
    log.error("Fallback : " + t.getMessage());
    return Mono.just("Sorry...I'm frontend.");
}

 このサーキットブレーカーを実装したアプリケーションをKubernetesクラスタにデプロイします。frontend Deploymentのコンテナイメージのバージョンをv1からv2に変更してバージョンアップします。

code manifest/frontend/deployment.yaml
# 変更前
containers:
- image: <your container registry name>/chaos-frontend:v1

# 変更後
containers:
- image: <your container registry name>/chaos-frontend:v2
kubectl apply -f manifest/frontend/deployment.yaml

 次のコマンドを実行し、Chaos Toolkitを使って動作しているfrontend Deploymentを削除し、システムの状態を確認します。

chaos run experiment.deployment.json
chaos run experiment.deployment.json
[2021-10-25 11:08:07 INFO] Validating the experiment's syntax
[2021-10-25 11:08:15 INFO] Experiment looks valid
....
[2021-10-25 11:08:16 INFO] Playing your experiment's method now...
[2021-10-25 11:08:16 INFO] Action: stop-backend-service
[2021-10-25 11:08:17 INFO] Pausing after activity for 10s...
[2021-10-25 11:08:27 INFO] Probe: all-services-are-healthy
[2021-10-25 11:08:27 INFO] Steady state hypothesis: Services are all available and healthy
[2021-10-25 11:08:27 INFO] Probe: all-services-are-healthy
[2021-10-25 11:08:27 INFO] Probe: front-service-must-still-respond
[2021-10-25 11:08:28 INFO] Steady state hypothesis is met!
[2021-10-25 11:08:28 INFO] Let's rollback...
[2021-10-25 11:08:28 INFO] No declared rollbacks, let's move on.
[2021-10-25 11:08:28 INFO] Experiment ended with status: completed

 Podを確認すると、backend Deploymentが削除されたため、frontendのみ動作している状態ですが、frontendがリクエストをクライアントに返しているため、サービスが提供され続けていることが分かります。

kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
front-55bd94d9c8-8dkjz   1/1     Running   0          3m38s

 Webブラウザから次のURLにアクセスします。

http://<frontend External IP>/

 backendアプリからデータは取得できないものの、frontendアプリがfallbackメソッドを呼び出し、frontendアプリ自身がデータを返していることが分かります。

サーキットブレーカーによりシステムを閉塞させることができた
サーキットブレーカーによりシステムを閉塞させることができた

 このように、サーキットブレーカーを入れることにより一部のサービス障害を他に伝播させることなく、システムを閉塞させることができました。

クリーンアップ

 検証が終わりクラスタが不要になったらクラスタのリソースグループを削除します。

RG_NAME=aks-sample
az group delete --name $RG_NAME 

 AKSクラスタを稼働し続けると、課金が発生しますので注意してください。

まとめ

 本記事では、Kubernetesクラスタで動くアプリケーションに対してカオス挿入と、障害でアプリケーションにアクセスできなくなったときに、サーキットブレーカーパターンを用いて閉塞させることで、システム全停止を防ぐにはどうすればよいかを説明しました。

 クラウドのような分散システムでは個々のサービスが適切に機能している場合でも、それらのサービス間の相互作用によって予測不可能な障害を引き起こす可能性があり、これらを完全にゼロにするということは不可能です。そのため、システムのどこか障害が発生しているという前提に立ち、いかに早く障害を検知・復旧させるかに重点を置いて設計開発をすることが重要です。そして、正確に予測不能な障害による不安定な状況にも耐えられることができるという自信をつけるために、カオスエンジニアリングを検討するとよいでしょう。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
クラウドネイティブ時代の実践カオスエンジニアリング連載記事一覧

もっと読む

この記事の著者

阿佐 志保(アサ シホ)

金融系シンクタンクなどで銀行/証券向けインフラエンジニア、製造業向けインフラエンジニアとして従事。都市銀行情報系基盤システム構築やシステム統廃合、証券会社向けバックオフィスシステムの共通基盤開発プロジェクトを経験。結婚・出産を経て現在は、日本マイクロソフトで法人向けにクラウド導入支援やプリセールスな...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/14861 2021/11/10 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー