広告技術部のUT@mocyutoです。
大幅コスト削減シリーズ第二弾です。
前回はこちら tech.gunosy.io
今回はアベイラビリティゾーン(AZ)間通信のコストをIstioのlocality load balancingを使って削減した話になります。
概要
みなさんはマイクロサービスを導入しているでしょうか? 最近はモジュラモノリスが流行り始めている雰囲気を感じてきていますが、弊社の広告配信サーバは以下のようなマイクロサービス化された設計(と言っても2つのサービスしかないのですが)になっています。
一般的にクラウドプロバイダ上で構築している場合、耐障害性を高めるために複数AZ、複数リージョンに分散させることが基本になるかと思います。 弊社では、単一リージョン複数AZに分散させて稼働しています。 リージョン間の通信にコストがかかるというのは直感的にわかるかと思いますが、AZ間でも通信コストはそれなりにかかってしまいます。 弊社はAWS上に構築しているため、AWSの通信コストを見てみるとAZ間の通信コストは $0.01/GBです
オンデマンドインスタンスの料金 - Amazon EC2 (仮想サーバー) | AWS
内部の通信だからといってガンガン通信していると実はすごいコストがかかっているということが発生してしまいます。 弊社のサービスでは内部通信ということでかなり大きなデータを載せて通信させていたので、少なくないコストが発生していました。
Istioとは
弊社のマイクロサービスはEKS上に構築されており、さらにサービス間通信にはIstioというサービスメッシュを利用しています。
Istioの導入の記事はこちら
Istioとはenvoy proxyをベースにしたk8s上のトラフィック制御のためのコンポーネントです。
Istioの導入メリットとしては、envoyの複雑な設定をラップしてくれてるため、設定が簡潔になります。 またenvoyはトレーシングをサポートしているので、Istioの管理下のトラフィックに対して様々な監視が適用できます。 しかしその反面、細かい挙動を制御したいという場合は、envoyの仕組みを知る必要があり、運用としては難しい点もあります。
Istioの機能としては大きく4つあります。
- Virtual services
- 仮想Serviceといってk8sのserviceを模したもので、Istio管理下であればVirtualServiceにもk8sのエンドポイントと同じようにリクエストを送ることができます。 VirtualServiceは実際の宛先を複数登録したり条件を設定することができ、load balancingのような働きをします。
- Destination rules
- 宛先を決めるルールの設定項目で、こちらもload balancingのような働きをします。 VirtualServiceは受け取る側の設定に対し、Destination rulesは送る側の設定になります。
- Gateways
- Istioが管理しているサービスメッシュの出入り口を制御するコンポーネントです。 Gatewayを内部のエンドポイントに紐付ける場合、VirtualServiceを指定します。
- Service entries
- Service entriesはMeshの外側のコンポーネント(例えばDBなど)に対して、仮想のServiceを作ります。 これを作ることでVirturalServiceと同様に接続させることが可能になります。
これらのリソースからIstioがk8sのpodのサイドカーに以下のコンテナを自動で作成します。
- k8sのinit-containerという仕組みを使ってistio-init
- envoy proxyコンテナとしての istio-proxy
詳しくは Istio / Demystifying Istio's Sidecar Injection Model に記載されています。
どのようにコスト削減したか
IstioのDestination rulesにはlocality load balancingという機能があります。 これは、自分のpodと近いpodはどのノードにあるかを見て通信させる機能です。 具体的には、同一のAZかどうかや同一のリージョンかどうかなどを見て同一リージョン同一AZのpodを優先して通信する機能です。 もちろんフェイルオーバーの仕組みもあり、どのノードにフェイルオーバーするかの設定(ただしIstioのバージョンによりできる範囲が違うので注意)もできます。
具体的には以下のように設定します。
apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: dest-rule1 spec: host: host_name # ホスト名 trafficPolicy: loadBalancer: simple: ROUND_ROBIN # ラウンドロビンでのアクセス localityLbSetting: # これがlocality load balancingの設定 enabled: true failoverPriority: # failoverの順序 - "topology.kubernetes.io/region" - "topology.kubernetes.io/zone" outlierDetection: consecutive5xxErrors: 5 # 連続で5xxが発生した場合に切り離される回数 interval: 1s # チェック頻度 baseEjectionTime: 1m # 切り離されるまでの時間
failoverPriorityでフェイルオーバーするノード属性の優先度を決めます。 フェイルオーバーを実施させるには上記の設定のようにoutlierDetectionを設定する必要があります。
ちなみにoutlierDetectionにおけるgRPC接続の場合は、grpc-statusからマッピングされたHTTPステータスを利用します
Outlier detection — envoy 1.27.0-dev-4e672c documentation
このlocality load balancingを用いるとマイクロサービス間の通信がAZをまたがないので、通信コストがなくなります。
その結果、以下のように通信コストを1/4に抑えることができました。
まとめ
マイクロサービスを利用する際、開発効率の話が主軸になりやすいですが、実は通信コスト部分にも目を向けると意外にコストがかかっていることがわかります。 Istio自体envoyの上に成り立っているので仕組みを理解するのは大変ですが、そこから得られる恩恵もあるので一度検討してみるのもよいかもしれません。