ZOZOTOWNマイクロサービスの段階的移行を支えるカナリアリリースとサービス間通信における信頼性向上の取り組み

はじめに

SRE部プラットフォームSREチームの川崎 @yokawasa です。

ZOZOTOWNではモノリシックなアーキテクチャーから、優先度と効果が高い機能から段階的にマイクロサービス化を進めています。本記事では、そのZOZOTOWNの段階的なマイクロサービス移行で実践しているカナリアリリースとサービス間通信の信頼性向上の取り組みについてご紹介します。

なお、ZOZOTOWNのリプレイス戦略ついてはこちらのスライドが参考になります。

speakerdeck.com

さて、ZOZOTOWNマイクロサービスプラットフォーム(以下、プラットフォーム)はAWS上に構築しており、コンテナーアプリ基盤にマネージドKubernetesサービスであるEKSを採用しています。また、複数サービスを単一Kubernetesクラスターで稼働させる、いわゆるマルチテナントクラスター方式を採用しています。

下記イメージは、そのマルチテナントクラスター(以下、クラスター)に展開されているマイクロサービスとクライアントからマイクロサービスへのリクエストフローを表した概念図です。本記事ではこの中の青点線で囲んだ部分にフォーカスしてその取り組みをご紹介します。

ZOZO API Gatewayを軸にした段階的なマイクロサービスへの移行

本プラットフォームでは、クライアントが直接サービスと通信するのではなく、すべてのリクエストをZOZO API Gatewayと呼ばれるアプリケーションを経由してサービスにルーティングするAPI Gatewayパターンを採用しています。

ZOZO API GatewayはURIパスベースのルーティング機能を提供し、ルーティング先であるターゲットをまとめたターゲットグループという単位でカナリアリリースの機能を提供します。また、ターゲットへのルーティングにおいてリトライ制御、タイムアウトなど通信の信頼性を高める機能を提供します。

特定のマイクロサービス移行に際して、これらの機能のおかげで古いエンドポイントから新しいものへの切り替えに対しても、クライアントがURI変更の影響を受けることなく安定的かつ段階的な切り替えが可能になります。

下図は、/searchで始まるパスのリクエストをターゲットであるZOZO Search API PrimaryとCanaryにそれぞれ90対10で加重ルーティングするイメージです。

ZOZO API GatewayはGolangで独自実装しており、アルゴリズムや細かな動作制御パラメーター、可用性の機能などZOZOTOWNのさまざまな独自要件に対して柔軟に対応が可能です。まさに、ZOZOTOWNのマイクロサービスアーキテクチャーへの段階的な移行を支える中心的なコンポーネントと言えます。

ZOZO API Gatewayについては各機能や実装レベルの詳細が書かれた人気の記事があるので、是非ご覧ください。

techblog.zozo.com techblog.zozo.com

ALB加重ルーティングによるAPI Gatewayのカナリアリリース

ZOZO API Gatewayをカナリアリリースするための手法を紹介します。

ZOZO API Gatewayの前段にはApplication Load Balancer(以下、ALB)があり、クライアントからのすべてのリクエストはALBからZOZO API Gatewayにフォワードされます。ZOZO API GatewayのカナリアリリースはこのALBが持つ加重ルーティング機能を活用して実現します。そして、このALB加重ルーティング設定の自動化を実現するのがAWS Load Balancer Controller(以下、コントローラー)です。

このコントローラーをクラスターにデプロイすると、Ingressリソースに指定するパスベースのルーティングや接続ターゲットの情報に基づきALBが作成され、ALBのTargetGroupsとしてアプリケーションPodに直接ルーティングするよう、自動的にALBリスナールールを設定します。

以下、ZOZO API GatewayにおけるIngressマニフェストの設定例を紹介します。

TargetGroups部分にカナリアリリースにおける既存のサービスのzozo-api-gateway-primaryと一部のリクエストを振り分けたい新しいサービスであるzozo-api-gateway-canaryを登録します。それぞれの比重を変更してクラスターに適用すると、Ingressリソースの更新イベントを常時モニタリングしているコントローラーにより自動的に指定された比重でALBリスナールールが更新され、ZOZO API Gatewayへのトラフィックの加重率が変更されます。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: zozo-api-gateway-ingresss
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/actions.forward-external-traffic: |
      { 
        "Type":"forward",
        "ForwardConfig":{ 
          "TargetGroups":[ 
            { 
                "ServiceName":"zozo-api-gateway-primary",
                "ServicePort":"80",
                "Weight":90
            },
            { 
                "ServiceName":"zozo-api-gateway-canary",
                "ServicePort":"80"
                "Weight":10
            }
          ]
        }
      }
spec:
  rules:
    - http:
        paths:
          - path: /*
            backend:
              serviceName: forward-external-traffic
              servicePort: use-annotation

ALB Load Balancer Controllerのannnotation設定について詳しくは公式リファレンスを参照ください。

Istioを活用したサービス間通信のトラフィック制御

Istioを活用したサービス間通信におけるトラフィック制御についてご紹介します。なお、本記事ではサービスメッシュの概要や、Istioそのものに関する説明はしません。

Istioサービスメッシュの導入背景について

ZOZO API GatewayからマイクロサービスへのルーティングにおいてはZOZO API Gatewayのトラフィック制御機能が使えますが、マイクロサービスと他サービス(クラスター外のサービスを含む)間の通信に対しても一貫した機能を提供したいという思いがありました。

これを実現するために出てきた選択肢に以下の3つがありました。

  1. マイクロサービス間の通信でもZOZO API Gatewayを介し、一貫したトラフィック制御機能を提供する
  2. タイムアウトやリトライ制御などの機能を提供する共通ライブラリを各アプリケーションに組み込む
  3. サービスメッシュを活用し、ソースコードを変更することなくアプリケーションPodにSidecarパターンでプロキシを注入して、透過的に機能を追加する

1については、ZOZO API Gateway独自に設定しているクライアント認証設定の手間と、ZOZO API Gatewayへの負荷を考慮すると現実的ではありませんでした。また2は、ZOZOTOWNのように利用言語やフレームワークが統一されていない多様な環境をサポートする必要がある状況下では難しさがありました。最終的に、3のサービスメッシュがもっとも現実的であるという結論に至りました。

そして、我々は次のような理由からIstioを選定して、2020年後半から検証を進めました。

ZOZO Aggregation APIにおける設定例

3月18日にZOZOCOSMEやZOZOVILLAがリリースされましたが、この裏側で利用されているマイクロサービスではじめてIstioを導入しました。

このマイクロサービスはZOZO Aggregation APIと呼ばれ、いわゆるBackends for Frontends(BFF)層としての複数APIの結果を集約し、フロントエンドの仕様に特化したレスポンスを返却します。

ZOZO Aggregation APIでは、下図のようにSidecarプロキシでネットワーク接続されたサービスメッシュ内ネットワーク(以下、メッシュネットワーク)のサービス間の通信とメッシュネットワーク外にあるサービスとの通信の2パターンにおいてIstioによるトラフィック制御の設定をしています。

はじめに、メッシュネットワーク内のZOZO Aggregation APIと検索機能を提供するZOZO Search APIサービス間の通信の設定例を紹介します。

以下のサンプルはVirtual Serviceというルーティングの振る舞いを定義するカスタムリソースのHTTPルーティング部分ですが、ここでZOZO Search APIへの加重ルーティングの比重、タイムアウトやリトライ制御を設定します。今回の例では、上図のように新旧それぞれ90対10の加重ルーティングと、5秒タイムアウトで5xxや接続エラーに対して最大2回のリトライ制御を設定しています。なお、サービス間通信設定では他にもDestination RuleというIstioのカスタムリソースの定義が必要になりますが、ここでは省略しています。

  http:
  - route:
    - destination:
        host: zozo-search-api.searchns.svc.cluster.local
        subset: zozo-search-api-primary
      weight: 90
    - destination:
        host: zozo-search-api.searchns.svc.cluster.local
        subset: zozo-search-api-canary
      weight: 10
    retries:
      attempts: 2
      perTryTimeout: 4s
      retryOn: 5xx,connect-failure
    timeout: 5s

次に、メッシュネットワーク外にあるBackend APIサービスとの通信設定を紹介します。

以下のサンプルもメッシュネットワーク内サービス間通信と同じくVirtual ServiceのHTTPルーティング部分です。ここでは、6秒タイムアウトで5xxや接続エラーに対して最大2回のリトライ制御を設定しています。なお、メッシュネットワーク外とのサービス間通信設定では他にもService Entryというカスタムリソースの定義が必要になりますが、ここでは省略しています。

  http:
  - route:
    - destination:
        host: zozo-backend-api.zozo-sample-service.com
    retries:
      attempts: 2
      perTryTimeout: 3s
      retryOn: 5xx,connect-failure
    timeout: 6s

分散トレーシング

上述の通り、本プラットフォームでは、ALBからZOZO API Gatewayへのルーティング、そこからマイクロサービスへのルーティングという通信連携があります。さらに、Istioを導入してからはサービスメッシュプロキシを通じてサービス間通信が透過的にルーティングされるため、より一層複雑性が増しています。

こういった中で、問題の発生箇所やパフォーマンスのボトルネック、信頼性の機構が期待通りに機能しているかなどをログやメトリクスのみから追うのは大変困難であることが容易に想像できます。

このような問題の解決策として本プラットフォームでは構築初期の頃から分散トレーシングを導入しており、バックエンドサービスとしてDatadog APMを活用しています。

ここでは、先日リリースしたZOZO Aggregation APIへのリクエストの処理状況を表すフレームグラフをご紹介します。ZOZO API GatewayからZOZO Aggregation APIにルーティングされ、そこから複数サービス間との通信で集約された結果がZOZO API Gatewayにより返されるまでの処理状況が一気通貫で確認可能です。

本プラットフォームにおけるDatadogを活用した可観測性の取り組みについて詳細はこちらの発表資料を参照ください。

speakerdeck.com

構成管理とCI/CD

本プラットフォームでは、インフラからアプリまでサービス環境の構成は可能な限りIaC化しており、その構築・更新はCI/CDパイプラインから行うことを基本としています。今回ご紹介した各所のカナリアリリースや、通信の信頼性のための設定についても当然ながら下図のようにCI/CDを起点としてサービス環境にロールアウトされる流れにしています。

なお、ZOZOTOWNマイクロサービスプラットフォームのCI/CD戦略に関しては、こちらの記事で解説していますので是非ご覧ください。

techblog.zozo.com

ちなみに、Istioの構成管理ですが、Istio OperatorというKubernetes Operatorを利用して、IaC化とCI/CDを通じた自動ロールアウトを実現しています。IstioOperatorカスタムリソースに構成設定を定義してクラスターにデプロイすると、カスタムリソースの定義を元にインストールやアップグレード、Istio全体の設定やコンポーネントごとの設定を自動ロールアウトしてくれます。

まとめ

ビックバンアプローチで全体を一気にマイクロサービスアーキテクチャーとしてリリースするケースがある一方、既存機能を動かしながら多様な環境状況を考慮しつつ段階的に移行するケースがあります。本記事では後者のケースにおいてそれを支えるためにZOZOTOWNで実践しているカナリアリリースとサービス間通信の信頼性向上の取り組みについてご紹介しました。

本記事では深く紹介できませんでしたが、ZOZO Aggregation APIやIstioについてはプロダクションリリース要件をクリアするまでにさまざまなチャレンジがありました。また、Istioは今後マイクロサービス全体にその利用広げていき、サーキットブレーカーをはじめとしたより高度な機能活用を行っていく予定です。これらについては別の記事にてその詳細をご紹介できればと思っております。

さいごに

ZOZOテクノロジーズでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。

tech.zozo.com

カテゴリー