Istioのトラフィック制御ならブルーグリーンデプロイメント、カナリアリリース、フォールトインジェクション、サーキットブレーカーは簡単にできるCloud Nativeチートシート(11)

Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する連載。今回は、Istioのトラフィック制御機能によってブルーグリーンデプロイメント、カナリアリリース、フォールトインジェクション、サーキットブレーカーを簡単に実現する方法を解説する。

» 2021年12月21日 05時00分 公開
[北村卓也, 岡本隆史株式会社NTTデータ]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

 Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する本連載「Cloud Nativeチートシート」。前回に引き続き、サービスメッシュのOSS(オープンソースソフトウェア)「Istio」を紹介します。前回はIstioのインストール手順やサンプルアプリを紹介しました。今回からは下記のIstioの機能を試していきます。

  • Traffic Management(トラフィック制御)
  • Security(セキュリティ)
  • Observability(可観測性)

Istioによるトラフィック制御

 今回は「Traffic Management」(トラフィック制御)について設定をしながら紹介します。トラフィック制御機能を使うと、アプリケーションを変更することなくサービス間のルーティングやトラフィックフローを制御できます。

 Istio公式サイトのチュートリアルを参考に、代表的な機能を紹介します。

事前準備

 基本的に前回紹介したサンプルアプリ「Bookinfo」に設定をします。

 前回記事の手順を実行し、IstioのインストールとサンプルアプリケーションBookinfoのデプロイを行っておいてください。

 本稿でデプロイしているYAMLは、Istio 1.11.3に含まれるBookInfoアプリケーションを利用します。YAMLのみ参照したい方は、istio-1.11.3のアーカイブからダウンロードして、解凍してご参照ください。

ルーティング制御(ブルーグリーンデプロイメント)

 Istioはルーティング制御機能によって、トラフィックのルーティング先となるPodを柔軟に変更できます。この機能を活用して、異なるバージョンのPod(アプリケーション)を並行して稼働させ、切り替えるデプロイ手法(「ブルーグリーンデプロイメント」「カナリアリリース」など)を利用できます。

 Request Routingのチュートリアルを参考に、このルーティング制御機能の動作を確認します。

現状のルーティング動作を確認する

 設定をする前に、現状のルーティング動作を確認します。前回記事で紹介した通り、Bookinfoのreviewsサービスはv1、v2、v3の3バージョンが動作しており、それぞれ下記のような挙動をします。

  • v1はratingsサービスを呼び出さないので、評価の星を表示しない
  • v2はratingsサービスを呼び出し、各評価を1〜5個の黒い星として表示する
  • v3はratingsサービスを呼び出し、各評価を1〜5個の赤い星として表示する

 Istioは、デフォルトではラウンドロビンで振り分けるので、Bookinfoにアクセスするたびに、これら3バージョンのいずれかを使用します。試しにBookinfoにブラウザでアクセスして何度か更新してください。更新するたびに、画面右側の「Book Reviews」が下記のいずれかの表示となることを確認できます。

各バージョンの「Book Reviews」の表示

 各バージョンの動作を確認できたところで、ルーティング制御を設定します。まずは、reviewsサービスについてv1のみが使用されるようにします。ルーティング制御には、「DestinationRule」「VirtualService」リソースを使用します。

 DestinationRuleは、特定のサービスへのトラフィックに適用されるポリシーを定義するリソースです。PodのLabelを用いた“まとまり”(サブセット)の作成や負荷分散ルールの構成、接続数の設定などができます。

 VirtualServiceは、サービスにリクエストをルーティングする方法を構成するリソースです。特定のサービスへのリクエストが発生した際に、HTTPヘッダやパスなどを条件化することでルーティング先を定義したり、DestinationRuleと組み合わせることでサブセット単位にルーティングのルールを設定したりすることができます。

 DestinationRuleとVirtualServiceの詳細については前回記事の「VirtualServiceとDestinationRule」の解説をご覧ください。

DestinationRuleのサブセット定義を確認する

 DestinationRuleについて確認する前に設定を一部変更します。Bookinfoのデフォルト設定ではサブセット名とラベルの値が同じ命名規則(v1、v2、v3)となっており、設定の対応関係が分かりづらいので、紹介の都合上サブセットの名前を変更します。サブセットは、VirtualServiceの設定の中でルーティング先のPodを示す際に使う値です。DestinationRuleの中で定義します。

 reviewsサービスのDestinationRuleを編集して、サブセットの名前を変更します。

$ kubectl edit destinationrules reviews
spec:
  host: reviews
  subsets:
  - labels:
      version: v1
    name: v1
  - labels:
      version: v2
    name: v2
  - labels:
      version: v3
    name: v3
編集前
spec:
  host: reviews
  subsets:
  - labels:
      version: v1
    name: subset-v1  ※変更箇所
  - labels:
      version: v2
    name: subset-v2  ※変更箇所
  - labels:
      version: v3
    name: subset-v3  ※変更箇所
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
編集後

 DestinationRuleを確認します。「reviews」というDestinationRuleにはサブセットとPodのラベル情報の関連が定義されています。下記の設定の場合、VirtualServiceにおけるサブセット「subset-v1」は、「version=v1というラベルを持つPod」を指すことになります。

$ kubectl get destinationrules reviews -o yaml
(略)
spec:
  host: reviews      #DestinationRuleの対象となるService名。VirtualServiceの「destination.host」と同じ値にする。
  subsets:           #サブセット定義部分
  - labels:
      version: v1    #Podのラベル情報
    name: subset-v1  #サブセットの名前(VirtualService内ではこの名前を指定する)
  - labels:
      version: v2    #Podのラベル情報
    name: subset-v2  #サブセットの名前(VirtualService内ではこの名前を指定する)
  - labels:
      version: v3    #Podのラベル情報
    name: subset-v3  #サブセットの名前(VirtualService内ではこの名前を指定する)
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL

reviewsサービス(v1)にルーティングするVirtualServiceを作成する

 次に、VirtualServiceを作成します。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews 
spec:
  hosts:                   #VirtualServiceが適用されるトラフィック送信先(KubernetesのServiceリソース名)
  - reviews                # reviews Serviceへのトラフィックに以下のルールを適用する
  http:                    #HTTPトラフィックに適用されるルールの記載部
  - route:                 #ルーティング先の記載部
    - destination:         #ルーティング先の一つを記載する箇所
        host: reviews      #reviews Serviceに登録されたEndpointのうち、subset-v1で定義されたPodにのみルーティングする
        subset: subset-v1  #hostとsubsetはDestinationRuleで定義した値を利用する
virtual-service-all-v1.yaml

 VirtualServiceを作成します。

$ kubectl apply -f virtual-service-all-v1.yaml

 各サービス(details、productpage、ratings、reviews)のVirtualServiceが作成されました。

$ kubectl get virtualservice
NAME          GATEWAYS               HOSTS             AGE
bookinfo      ["bookinfo-gateway"]   ["*"]             164m
details                              ["details"]       7m21s
productpage                          ["productpage"]   7m24s
ratings                              ["ratings"]       7m22s
reviews                              ["reviews"]       7m23s

reviewsサービス(v1)にルーティングされていることを確認する

 VirtualServiceの作成前は、reviewsサービス宛てのトラフィックはIstioのデフォルト挙動であるラウンドロビンで振り分けられ、Bookinfoにアクセスするたびに3バージョンのいずれかが使用されていました。

 VirtualServiceを作成した後は、reviewサービスに対するトラフィックはsubset-v1でマッピングされたv1のPodにルーティングされるようになります。Bookinfoにアクセスすると、画面右側の「Book Reviews」に、評価を表す星マークが表示されなくなったことを確認できます。

 VirtualServiceの動きをもう少し詳しく見ます。

Virtual ServiceとDestination Ruleの役割

 VirtualServiceでは、hostsフィールドで指定した宛先(KubernetesのServiceリソースなど)のHTTPトラフィックに、「http」フィールドに記載したルーティングルールが適用されます。ルーティングルールの適用後、最終的にトラフィックがルーティングされる先は、「destination.host」(=DestinationRuleのhost)に設定したエンドポイントです。

 そのため、上記のVirtualServiceは、「reviews Serviceリソース宛てのHTTPトラフィックは、reviews Serviceリソースにひも付くPodの中でサブセットが"subset-v1"であるものにルーティングされる」という内容です。前述の通り、VirtualServiceにおけるsubset-v1はDestinationRuleに定義されており、「version-name=label-v1というラベルを持つPod」を示します。結果として、reviews Serviceへのトラフィックは、reviews Podのv1にのみルーティングされることになります。

ブルーグリーンデプロイメントを想定したルーティング設定の変更

 ブルーグリーンデプロイメントを想定して設定を変更します。

 ブルーグリーンデプロイメントとは、古いバージョンのアプリケーションを稼働させたまま新しいバージョンのアプリケーションを稼働させ、接続先を切り替えるデプロイ手法です。もし新しいバージョンのアプリケーションに不具合があった場合は、接続先を元に戻すことで、迅速にロールバックできるメリットがあります。

ブルーグリーンデプロイメントのイメージ

 今、reviewsサービスはv1のみが使用されています。ここで、新しいバージョンとしてreviewsサービスのv2がデプロイされたと仮定します。v2に切り替えたい場合は、VirtualServiceを変更するだけです。下記のようにVirtualService内のsubset設定を「subset-v1」から「subset-v2」に変更してください。

$ kubectl edit virtualservice reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: subset-v1 #変更箇所
編集前
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: subset-v2 #変更箇所
編集後

 ブラウザでBookinfoにアクセスすると、画面右側の「Book Reviews」に、評価を表す黒い星マークが表示されるようになり、reviewsサービスがv2に切り替えられたことが分かります。

 あくまでルーティング先を切り替えただけなので、reviewsサービスのv1もまだKubernetes内で稼働しています。そのため、もし切り替え後にv2に何か問題があることが分かった場合は、迅速にv1にロールバックできます。このロールバックについても、VirtualService内のsubset設定を「subset-v2」から「subset-v1」に戻すだけで実施可能なので、気になる方は実施してみてください。

 このように、VirtualServiceを構成することで、複数バージョンのアプリケーションへのルーティングを制御できることを確認しました。これにより、ブルーグリーンデプロイメントも簡単に実現できます。

 上記VirtualService例ではhttpフィールドを使用していますが、「tcp」「tls」といったフィールドを使うことで、TCPトラフィックのルーティング制御も可能です。より詳細な設定項目については、Istio公式サイトのVirtualServiceの項目をご参照ください。

トラフィック移行(カナリアリリース)

 前項では、ブルーグリーンデプロイメントを想定して、トラフィックの100%を一度に別のバージョンに切り替えましたが、Istioはトラフィックの宛先を徐々に移行することも可能です。これは、カナリアリリースに役立つ機能です。

 ちなみに、「カナリア」という鳥はとても敏感で、有毒ガスに反応して騒いだりします。そのため、未知の炭鉱を開拓する際に先頭にカナリアを連れて炭鉱の安全を確認していました。カナリアが騒ぎだすと、危険だと判断し、その坑道からは徹底しました。カナリアリリースはこのカナリアが由来で、先行的に一部のユーザーにカナリアのように新しいバージョンのアプリケーションを割り当てて、試用、テストしてもらうという意味です。

 カナリアリリースでは、一部のユーザーに新しいバージョンのサービスに割り当てテストを行い、リリースの過程で問題が発生した場合は、元のバージョンにロールバックします。カナリアリリースには、「本番環境のトラフィックを用いて動作確認ができる」「ダウンタイムなくリリースできる」といったメリットがあります。

 Traffic Shiftingのチュートリアルを参考に、トラフィック移行を実施します。reviewsサービスのv3をカナリアリリースすることを想定して、次の流れでトラフィック移行の動作を確認します。

  1. トラフィックの100%をreviewsサービス(v1)にルーティングする
  2. トラフィックの50%をreviewsサービス(v1)、残りの50%をreviews(v3)にルーティングする
  3. トラフィックの100%をreviewsサービス(v3)にルーティングする

VirtualServiceにルーティングの重み付けを設定する

 トラフィックの100%がreviewsサービス(v1)にルーティングされるよう、VirtualServiceを作成します。

$ kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml

 Bookinfoアプリケーションにアクセスし、reviewsサービスのv1(星マークが表示されない)が使用されていることを確認します。

 それでは、トラフィックの50%をreviewsサービス(v1)、残りの50%をreviewsサービス(v3)にルーティングするよう、VirtualServiceを変更します。変更に使用する「virtual-service-reviews-v3.yaml」は以下です。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: subset-v1
      weight: 50      #reviews Serviceのサブセット「subset-v1」にルーティングするトラフィックの比率。合計は100にする必要がある。
    - destination:
        host: reviews
        subset: subset-v3
      weight: 50      #reviews Serviceのサブセット「subset-v2」にルーティングするトラフィックの比率。合計は100にする必要がある。

 routeフィールドの下に、weightフィールドが記載されています。weightフィールドには、各subsetにルーティングされるトラフィックの割合を、合計を100として設定できます(合計が100でないと、変更の適用時にエラーとなります)。上記では、v1とv2がそれぞれ「weight:50」なので、トラフィックの50%ずつがv1とv2にルーティングされることになります。

 変更を適用します。

$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml

 ブラウザでBookinfoにアクセスし数回更新すると、約50%の確率でv1(星なし)とv3(赤い星)が表示されることが確認できます。

トラフィックを全てv3にルーティングする

v3の動作が問題ないと判断できたら、次のyamlを適用することで、トラフィックの100%をv3にルーティングするよう変更します。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: subset-v3
$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-v3.yaml

 ブラウザでBookinfoを数回更新すると、常にv3(赤い星)が表示されることが確認できます。

Kubernetesによるトラフィック移行との違い

 VirtualServiceのweightフィールドを利用することでトラフィックの段階的な移行が可能なことを確認できました。前述したように、この機能はカナリアリリースの実施に役立ちます。

 実はKubernetes単体でも、カナリアリリースは可能です(参考:Kubernetes公式サイト)。この場合、Serviceのselectorにマッチする旧バージョンのDeploymentと、新バージョンのDeploymentを作成し、トラフィックを分散させることになります。

 しかし、Serviceでの負荷分散は単純なラウンドロビンなので、新バージョンにルーティングされるトラフィック量を制御するには、レプリカの比率を調整する必要があります。またHPA(Horizontal Pod Autoscaler)を利用すると、この比率が崩れる可能性があるので、比率を固定化したい場合にはHPAが利用できません。「Istiod」ならレプリカ比率とは無関係にトラフィックのルーティング比率を調整できるので、制御が容易です。また、HPAを利用してPodがスケールしても、ルーティング比率に影響を与えません。

 下図の例でいうと、Podのv1とv2でPod数の比率は1対3です。KubernetesのServiceリソースでは、トラフィックもv1とv2で1対3に分散することになり、この比率を変えるにはPod数を変更するしかありません。Istioなら、Pod数比率と無関係に、指定した割合のトラフィックのみv2のPodにルーティングできます。つまり、Istioを導入することで、より容易かつ柔軟にカナリアリリースができるといえます。

カナリアリリースにおけるKubernetesとIstioの違い

タイムアウト(フォールトインジェクション)

 サービスの運用中にネットワークやサービスに障害が発生し、リクエストの応答が返ってこないことが想定されます。この場合、応答を待ち続けると、応答がないのにリクエストがどんどんたまっていき、システム全体のパフォーマンス低下や障害を引き起こす恐れがあります。通常、一定時間応答が返ってこない場合は、タイムアウトでリクエストを強制終了します。ここで、IstioのVirtualServiceを利用すると、アプリケーションでタイムアウト処理を実装することなく、Istioが強制的にリクエストを切断できます。

 ここでは、reviewサービスに1秒のタイムアウトを設定してみます。詳細は、IstioのRequest Timeoutsのドキュメントをご覧ください。

タイムアウトが設定されていない場合の動作

 タイムアウトを設定していない場合にreviewsサービスの応答に時間がかかるとBookinfoアプリケーションにどのような影響があるのかを確認します。下記を実行して、トラフィックの100%がreviews(v1)にルーティングされる状態に設定します。

$ kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml

 Bookinfoアプリケーションにアクセスし、reviewsサービスのv1(星マークが表示されない)が使用されていることを確認します。本項ではreviewsサービスのv2を利用するので、下記を実行してVirtualService内のreviewsのsubsetを変更します。

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: subset-v2  #変更箇所
EOF

 reviewsサービスの実行に時間がかかるよう、ratingsサービスに2秒の遅延を発生させます。遅延を意図的に発生させたい場合、VirtualServiceにfaultフィールドを設定します。

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - fault:              #フォールトインジェクションポリシーの設定箇所
      delay:            #発生される遅延に関する設定箇所
        percent: 100    #遅延を発生させるリクエストの割合
        fixedDelay: 2s  #遅延時間
    route:
    - destination:
        host: ratings
        subset: v1
EOF

 ブラウザでBookinfoにアクセスすると、表示内容は正常ですが、ページの表示に約2秒の遅延が発生します。これは、reviewsサービスからのratingsサービスへの呼び出しが遅延したことで、reviewsサービス自体の実行にも時間がかかっているからです。

 このように、リクエストにタイムアウトを設定していない場合、あるサービスの障害がシステム全体のパフォーマンス低下を引き起こすことがあります。

※上記のように、テストのために障害をわざと発生させることを「フォールトインジェクション」と呼びます。Istioでは、VirtualServiceのfaultフィールドを設定することで、フォールトインジェクションができます。詳細は公式サイトのFault Injectionをご覧ください。

VirtualServiceにタイムアウトを設定する

 確認したように、reviewsサービスからratingsサービスへのリクエストに時間がかかっているために、システム全体のパフォーマンスに影響が出ています。この影響を低減するために、タイムアウトを設定してみましょう。

 ここでは、reviewsサービスの呼び出しに0.5秒のタイムアウトを設定します(2秒の遅延を発生させているので、それより短い値を設定しています)。

 タイムアウトの設定は、以下のようにtimeoutフィールドにタイムアウト時間を設定することで有効になります。

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: subset-v2
    timeout: 0.5s  #タイムアウト時間。reviews Serviceのサブセット「subset-v2」にルーティングするトラフィックに適用される
EOF

タイムアウト設定後の動作

 ブラウザでBookinfoアプリケーションにアクセスすると、レビュー結果表示はエラーになるものの、ページ表示の遅延が約1秒に短縮されます。これにより、VirtualSeviceで設定したタイムアウトが有効になっていることが分かります。

※タイムアウト値を0.5秒に設定したのに約1秒の遅延が発生する理由は、productpageサービスが一度リトライを実行する仕様だからです(0.5秒×2回=1秒となります)。

 このように、アプリケーションでタイムアウト処理を実装しなくても、Istioが強制的にコネクションを切断し、タイムアウトさせることができます。

サーキットブレーカー

 サーキットブレーカーとは、障害が発生したサービスへのリクエストを遮断する機能です。例えば、次のような場合に有効です。

1. 高負荷が原因でサービス処理時間が極端に長くなり、呼び出し元のサービスからのリクエストが毎回タイムアウトしてしまう

2. サービスがエラーとなり、正しく動作していないのに、リクエストを送り続けてしまう

 1の場合は、結局タイムアウトしてしまうなら、タイムアウト時間を待たずに即座にエラーを返した方が、呼び出し元サービスへの影響を抑えることができます。サーキットブレーカーを利用することにより、障害が発生したサービスのレスポンスを待たずにエラー処理を行えるようになります。

 2の場合は、エラーとなっているサービスに無駄なリクエストを送り続けることで、障害が悪化する可能性があります。これも、サーキットブレーカーがリクエストを遮断することで、障害悪化を防止できます。

サーキットブレーカーのユースケース

 Istioを使えば、このサーキットブレーカーの仕組みを容易に導入できます。以下、3回連続でサービスへのアクセスがエラーになったら、1分間サーキットブレーカーを動作させるサンプルを例にサーキットブレーカーを動作させながら、解説します。詳細は、IstioのドキュメントのCircuit Breakingをご覧ください。

※なお本項では、Bookinfoアプリケーションは使用しません。

動作確認の流れ

 動作確認には、curlを実行できるPod(testpod)と、Istio公式サイトに用意されているhttpbinサービスを使用します。下記の流れでサーキットブレーカーの動作を確認します。

1.クライアントPodとhttpbinサービスを作成する

2.httpbinサービスにサーキットブレーカーを設定する

3.testpodからhttpbinサービスにエラーとなるリクエストを実行すると、その後リクエストを実行してもIstioがエラーを代理返答することを確認する

4.一定時間経過後、httpbinサービスに対しリクエストができる状態に戻ることを確認する

クライアントPodの作成

 動作確認に使用するクライアントとして、curlが実行可能なPodを起動します。

$ kubectl run -it --rm --restart=Never testpod --image=radial/busyboxplus:curl --overrides='{ "apiVersion": "v1", "metadata": {"annotations": { "sidecar.istio.io/statsInclusionPrefixes":"cluster.outbound,cluster_manager,listener_manager,http_mixer_filter,tcp_mixer_filter,server,cluster.xds-grpc" } } }' /bin/sh
[ root@testpod:/ ]$ hostname
testpod
※後ほどIstioのデータプレーン(Envoy)の統計情報を参照する際に必要となるので、annotationを設定している

httpbinサービスの作成

 動作確認に使用するサービスとして、httpbinサービスをデプロイします。

$ kubectl apply -f samples/httpbin/httpbin.yaml

 httpbinサービスは、HTTPリクエストによってリクエスト内容を返却してくれる、シンプルなサービスです。

[ root@testpod:/ ]$ curl http://httpbin:8000/get
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin:8000",
    "User-Agent": "curl/7.35.0",
    "X-B3-Parentspanid": "54d13dd9027556eb",
    "X-B3-Sampled": "1",
    "X-B3-Spanid": "94f620a4e50db768",
    "X-B3-Traceid": "18e820c64602fbda54d13dd9027556eb",
    "X-Envoy-Attempt-Count": "1",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=e6e9afca5a693fe96ffef99cd8a93bcdac2bb59bac284e717ace66d90523f8e7;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/default"
  },
  "origin": "127.0.0.6",
  "url": "http://httpbin:8000/get"
}

 URLパスに「status/503」のように指定することで、任意のステータスコードでレスポンスを返す機能も持っています。

[ root@testpod:/ ]$ curl -v http://httpbin:8000/status/503
> GET /status/503 HTTP/1.1
> User-Agent: curl/7.35.0
> Host: httpbin:8000
> Accept: */*
>
< HTTP/1.1 503 Service Unavailable
< server: envoy
< date: Tue, 17 Aug 2021 08:13:53 GMT
< content-type: text/html; charset=utf-8
< access-control-allow-origin: *
< access-control-allow-credentials: true
< content-length: 0
< x-envoy-upstream-service-time: 6

サーキットブレーカーを設定する

 ここではクライアントからhttpbinサービスへのアクセスが3回連続で5xxエラーになったら、httpbinサービスが復旧するまで、最低1分間はサーキットブレーカーがエラーを代理返答するよう設定します。サーキットブレーカーの設定は、DestinationRuleで行います。

 以下のようにoutlierDetectionを設定することで、エラーが発生したサービスへのアクセスに対してIstioが代理返答してくれるようになります。

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
spec:
  host: httpbin                #DestinationRuleの対象となるエンドポイント
  trafficPolicy:
    outlierDetection:          #負荷分散先から異常なホストを排除する(代理返答の対象とする)ための設定箇所
      consecutive5xxErrors: 3  #しきい値となる連続する5xxエラー(502,503,504)件数
      interval: 1s             #サービスの状態を確認する間隔
      baseEjectionTime: 1m     #代理返答を行う最低期間
EOF

※詳細な仕組みについては、IstioのデータプレーンであるEnvoyの公式ドキュメントをご参照ください。

 動作確認の前に下記のように設定を変更します。Istioを適用したサービスへのリクエストは、デフォルトでIstioのデータプレーン(Envoy)によって、デフォルトで2回リトライされます。上記「consecutive5xxErrors」のカウントにはこのリトライも含まれます。

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - httpbin
  http:
  - route:
    - destination:
        host: httpbin
    retries:       #リクエストが失敗したときのリトライに関する設定箇所
      attempts: 0  #リトライ回数の設定。今回は0にする。
EOF

 動作確認の際に挙動が分かりづらくなってしまうので、httpbinサービスへのアクセスについてはリトライしないようにVirtualServiceを設定しています。

※Istioのリトライ設定についての詳細は、公式ドキュメントをご覧ください。

サーキットブレーカー設定後の動作確認

 サーキットブレーカーが動作するか確認します。まずは、httpbinサービスの状態を確認します。istioctlコマンドを使うと、サービスの呼び出し元(testpod)から見て、httpbinサービスが利用可能な状態になっているかどうかを確認できます。下記のように「OUTLIER CHECK」が「OK」となっていれば、利用可能です。

$ istioctl proxy-config endpoint testpod | grep -E '^ENDPOINT|httpbin'
ENDPOINT                         STATUS      OUTLIER CHECK     CLUSTER
10.8.2.16:80                     HEALTHY     OK                outbound|8000||httpbin.default.svc.cluster.local

 また、Envoyの統計情報の「ejections_detected_consecutive_5xx」という値に、しきい値を超えた回数が記録されます。

$ kubectl exec testpod -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep ejections_detected_consecutive_5xx
cluster.outbound|8000||httpbin.default.svc.cluster.local.outlier_detection.ejections_detected_consecutive_5xx: 0

 こちらも確認すると、現在は0です。

※その他の統計情報についてはEnvoy公式ドキュメントをご覧ください。

 この状態で、httpbinサービスに対して503エラーとなるリクエストを3回連続で実行した後、通常のリクエストを実行してください。

[ root@testpod:/ ]$ curl http://httpbin:8000/status/503
[ root@testpod:/ ]$ curl http://httpbin:8000/status/503
[ root@testpod:/ ]$ curl http://httpbin:8000/status/503
[ root@testpod:/ ]$ curl http://httpbin:8000/get
no healthy upstream

 このように、「no healthy upstream」というエラーメッセージが返ります。httpbinサービスの状態を確認すると、「OUTLIER CHECK」がFAILED、つまりhttpbinサービスが利用不可の状態になっていることが分かります。

$ istioctl proxy-config endpoint testpod | grep -E '^ENDPOINT|httpbin'
ENDPOINT                         STATUS      OUTLIER CHECK     CLUSTER
10.8.2.16:80                     HEALTHY     FAILED            outbound|8000||httpbin.default.svc.cluster.local

 Envoyの統計情報も確認すると、しきい値超えが一度発生したのでejections_detected_consecutive_5xxの値が1に増加しています。

$ kubectl exec testpod -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep ejections_detected_consecutive_5xx
cluster.outbound|8000||httpbin.default.svc.cluster.local.outlier_detection.ejections_detected_consecutive_5xx: 1

サービス復旧の検知

 約1分経過後に再度httpbinサービスの状態を確認すると、「OUTLIER CHECK」がOKに戻っており、httpbinサービスに正常に接続できるようになっています。「baseEjectionTime」に設定した代理返答の最低期間が終わり、httpbinサービスも正常だからです。

$ istioctl proxy-config endpoint testpod | grep -E '^ENDPOINT|httpbin'
ENDPOINT                         STATUS      OUTLIER CHECK     CLUSTER
10.8.2.16:80                     HEALTHY     OK                outbound|8000||httpbin.default.svc.cluster.local
[ root@testpod:/ ]$ curl http://httpbin:8000/get
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin:8000",
    "User-Agent": "curl/7.35.0",
    "X-B3-Parentspanid": "87f55848da801cc0",
    "X-B3-Sampled": "1",
    "X-B3-Spanid": "7f9861950dd4b6c4",
    "X-B3-Traceid": "347a4b63e6f3834887f55848da801cc0",
    "X-Envoy-Attempt-Count": "1",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=1978905bdf649d80477130c062bf871bab958731a71479d2be6c87d297aed26f;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/default"
  },
  "origin": "127.0.0.6",
  "url": "http://httpbin:8000/get"
}
サーキットブレーカーの挙動

 サーキットブレーカーには、エラーのカウントや代理返答、ダウンしたサービスの復旧検知などの仕組みが必要なので、自作しようとするとかなりの手間がかかります。Istioがあれば、DestinationRuleの設定だけで、簡単にサーキットブレーカーを導入できます。

次回は、Istioのセキュリティ機能

 今回はIstioのトラフィック制御機能について、設定と動作を確認しながら解説しました。Istioを利用することで、アプリケーションを変更することなく、マイクロサービスのトラフィックに関わるさまざまな課題を解決できることが実感できたと思います。

 紹介したもの以外にも細かいトラフィック制御が可能なので、公式サイトのVirtualServiceDestinationRuleの項目を参照して試してみてください。

 次回はIstioのセキュリティ機能を紹介する予定です。こちらもぜひご覧ください。

Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

4AI by @IT - AIを作り、動かし、守り、生かす
Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。