これだけは知っておこう負荷試験 ~その1 基本とお試し試験~
記事の概要
負荷試験はシステム運用において避けて通れないタスクであるが難易度が高いタスクでもある。
本記事ではまず、実際にWordpressに対して簡単な負荷試験を行いながら、 負荷試験における基本的な観点を整理していく。
負荷試験の主な目的
負荷試験は何のために行うのだろうか? 主な観点としては下記のような項目が挙げられる。
- 負荷に耐えうる構成か?
- 要件に対して最適な構成になっているか?
- 突然スパイクしたときにも自動でスケールし耐えられる構成になっているか?
- 通常時に必要以上にインフラリソースを確保していないか?
- スケール特性を把握する
- まずどこにボトルネックが来て、どこをチューニングすればシステムがスケールするかを確認する
負荷試験の大事なルール
必ずどこかがボトルネックになっている状態を作ること
スコープを絞ってステップごとに区切って実施すること
- 問題の原因特定を効率的に行うことができるため、少しずつ負荷のかけ方を変えながら実施する
ネットワーク的に近いところから攻撃する
最初は最小の構成 & 自動スケールしないようにして実施する
負荷試験をする上で参考にする性能指標
負荷試験実施の際は、下記指標を確認していき、問題があった場合はCPU使用率やメモリ使用率、コネクション数などの細かい指標を追っていくことになる。
実際の負荷試験のステップ例
攻撃サーバのセットアップ
- 最大攻撃性能を把握することで攻撃のパフォーマンスが十分に発揮できているかを確認する。
フレームワークの素の性能の把握
DBへの参照を含めた性能の把握
- DBとの接続方法、クエリの実行方法などが適切に設定、実装されているかを確認する。
- 前工程と比較してDB参照に大幅な劣化がある場合は、下記観点で確認する。
- Web, DB, 攻撃サーバで負荷が正しくかかっているかを確認する
- バッファプールにデータが乗り切っているか、キャッシュヒット率はどのくらいか
- コネクションを永続化できているか確認する(コネクションプーリングの設定)
- アプリケーション側でN+1やスロークエリなどが出ているか
DBへの更新を含めた性能の把握
- DBとの接続方法、クエリの実行方法などが適切に設定、実装されているかを確認する。
- 前工程と比較してDB参照に大幅な劣化がある場合は、下記観点で確認する。
- Web, DB, 攻撃サーバで負荷が正しくかかっているかを確認する
- 必要以上に偏ったユーザデータのシナリオになっていないか? ※例えばテストユーザを一人だけにして更新テストを行う場合、ロックの傾向が偏ってしまい、本番のシナリオと剥離してしまう。
- 不必要なロック、トランザクション分離レベルなどを見直しつつ調整
外部サービスとの通信を含めた性能の把握
- 外部サービスとネットワークを経由して通信しているケースの負荷シナリオ
- 自サービスの管轄外の場合、ここがボトルネックになりやすい
- 負荷をかける場合は、意図せず外部サービスに過負荷をかけてしまわないよう気をつける
実際の使用を想定したシナリオでの性能の把握
- 実際のユーザのシナリオを想定し、負荷をかける
- このシナリオでの負荷試験でしっかり想定どおりのパフォーマンスがでるかどうかを検証する
スケール特性試験
実践してみよう!
- このWordPressリソースを用いて試しに負荷試験の流れを学ぶ https://kubernetes.io/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/
環境
この環境で出せる攻撃時スループットの上限値を知る
現時点で、MAXでどのくらいのスループットがでるのかを検証するため、 一番処理の軽い静的ファイルを返すエンドポイントに対し、 Apache Benchを使って負荷をかけてみる。 ネットワーク的な要因を排除した純粋なスループットを計測したいのでホストから実行する。 ネットワークはスループットをかなり落としてしまう要因になるので、実際の負荷試験においてもなるべくネットワーク的に近いところから試験することはかなり重要になってくる。
ab -n 20000 -c 200 -k http://10.108.252.89/readme.html
大体この数値がこの環境における攻撃時に出せる最大のスループットということになる。
Requests per second: 7711.03 [#/sec] (mean)
検証の妥当性
この結果が妥当であることをどのように判別すればよいだろうか? まず最初に見るべき観点は以下になる。
対象システムのCPU使用率が100%近くまで上がっているか?
- ※DBサーバへの負荷の場合はもう少し下がることが多い
攻撃サーバ側のCPU使用率は100%近くまで上がっているか?
- 余裕がある場合は、同時接続クライアント数が足りていない
極端に同時接続クライアントが多すぎる場合スループットには出ないが、レイテンシが極端に落ちる。(リクエストが実行されるまでの待ち時間も含まれてしまうため)
こうなると問題の切り分けが大変になってしまうので、同時接続クライアント数は多すぎず、少なすぎない状態で実施するのがベスト。
(十分に負荷がかかっているのに必要以上にクライアント数をあげない)
今回は静的ファイルのため、Webサーバ単体への負荷となり、WebサーバはCPU使用率が90%以上で張り付いていたため、 十分に負荷をかけられていると判断した。
Wordpressの実際のスループット
今度は静的ページではなく、MySQLへのアクセスを伴う実際のページにアクセスしてみる。
ab -n 20000 -c 200 -k http://10.108.252.89
スループットは下記まで落ち込んだ。
Requests per second: 30.93 [#/sec] (mean)
また、DBもWebもCPU使用率90%以上で張り付いているので負荷はしっかりかかっていると見て良さそうである。
DB側がボトルネックになっていそうなので、DBのPodをスケールアップしてみる。
DBをスケールアップしてみた後
もともとが1コアを割り当てていたので、2コア割り当てるようにしてみた。
kubectl describe node Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE --------- ---- ------------ ---------- --------------- ------------- --- default wordpress-5bbd7fd785-gswtm 0 (0%) 0 (0%) 0 (0%) 0 (0%) 22h default wordpress-mysql-88898b8ff-jhj9g 2 (50%) 2 (50%) 0 (0%) 0 (0%) 22h
ab -n 20000 -c 200 -k 10.108.109.63
両者ともにCPU使用率は90%以上で、スループットが純粋に向上した。
Requests per second: 70.14 [#/sec] (mean)
ただ、またもやDBもWeb側もCPUが90%以上になっているため、 まずDB側をスケールアップしないとスループットは上がらなさそうである。 今回はアプローチを変えてDBを一定時間キャッシュさせ、DBアクセスをしないようにしてみる。
DBキャッシュ適用後
DBキャッシュを有効にした結果、スループットが劇的に改善している。
Requests per second: 3120.29 [#/sec] (mean)
DB側のCPU使用率も一瞬はねたが、一気に下がった。 一方Web側は変わらずCPU使用率が90%超えであり、 ボトルネックがDBからWebに移ったのが分かる。
まとめ
ボトルネックがWeb側に移動したのでWeb側をスケールしたいところだが、ローカルの貧弱な環境だと限界なのでローカルでの試験は一旦ここまでとする。
基本的な流れをこのさきのステップも同じで、
を繰り返していくことになる。
次回はKubernetesにアプリケーションをデプロイし、複数ノード & 攻撃サーバも負荷対象システムもスケールできる環境で実際にやってみる。
~ TO BE CONTINUED ~
Istioで割合でTraffic Managementするときにユーザごとにセッションを固定する
解決したい課題
Istioでweightによってサービスのバージョンを切り替えているとき、 リクエストの割合ベースで切り替えているだけなので、 同一ユーザでも異なるバージョンが表示されてしまう。 ユーザには少なくとも一定期間は同じバージョンを見せたいケースが多いと思うので今回はその方法を検証してみた。
Istioに用意されているSession Affinity機能
IstioではDestinationRuleで、ユーザごとに一定期間バージョンを固定化してルーティングするよう宣言できる。 https://istio.io/docs/reference/config/networking/destination-rule/#LoadBalancerSettings-ConsistentHashLB だがこれはConsistent Hashアルゴリズムによって実装されているため、PodがHPAでスケールアウト、スケールインしたときにルーティングされる向き先が変わってしまう。 また、この機能はweightの指定によってルーティングを設定しているときには併用してつかうことができない。 この機能はIstioというよりは実際にはEnvoyが担っていて、Envoy側のIssueを見る限りまだ解決されている様子はない。 https://github.com/envoyproxy/envoy/issues/8167
ではどうすればよいのか?
今回はWorkAround的な手法として、Cookieを用い、下記の方法を考えた。
あるバージョンに割り振られたトラフィックに対して、レスポンスにSetCookieヘッダーを付与し、 そこにサービスのバージョン情報を付与する、という方針を考えた。
具体的な実装
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: sample-service-gateway spec: selector: istio: ingressgateway # use istio default controller servers: - port: number: 80 name: http protocol: HTTP hosts: - "*" --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: sample-service-gateway-vs spec: hosts: - "*" gateways: - sample-service-gateway http: - match: - headers: Cookie: exact: sample-service-version=v1 route: - destination: host: sample-service subset: v1 port: number: 8080 - match: - headers: Cookie: exact: sample-service-version=v2 route: - destination: host: sample-service subset: v2 port: number: 8080 - route: - destination: host: sample-service subset: v1 port: number: 8080 weight: 50 headers: response: add: "Set-Cookie": sample-service-version=v1; Max-Age=2592000 - destination: host: sample-service subset: v2 port: number: 8080 weight: 50 headers: response: add: "Set-Cookie": sample-service-version=v2; Max-Age=2592000 --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: sample-service spec: host: sample-service subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2
これで実際にアクセスしてみると、最初は50%の確率でどちらかに割り振られ、それ以降のリクエストは同じバージョンへルーティングされる。
まとめ
今回はIstioでCookieを用いることで、バージョンを固定したルーティングを行った。 A/Bテストなど、ユーザには一貫した結果を出したい場合に活用したい。
Istio + Argo Rolloutとカナリアリリース
本記事について
Argo Rolloutを導入すると、様々なデプロイ戦略を実践することができる。 ただ少し実際の運用イメージが見えづらかったので整理してみる。
Argo Rolloutについての概要
Argo Rolloutとは通常のk8sのDeploymentに加え、カナリアリリースなどの様々なアップデート戦略をサポートするためのコンポーネント。 Rolloutという、Deploymentに対し、様々なPodのアップデート戦略をつけ加えたCRDを用いる。 Deployment同様に内部ではReplicaSetを管理していて、 アップデートのたびに新しいReplicaSetを作り、設定の通りにPodを移行させる。
カナリアリリース
一部のリクエストを新しいバージョンに流し、なにかあればロールバック、何も無ければ徐々にトラフィックを増やし、最終的に100%に切り替える。 ArgoRolloutでは、Rolloutリソースにどのように移行していくかをステップごとに宣言する。 設定できるのは下記のような項目。
- setWeightでカナリアバージョンの比重を設定する
- pauseでその状態でどのくらい待機するかを定義する
- analysisで同期的な新バージョンのヘルスチェックを挟む
ヘルスチェックについて
B/Gやカナリアの場合、定期的にヘルスチェックを行い、失敗した場合にロールバックすることができる。 実際にはAnalysisTemplateに記述し、Rolloutで指定することで、実行時にAnalysisRunリソースが作られ、 指定したヘルスチェックを実行する。 AnalysisTemplateでは、下記の実行形式をサポートしている。
Prometheusサーバからのメトリクス取得
- 一定時間内の200エラーが95%以上など
- 一定時間内の5xx系エラーが1%以下など
Job Metrics
WebHook
ちなみにAnalysisを定義しない場合はロールバックは自動で行わない。
結局Argo Rolloutを使って担保されるものはなに?
- 自動的な比重の変更と自動的なモニタリング + ロールバック
カナリアリリースの想定実行フロー
- 新バージョンデプロイ => 数%のトラフィックを新バージョンへ流す => 一定時間経って問題なければ自動的に新バージョンをStableバージョンを切り替え
- 新バージョンデプロイ => 数%のトラフィックを新バージョンへ流す => manual approveで新バージョンをStableバージョンへ切り替え
やらないといけないこと
- DeploymentはRolloutに置き換える
- カナリアリリース用のServiceを作成する
- VirtualServiceでカナリアリリースサービスとStableリリース用サービスで比重を0%, 100%に設定しておく
- ヘルスチェック用のリソース定義(AnalysisTemplate)
- Rolloutでstable用Service, canary用Service, virtualService, AnalysisTemplateを指定しておく
挙動FAQ
Podが存在しないときはどうなる?
- Deploymentと同様で、UpdatePolicyによらず、新規のReplicaSetおよびPodが作られる
どのようにWeightをコントロールしている?
Istioと連携するとどうなる?
- ルーティングがリクエストの割合ベースで分散できる
- WeightはArgoRolloutのPodがVirtualServiceの設定を随時書き換えている
カナリア用のServiceの向き先はStableと同じでいいの?
- 良い。Argo Rolloutが随時Serviceのセレクタに条件を付け足している
How to Use
- https://argoproj.github.io/argo-rollouts/getting-started/
- https://argoproj.github.io/argo-rollouts/features/canary/#canaryservice
- https://argoproj.github.io/argo-rollouts/features/analysis/#job-metrics
- https://argoproj.github.io/argo-rollouts/features/traffic-management/istio/
設定例
- stable = v1, canary = v2のケース
- AnalysisTemplateを使って、500エラーが一定期間内に3回でたらNGという定義を行っている
- istioが持つprometheusに対してクエリを投げている
- この設定では、比重を10%にし10分待ち、以降徐々に10分毎に20%ずつ増やす。50%になったら、そのまま待ち手動によるResumeを待つ。
apiVersion: v1 kind: Service metadata: name: reviews labels: app: reviews service: reviews spec: ports: - port: 9080 name: http selector: app: reviews --- apiVersion: v1 kind: Service metadata: name: reviews-canary labels: app: reviews service: reviews spec: ports: - port: 9080 name: http selector: app: reviews --- apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: name: reviews labels: app: reviews spec: replicas: 1 selector: matchLabels: app: reviews template: metadata: labels: app: reviews version: v2 spec: serviceAccountName: bookinfo-reviews containers: - name: reviews image: docker.io/istio/examples-bookinfo-reviews-v2:1.15.0 volumes: - name: wlp-output emptyDir: {} - name: tmp emptyDir: {} strategy: canary: analysis: templates: - templateName: failure-count args: - name: service-name value: reviews-canary.default.svc.cluster.local stableService: reviews canaryService: reviews-canary trafficRouting: istio: virtualService: name: reviews routes: - primary steps: - setWeight: 10 - pause: { 10m } - setWeight: 30 - pause: { 10m } - setWeight: 50 - pause: { } --- apiVersion: argoproj.io/v1alpha1 kind: AnalysisTemplate metadata: name: failure-count spec: args: - name: service-name metrics: - name: failure-count interval: 30s failureCondition: result >= 3 failureLimit: 3 provider: prometheus: address: "http://prometheus.istio-system.svc.cluster.local:9090" query: | sum(irate( istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[30s] ))
GitOpsとの兼ね合い
カナリアリリースが完了し、100%新バージョンに移行したあとに同期されたらどうなる?
- 結論: 何も変わらない
- ArgoRolloutがいじった結果とリソースが一緒になる
- StableとCanaryのServiceの向き先が両方共新Podになっていて、VirtualServiceもStableに100%向ける設定になっている
- 結論: 何も変わらない
カナリアリリース実行中に同期されたらどうなる?
- 結論: 一度ルーティングの割合がStable 100%, Canary 0%にリセットされた後、Rolloutによって同期される直前の状態に戻される
カナリアリリース時と通常リリース時でのフローの違い
デバッグ方法
- RolloutとAnalysisRunリソースを見るとデプロイの挙動が分かる
kd rollouts.argoproj.io kd analysisruns.argoproj.io
Analysisの設定は実運用上どうすれば良い?
IstioでPrometheusを有効にしてインストールし、Istioのメトリクスによって判別する
- --set values.prometheus.enabled=trueをつけてistioctl manifest applyすればprometheusが有効になるので、これをつかってメトリクスを取得することはできる
- ただ、素直に入れても可用性は担保されていないので、別途冗長化するなどの可用性対応が必要になってくる
Stackdriverなどにメトリクスを集約している場合、Goなどでデータを引っ張ってきた上で判定する
カナリアリリースした結果失敗してロールバックしたよね、というのはどのように検知すればよいか?
- Argo Rolloutの機能では通知できないので、自前の検証プログラムを使ってAnalysisRunを動かす場合は、プログラムの中にエラーケースでアラートするように記載しておく
- 例えばStackdriverならCloud Monitoringを活用して、ロールバック条件と同じ条件でアラートが飛ぶようにポリシー設定しておく
【GCP Anthos】 Fail Overの挙動から最適なマルチクラスタ構成を考える
記事の目的
前回の記事でIngress For Anthosを用いてRegionにまたがったKubernetsのマルチクラスタを構築した。 今回は1段深く掘り下げて、クラスタ内に複数のフロントサービスを持つケースを考える。 このケースでAnthosで構築したマルチクラスタのFail Overの挙動を詳しく検証し、 どんなときに、どんな条件でFail Overするのかを整理する。 また、それによって最適なマルチクラスタ構成を考える。 今回はIstioはマルチコントロールプレーン構成にしないので、クラスタ内のサービス間通信のFail Overについてはとりあげない。 【GCP Anthos】 Regionに跨って冗長化したKubernetsのマルチクラスタをロードバランシングする
題材となるサービス
今回は少し実運用を考慮し、クラスタ内に2つのフロントサービス(外部から直接ルーティングされてくるサービス)を作り、それぞれへはIngressGatewayでルーティングすることとする。
GCLB + NEGsのFailOverの検証
Anthosはクラスタ間のロードバランシングにGCPのCloud Loadbalancer(以下GCLB)とクラスタ内の任意のPodに紐付いたNEGを使う。 よってまずはGCLB + NEGの構成のFailOverの挙動をまずは整理する。
事前準備
- 東京クラスタ
- GKE 1.15
- Anthosへのメンバー登録済み
- istioインストール済み(v1.5.1)
- 台湾クラスタ
- GKE 1.15
- Anthosへのメンバー登録済み
- istioインストール済み(v1.5.1)
アプリケーションのデプロイ
ホストヘッダーによってbookinfoとzoneprinterへのルーティングの向き先を変更するようにしている。
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: zoneprinter spec: hosts: # - "*" - "www.zoneprinter.com" gateways: - single-gateway http: - match: - uri: exact: / - uri: exact: /ping - uri: exact: /location route: - destination: host: zoneprinter port: number: 80 --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: bookinfo spec: hosts: - "*" # - "www.bookinfo.com" gateways: - single-gateway http: - match: - uri: exact: / - uri: exact: /productpage - uri: prefix: /static - uri: exact: /login - uri: exact: /logout - uri: prefix: /api/v1/products fault: abort: httpStatus: 503 percentage: value: 100.0 route: - destination: host: productpage port: number: 9080
MCI, MCSリソースのデプロイ
一つのMCSを一つのIngress GatewayのPodに割り当てている。
FailOverの検証方法
MultiClusterServiceが一つなのでIngress GatewayのPodに紐付いた単一のNEGが作られる。 よってこのままだと、ヘルスチェックに使用できるパスが一つのフロントサービスへのルーティングのみになってしまうが、一旦このまま検証する。
- ヘルスチェック対象のサービス = zoneprinter, ヘルスチェック非対象のサービス = bookinfo
- ホストヘッダーがwww.bookinfo.comのものはbookinfoへ、それ以外はzoneprinterへルーティング
- ヘルスチェックは、
/
のパスへ - ダウンさせるサービスは、IstioのFault Injectionを利用し、エラーが常に返却されるように
- サンプルコードはこちら
GCLB + NEGのFail Overの挙動結果まとめ
ヘルスチェック対象でないサービスがダウン
ヘルスチェック対象のサービスがダウン
Fail Over条件のカスタマイズは不可能
つまり、ヘルスチェックによってのみFail Overするかどうかが決まる。 よって、今の構成だと、ヘルスチェック対象のサービスがUnhealthyの場合、 引きずられてHealthyなサービスまでも、冗長化された別クラスタにFail Overされてしまう。
理想的な挙動はなにか?
フロントサービスに対して個別にヘルスチェックを実施し、 一つのフロントサービスのヘルスチェックステータスが他のサービスのルーティングに影響しないようにしたい。 つまり、今回ならzoneprinterがUnhealthyだからといって、bookinfoまで別クラスタにルーティングしないでほしい。
どういう構成にするべきか
ようは各フロントサービスごとにヘルスチェックしてほしい => 各サービスごとにNEGが作られて、それらがGCLBのバックエンドとして設定されてほしいということになる、 よってフロントサービスの数だけMultiClusterServiceを作成し、単一のIngressGatewayに紐付けることにした。
補足
ちなみに、各フロントサービスに対して直接NEGを紐付ければいいのでは?と思うかもしれないが、 そうなるとIngressGatewayのEnvoyを通過せずに直接リクエスト先サービスのPodにルーティングされてしまうので、 VirtualServiceやDestinationRuleの設定が無視されてしまう。 この辺の挙動のイメージは、こちらにまとめた。 また、IngressGatewayのPodへのNEGを作るので、IngressGatewayにアタッチされているL4のGCLBは不要になる。 よってIstio Operatorなどを使ってNodePortに変更する。
実際のコード
apiVersion: cloud.google.com/v1beta1 kind: BackendConfig metadata: name: multi-gateway-bookinfo-cfg namespace: istio-system spec: healthCheck: checkIntervalSec: 15 timeoutSec: 10 healthyThreshold: 1 unhealthyThreshold: 2 type: HTTP port: 80 requestPath: / hostHeader: "www.bookinfo.com" --- apiVersion: networking.gke.io/v1 kind: MultiClusterService metadata: name: multi-gateway-bookinfo namespace: istio-system annotations: beta.cloud.google.com/backend-config: '{"ports": {"80":"multi-gateway-bookinfo-cfg"}}' spec: template: spec: selector: app: istio-ingressgateway ports: - name: multi-gateway-bookinfo protocol: TCP port: 80 targetPort: 80 --- apiVersion: cloud.google.com/v1beta1 kind: BackendConfig metadata: name: multi-gateway-zoneprinter-cfg namespace: istio-system spec: healthCheck: checkIntervalSec: 15 timeoutSec: 10 healthyThreshold: 1 unhealthyThreshold: 2 type: HTTP port: 80 requestPath: / hostHeader: "www.zoneprinter.com" --- apiVersion: networking.gke.io/v1 kind: MultiClusterService metadata: name: multi-gateway-zoneprinter namespace: istio-system annotations: beta.cloud.google.com/backend-config: '{"ports": {"80":"multi-gateway-zoneprinter-cfg"}}' spec: template: spec: selector: app: istio-ingressgateway ports: - name: multi-gateway-zoneprinter protocol: TCP port: 80 targetPort: 80
apiVersion: networking.gke.io/v1 kind: MultiClusterIngress metadata: name: multi-gateway namespace: istio-system spec: template: spec: rules: - host: "www.bookinfo.com" http: paths: - path: / backend: serviceName: multi-gateway-bookinfo servicePort: 80 - host: "www.zoneprinter.com" http: paths: - path: / backend: serviceName: multi-gateway-zoneprinter servicePort: 80 backend: serviceName: multi-gateway-zoneprinter servicePort: 80
確認
この状態でbookinfoのヘルスチェックエンドポイントに対してFaultInjectionし、 zoneprinterにcurlすると、
❯❯❯ curl -H "Host:www.zoneprinter.com" http://35.241.18.9/location <!DOCTYPE html> <h4>Welcome from Google Cloud datacenters at:</h4> <h1>Tokyo, Japan</h1> <h3>You are now connected to "asia-northeast1-a"</h3> <img src="https://upload.wikimedia.org/wikipedia/en/9/9e/Flag_of_Japan.svg" style="width: 640px; height: auto; border: 1px solid black"/>
となり、引きずられてFailOverしていないことが分かる。
また、逆にzoneprinterにFaultInjectionし、 bookinfoにアクセスすると、 台湾クラスタの方のログにアクセスが来たことが分かる。
まとめ
- GCLBはヘルスチェックによってのみFailOverする
- MCSに対して一つずつNEGが作られるので、複数のNEGを作ることで複数のフロントサービスに対してヘルスチェックを行うことができ、サービスごとにFailOverできる
- 今回のコード
IstioのTraffic Managementの動作イメージを掴もう
本記事について
Istioを使う上で、Traffic Managementを司るVirtual Service, DestinationRuleについて、 どう作用しているのかという点がわかりにくかったため、本記事にて整理し実際に動かしながら検証する。 前提として、サービスメッシュやEnvoyの基本的な設定についてはざっくりと理解している読者を想定している。
Istioとは?
Istioは、サービスメッシュの機能を統括的に導入、管理を行うためのOSSである。 内部ではEnvoyを使用していて、IstioとしてはとしてはこのEnvoyのコントロールプレーンとしての機能も担っている。 What is Istio?
主に、
Traffic Management
- 割合やヘッダーによるルーティングのコントロールやロードバランシングなどを行うための機能
- Envoyの機能であるような、サーキットブレーカー、Fault Injectionなどの機能も基本的に使える
Observability
- Promheusなどメトリクスを集計するためのソフトウェアにデータを転送する機能
Security
- mTLS通信による認証された通信や、JWTを活用した認可処理をプロキシとして挟んだりすることができる
- mTLSに用いられる証明書はIstioが独自証明書を発行してくれ、特に意識せずともmTLSをサービス間通信に導入することができる
といった機能を提供しており、Kubernetsなどのプラットフォーム連携が充実している。
Traffic Managementについて
Istioは、k8sにデプロイされたPodに自動的にサイドカーとしてEnvoyベースのProxyをInjectする機能が備わっている。 基本的には、リクエスト元サービス -> Envoy -> Envoy -> リクエスト先サービスという流れで通信することになる。 そして、このEnvoyが実際にルーティングの制御を行っている。 このEnvoyに設定情報を提供するのがk8sのCRDである下記3種類のリソースである。
- Virtual Service
- DestinationRule
各リソースについての概念説明などはなどは、 公式ドキュメント に記載されている通りである。 このさきは、minikubeにIstioと、Istioのデモアプリである、Bookinfo をデプロイして挙動を確認していく。
事前準備
- minikube v1.9.2
- Istio v1.5.2
- Kubernets v1.18.0
Ingress Gatewayの実態を見てみよう
初期状態
Ingress Gatewayを有効にしてIstioをk8sのクラスタにインストールすると、
- Service(Load Balancer Type)
- Deployment
のリソースがそれぞれ作られていることが分かる。
❯❯❯ kubectl get service -n istio-system istio-ingressgateway LoadBalancer 10.103.255.236 10.103.255.236 15020:31097/TCP,80:31587/TCP,443:32153/TCP,15029:30056/TCP,15030:32475/TCP,15031:32379/TCP,15032:32322/TCP,31400:32143/TCP,15443:32376/TCP 3d15h
❯❯❯ kubectl get deployments.apps -n istio-system NAME READY UP-TO-DATE AVAILABLE AGE istio-ingressgateway 1/1 1 1 3d15h
そして、このDeploymentによって管理されているPodの定義を見てみると、
❯❯❯ kubectl get pod -n istio-system istio-ingressgateway-6489d9556d-sp8f2 -o yaml | grep image: image: docker.io/istio/proxyv2:1.5.2
のようになっているのが分かる。 このproxyvというのが中でEnvoyが動いてる。 この状態でRoutingの設定を覗いてみると、まだなにも定義していないためすべて404になるようになっている。
❯❯❯ istioctl -n istio-system proxy-config route istio-ingressgateway-6489d9556d-sp8f2 -o json | yq -y '.' - - name: http.80 virtualHosts: - name: blackhole:80 domains: - '*' routes: - name: default match: prefix: / directResponse: status: 404 validateClusters: false
GatewayリソースとGatewayリソースに紐付けたVirtualServiceの作成
では次に下記リソースをapplyし、IngressGatewayにAttachしたGatewayリソースと、VirtualServiceを作ってみる。
❯❯❯ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
そうすると、下記のようなルーティングルールが作成されている。
❯❯❯ istioctl -n istio-system proxy-config route istio-ingressgateway-6489d9556d-sp8f2 -o json | yq -y '.' - - name: http.80 virtualHosts: - name: '*:80' domains: - '*' - '*:80' routes: ... - match: path: /productpage caseSensitive: true route: cluster: outbound|9080||productpage.default.svc.cluster.local timeout: 0s retryPolicy: retryOn: connect-failure,refused-stream,unavailable,cancelled,resource-exhausted,retriable-status-codes numRetries: 2 retryHostPredicate: - name: envoy.retry_host_predicates.previous_hosts hostSelectionRetryMaxAttempts: '5' retriableStatusCodes: - 503 maxGrpcTimeout: 0s metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/bookinfo decorator: operation: productpage.default.svc.cluster.local:9080/productpage ...
このように、IngressGatewayにAttachしたGatewayを作成し、さらにそのGateway指定したVirtual Serviceを作ると、 L4/LBから直接ルーティングされてくるEnvoyにルーティングルールが追加される。 また、向き先としてはPodのIPが解決されている。
❯❯❯ istioctl -n istio-system proxy-config endpoint istio-ingressgateway-6489d9556d-sp8f2 -o json | yq -y '.' - | grep "outbound|9080||productpage.default.svc.cluster.local" -A 30 - name: outbound|9080||productpage.default.svc.cluster.local addedViaApi: true hostStatuses: - address: socketAddress: address: 172.17.0.12 portValue: 9080 weight: 1
Gatewayに紐付けないVirtualService
次に下記リソースをapplyし、reviewsサービスにはv2に50%、v3に50%の割合でルーティングされるようにしてみる。
❯❯❯ kubectl apply -f samples/bookinfo/networking/destination-rule-reviews.yaml ❯❯❯ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-v2-v3.yaml
まずIngress Gateway側のEnvoyの設定を確認する。
❯❯❯ istioctl -n istio-system proxy-config route istio-ingressgateway-6489d9556d-sp8f2 -o json | yq -y '.' -
今回はGatewayにAttachしていないVirtualServiceを作成したので、特にルーティング設定に変化がないことが分かる。
ではアプリケーション側のPodに配置されたEnvoyの設定はどうだろうか?
❯❯❯ istioctl proxy-config route productpage-v1-7f44c4d57c-nfnjd -o json | yq -y '.' - - name: reviews.default.svc.cluster.local:80 domains: - reviews.default.svc.cluster.local - reviews.default.svc.cluster.local:80 routes: - match: prefix: / route: weightedClusters: clusters: - name: outbound|80|v2|reviews.default.svc.cluster.local weight: 50 - name: outbound|80|v3|reviews.default.svc.cluster.local weight: 50 timeout: 0s retryPolicy: retryOn: connect-failure,refused-stream,unavailable,cancelled,resource-exhausted,retriable-status-codes numRetries: 2 retryHostPredicate: - name: envoy.retry_host_predicates.previous_hosts hostSelectionRetryMaxAttempts: '5' retriableStatusCodes: - 503 maxGrpcTimeout: 0s metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviews decorator: operation: reviews:80/*
長いので省略しているが、reviewsへのトラフィックのときに下記2つの向き先へ50%の割合でルーティングしているのが分かる。
endpointの設定を見てみると、それぞれv3のPodのIP、v2のPodのIPを指していることが読み取れる。
❯❯❯ istioctl proxy-config endpoint productpage-v1-7f44c4d57c-nfnjd -o json | yq -y '.' - | grep reviews -A 30 - name: outbound|9080|v3|reviews.default.svc.cluster.local addedViaApi: true hostStatuses: - address: socketAddress: address: 172.17.0.10 portValue: 9080 - name: outbound|9080|v2|reviews.default.svc.cluster.local addedViaApi: true hostStatuses: - address: socketAddress: address: 172.17.0.11 portValue: 9080
ちなみにこのルーティング設定は、Bookinfoアプリケーション内のそれぞれのサービスのサイドカーEnvoyの設定に組み込まれている。 (正確にはIstio Pilotの仕組みを使って設定が動的にInjectされている) つまり、リクエストを送る側、つまりクライアント側のプロキシ(Envoy)でロードバランシング、ルーティング制御を行い、Podにトラフィックを送っているのである。
DestinationRuleを削除してみる
最後に上記までの状態からDestinationRuleだけ削除してみる。
❯❯❯ kubectl apply -f samples/bookinfo/networking/destination-rule-reviews.yaml
ルーティングルールとしては変化していないように見える。
❯❯❯ istioctl proxy-config route productpage-v1-7f44c4d57c-nfnjd -o json | yq -y '.' - clusters: - name: outbound|80|v2|reviews.default.svc.cluster.local weight: 50 - name: outbound|80|v3|reviews.default.svc.cluster.local weight: 50
だが、エンドポイント定義としてはv2, v3が消えてしまっているのが分かる。
❯❯❯ istioctl proxy-config endpoint productpage-v1-7f44c4d57c-nfnjd -o json | yq -y '.' - | grep reviews - name: outbound|9080||reviews.default.svc.cluster.local
まとめ
【GCP Anthos】Anthos Service Mesh + Ingress For Anthosを組み合わせたk8sのマルチクラスタを構築する
この記事について
前回の記事でIngress For Anthosを使ったマルチクラスタによるk8sの冗長化について触れた。 今回はManagedなIstioを提供するAnthos Service Meshも取り入れ、 サービスメッシュを導入したマルチクラスタを実際に構築してみる。
Anthos Service Meshとは?
- 基本的にはIstioだが、Configuration ProfileがAnthos用にカスタムされて提供されている。
- 諸々Managedな代わりに色々制約がある。
- サポート機能
何がマネージドになっているのか?
サービスの依存関係、SLOモニタリング、各サービスのメトリクス収集がManagedに集約され、ダッシュボードで統括的にみることができる
- Istio Proxyのmetrics送信部分がおそらくManagedになっていて、ほぼ何もしなくてもAnthosに自動的に送られるようになっているぽい
GKE環境下ではCitadelがManagedになっていて、ルート証明書、サーバ証明書周りの管理をGoogleに任せられる
- ほぼ何も意識せずにmTLS機能を有効にできる
- Cloud IAPと連携ができる(GCPのサービスアカウントによる認証プロキシをメッシュに挟める)
Traffic DirectorがManagedなIstio Pilotとして提供されているらしい(まだ未検証)
クラスタの要件
- 4つのvCPUを持つ、n1-standard-4のノードが4つ以上存在すること https://cloud.google.com/service-mesh/docs/gke-install-existing-cluster#requirements
Your GKE cluster must meet the following requirements: - At least four nodes. If you need to add nodes, see Resizing a cluster - The minimum machine type is n1-standard-4, which has four vCPUs. If the machine type for your cluster doesn't have at least four vCPUs, change the machine type as described in Migrating workloads to different machine types - Use a release channel rather than a static version of GKE
本記事で構築するクラスタのアーキテクチャ図
- アプリケーションとしては、Istioで用意されているでもアプリのBooknfoを用いる。
- 注意点としては、
事前準備
n1-standard-4, 4つのvCPUのインスタンスを余裕を持って5つ程度でノードプールを構築し、クラスタを作っておく
手順について
基本的にはこの手順でIstioをインストールする ドキュメントの通りではあるが、ピックアップした手順を記載しておく。
Anthosの仲間に加える
- role/gkehub.connectの権限を持ったサービスアカウントを作成する
- 環境変数の設定
export PROJECT_ID= export PROJECT_NUMBER= export CLUSTER_NAME= export CLUSTER_LOCATION= export WORKLOAD_POOL=${PROJECT_ID}.svc.id.goog export MESH_ID="proj-${PROJECT_ID}" export SERVICE_ACCOUNT_NAME=anthos-connector export SERVICE_ACCOUNT_KEY_PATH=
- クラスタをAnthosの仲間に加える
gcloud container hub memberships register $CLUSTER_NAME \ --gke-cluster=${CLUSTER_LOCATION}/${CLUSTER_NAME} \ --service-account-key-file=$SERVICE_ACCOUNT_KEY_PATH
Anthos Service Meshの設定
- クラスタのラベルにメッシュIDを設定(モニタリングに用いられる)
gcloud container clusters update ${CLUSTER_NAME} \ --update-labels=mesh_id=${MESH_ID}
gcloud container clusters update ${CLUSTER_NAME} \ --workload-pool=${WORKLOAD_POOL}
- Stackdriver MonitoringとLoggingを有効化
gcloud container clusters update ${CLUSTER_NAME} \ --enable-stackdriver-kubernetes
- 自分にクラスタ管理者のロールを当てる
kubectl create clusterrolebinding cluster-admin-binding \ --clusterrole=cluster-admin \ --user="$(gcloud config get-value core/account)"
Anthos Service Mesh用のConfiguration Profileを用いてIstioをインストールするため、Anthos用のistioctlをダウンロードする
Anthos Service MeshのIstioをInstall
istioctl manifest apply --set profile=asm \ --set values.global.trustDomain=${WORKLOAD_POOL} \ --set values.global.sds.token.aud=${WORKLOAD_POOL} \ --set values.nodeagent.env.GKE_CLUSTER_URL=https://container.googleapis.com/v1/projects/${PROJECT_ID}/locations/${CLUSTER_LOCATION}/clusters/${CLUSTER_NAME} \ --set values.global.meshID=${MESH_ID} \ --set values.global.proxy.env.GCP_METADATA="${PROJECT_ID}|${PROJECT_NUMBER}|${CLUSTER_NAME}|${CLUSTER_LOCATION}" \ --set values.global.mtls.enabled=true
- 任意のnamespaceに対してSidecarのAuto Injectionをdefault有効にする
kubectl label namespace default istio-injection=enabled
アプリケーションのデプロイ
下記のプロジェクトをダウンロードしてくる
ServiceとDeploymentリソースをデプロイ
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
- VirtualServiceをデプロイ
kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml
- DestinationRuleをデプロイ
kubectl apply -f samples/bookinfo/networking/destination-rule-all-mtls.yaml
kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
Ingress For Anthosと連携する
- Config用のクラスタを登録(本記事ではアプリケーションと兼用している)
gcloud alpha container hub ingress enable \ --config-membership=projects/$PROJECT_ID/locations/global/memberships/$CLUSTER_NAME
[ { "op": "replace", "path": "/spec/type", "value": "NodePort" }, { "op": "remove", "path": "/status" } ]
kubectl -n istio-system patch svc istio-ingressgateway \ --type=json -p="$(cat istio-ingressgateway-patch.json)" \ --dry-run=true -o yaml | kubectl apply -f -
- MultiClusterServiceをデプロイ
kubectl apply -f - <<EOF apiVersion: networking.gke.io/v1 kind: MultiClusterService metadata: name: bookinfo namespace: istio-system annotations: beta.cloud.google.com/backend-config: '{"ports": {"80":"zone-health-check-cfg"}}' spec: template: spec: selector: app: istio-ingressgateway ports: - name: web protocol: TCP port: 80 targetPort: 80 EOF
- BackendConfigをデプロイ
kubectl apply -f - <<EOF apiVersion: cloud.google.com/v1beta1 kind: BackendConfig metadata: name: bookinfo-health-check-cfg namespace: istio-system spec: healthCheck: checkIntervalSec: 30 timeoutSec: 10 healthyThreshold: 1 unhealthyThreshold: 2 type: HTTP port: 80 requestPath: /productpage EOF
- MultiClusterIngressをデプロイ
kubectl apply -f - <<EOF apiVersion: networking.gke.io/v1 kind: MultiClusterIngress metadata: name: bookinfo-ingress namespace: istio-system spec: template: spec: backend: serviceName: bookinfo servicePort: 80 EOF
結果
AnthosによってGlobalなGCLBが作成され、Ingress GatewayにルーティングされるNEGがバックエンドとして登録されている
GCLBのVIPにアクセスすることでアプリケーションが表示される
サービスメッシュのダッシュボードはこんな感じになっている(SLOモニタリングの機能もある)
試しにIstioでルーティングを変えてみる
Istioの代表的な機能である、Traffic Managementを試してみる。 今回は試しにreviewsサービスのv1に50%、v3に50%の割合でルーティングされるようにしてみる。
kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml
何度かリロードするとReviewの部分が切り替わることが分かる。 各クラスタごとにIstioのコントロールプレーンを持っているので、 マルチクラスタによって冗長化しているケースはどちらにもapplyしてあげる必要がある。 (v3は赤い星、v1は星無しで出る)
まとめ
Anthosを活用することでGKEに関しては、 マルチクラスタ構成による冗長化、ManagedなIstioの導入を組み合わせてかんたんに構築できることが分かった。 まだまだAnthosに関しては発展途上で情報も全然ない印象ではあるが、機能としては便利なので今後に期待したい。
参考
https://speakerdeck.com/jukuwa/gcpug-tokyo-istio-1-dot-5-day-anthos-service-mesh
【GCP Anthos】 Regionに跨って冗長化したKubernetsのマルチクラスタをロードバランシングする
Anthosとは?
アプリケーションを、OSSをベースにモナタイゼーションするための統括的な機能を提供するプラットフォーム。
- モナタイゼーションとは?
- マイクロサービス化
- インフラとアプリケーションの疎結合化
- サーバレス
- 自動化
- なるべくManagedへ
- モナタイゼーションとは?
Anthosが提供する機能は現時点では下記の資料が一番わかり易い
本記事の目的
本記事ではマルチクラスタを構築し、Anthosの機能を使ってクラスタにまたがって負荷分散することができるかに焦点をあてる。 GKE HubのIngress For Anthosという機能を用い実現する。 本当はマルチクラウドなクラスタで負荷分散させたかったが、GKE以外のクラスタはまだサポートされていないようだ。
Anthosへのクラスタ登録とクラスタモニタリングの仕組み
Anthosはクラスタ管理のため、各クラスタ内にGKE Connector Agentをデプロイする。 このAgentがAnthosに情報を集約し、Anthosのコントロールプレーンからの命令を各クラスタで実行することになる。 引用: https://cloud.google.com/anthos/multicluster-management/connect/security-features
Ingress For Anthosのアーキテクチャ
Anthosによるクラスタ間のロードバランシングは、Ingress For Anthosという仕組みを用いて実現する。 これは下記イメージのようなアーキテクチャになっている。
引用: https://cloud.google.com/kubernetes-engine/docs/concepts/ingress-for-anthos
登場人物について
Config Cluster
Anthos Member Cluster
- Anthosが管理するクラスタ郡のメンバー
Anthos Ingress Controller
MultiClusterService(MCS)
MultiClusterIngress(MCI)
- このリソースが作成されると、GCLBとZone NEG(MCSによって各クラスタに作成されたServiceに紐づく)が作られ、GCLBのバックエンドにはNEGが紐付けられる
動作イメージ
- MultiClusterIngressがConfig Clusterに作成されたタイミングで、Anthos Ingres Controllerが単一のGCLBを作成し、それぞれのクラスタに紐づくZone NEGをバックエンドとしてぶら下げる
Config Clusterの可用性について
当然疑問に上がってくるのは、Config Clusterの可用性はどう担保するのか?というところ。 ちなみにConfig Clusterが死ぬとMultiClusterIngressとMultiCluserServiceの設定変更ができなくなる。 つまりすでに適用した設定でのロードバランシングには影響しないが、なるべくRegionalなクラスタで運用するようなどしたほうが良い。 ただ、個人的にはサービスダウンに直結するわけではない & MultiClusterServiceとMultiClusterIngressを頻繁にメンテすることはあんまりなさそうなのでそこまでガチな可用性はなくても良さそうに感じている。 (ここにマルチクラスタで冗長化とかあんまやりたくはない...)
実際に動かしてみる
セットアップ
GKEクラスタをAnthosのクラスタメンバーに登録
gcloud container hub memberships register gke-anthos-asia \ --project=[project_name] \ --gke-cluster=asia-northeast1-a/gke-anthos-asia \ --service-account-key-file=./service-account.json
gcloud container hub memberships register gke-anthos-eu \ --project=[project_name] \ --gke-cluster=europe-north1-a/gke-anthos-eu \ --service-account-key-file=./service-account.json
ConfigクラスタをAnthosに設定
gcloud alpha container hub ingress enable \ --config-membership=projects/[project_name]/locations/global/memberships/gke-anthos-config
アプリケーションのデプロイ
- このドキュメントのリンクのセクションに従ってzoneprinterをデプロイする https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-for-anthos#deployment_tutorial
結果
クラスタがAnthosのメンバーとして認識された様子(本記事の手順では割愛しているが、EKSも検証のため入れてみた)
ロードバランサーのバックエンドに自動的に作成されたNEGが紐付いている
中身はクラスタのMultiClusterServiceによって作成されたHeadlessServiceから取得されたPodのIPに紐付いている
試しにcurlしてみると、GCLBがAnyCastで最寄りのクラスタ(ネットワーク的に)にルーティングしてくれるため、常に東京クラスタの結果が帰ってくる
❯❯❯ curl 34.107.232.154/ping {"Hostname":"34.107.232.154","Version":"1.0","GCPZone":"asia-northeast1-a","Backend":"zone-ingress-5f6ff94966-2phdz"}%
試しにAsiaクラスタを殺してみる
- deploymentを殺し、Podが存在しないようにする
❯❯❯ kubectl delete -f deployment.yaml deployment.apps "zone-ingress" deleted
❯❯❯ curl 34.107.232.154/ping {"Hostname":"34.107.232.154","Version":"1.0","GCPZone":"europe-north1-a","Backend":"zone-ingress-5f6ff94966-7hlt7"}%
まとめ
GKEのクラスタであればかんたんにロードバランシングできるので、 クラスタ自体のアップデートをしたいが、ダウンタイムを許容したくないときやRegion障害などに活用できそうだということがわかった。 EKSなどのサポートも進めばよりいっそう便利なので対応を待望しています。