AWS App Meshを使ったABテスト環境の構築

# 概要
サイト内検索の読者体験を向上させるため、社内でABテスト環境を構築しました。
社内では現在マイクロサービスアーキテクチャを採用しており、サービスメッシュでAWS App Meshを利用しています。
この記事ではAWS App Meshだとこういった方法でABテストができる、という知見をまとめます。

# 実現したかったこと
- 読者体験の向上を目指したかったので行動データを元に検索エンジンやチューニングを比較したい
- トレンド性のあるサービスなので、同時に比較したい
- UIのABテストでFirebase RemoteConfigを利用しているので同じツールでテストをし、既存のアクセス解析ツールで分析したい

# ハイレベルアーキテクチャ
Control PlaneのAB Testing Controllerでは、データアナリストやPOなどの人がABテストの設定を行います。
これはどういった人にどの程度AとBをそれぞれ公開するのか、制御するための設定です。
Client Applicationはその割り当てられた情報をエンコードしてリクエストヘッダに付与してAB Testing Converterに送信します。
AB Testing ConverterはData Plane（バックエンドシステム）で解釈できるフォーマットに変換し、HTTPであればリクエストヘッダ、gRPCであればMetadataにその情報を付与してAB Testing Dispatcherに送信します。
AB Testing Dispatcherは付与されているHTTPリクエストヘッダやgRPC Metadataを元に適切な場所へのルーティングを行います。
今回は読者がどう行動したか、といった情報はClientApplicationで取得しAnalyzerに送信するようにしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84972/0857dec6-7763-c482-bb15-998269aa4717.png)

# システム構成
![ABテスト_ システム構成 - ページ 1 のコピー (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84972/66c070b8-d7f1-69fc-1c77-f72f96b1a3fa.png)

## AB Testing Converter
現状クライアントのプラットフォーム毎にABテストを実施するツールが異なっているので、ABテスト情報を変換するAB Testing ConverterはBFFに実装することにしました。
今回のBFFはGoで開発されており、go-chiというHTTP Routerが使われているので、AB Testing ConverterはそのMiddlewareとして開発しました。

```
Client Application -> BFF -> Microservices
```

後段のMicroservicesとはgRPCで通信をしているので、その情報をgRPCクライアントのメタデータに付与する処理も必要です。
BFFのソースコードでは以下のような処理のイメージで実装しており、 `propagation` というシンプルな内製ライブラリを使い、その情報を `context.Context`　に詰めてアプリケーション内で伝搬させています。この内製ライブラリはOpen TelemetryのGoライブラリでトレース情報などを別のアプリケーションに伝搬させるためのパッケージである[go.opentelemetry.io/otel/propagation](https://pkg.go.dev/go.opentelemetry.io/otel/propagation)と似たような役割になります。

```go
// BFFのHTTPサーバのミドルウェア
func MiddleABTesting(next http.Handler) http.Handler {
    return func(w http.ResponseWriter, r *http.Request) {
        .
        .
        .
        abtestInfo := r.Header.Get("ControlPlaneから来るヘッダ名")
        // DataPlaneに送信するための形式に直接変換はせず、
        // 一旦、通信プロトコル毎の差異を抽象化したpropagation.Headerに変換する
        // 送信する際にHTTPであればhttp.HeaderやgRPCであればmetadata.MDなど、必要に応じて変換する
        header := propagation.NewHeader(decodeAbTestInfo(abtestInfo))
        ctx := propagation.NewContext(r.Context(), header)
		r2 := r.WithContext(ctx)
		next.ServeHTTP(w, r2)
    }
}

// 後段のMicroservices (gRPC)のクライアントを初期化する処理
func xxx() {
    conn, err := grpc.DialContext(
		ctx,
        // context.Contextに入っているpropagation.Header構造体をgRPCのメタデータに変換して
        // 接続先のAPI Gatewayにリクエストを送信するようにするインタセプター
		grpc.WithChainUnaryInterceptor(propagation.UnaryClientInterceptor()),
	)
    .
    .
    .
}
```


## AB Testing Dataplane
社内ではマイクロサービスアーキテクチャを採用しており、可観測性や通信の信頼性などの観点でサービスメッシュとしてAWS App Meshを採用しています。

### AWS App Mesh について
AWS App MeshはIstioと同様のサービスメッシュのツールで、AWSのマネージドサービスです。
マネージドサービスなので、サービスメッシュのコントロールプレーンはAWS側で管理されています。

### AWS App Meshの利用について
Istioと比較検証し、大きく以下の4点の理由で採用しています。

- EKSだけでなく、ECSやEC2などの既存の環境もマイクロサービス化を実現したかった
- Istioのインフラを構築しメンテナンスする人的リソースが足りない
- 認証認可の機能以外は最低限必要な機能が揃っており、今後欲しくなりそうな機能も[開発ロードマップ](https://github.com/aws/aws-app-mesh-roadmap/projects/1)に載っていたこと
- サービスディスカバリとしてCloudMapと連携する必要があるためAWS内でしか利用できないが、インフラはほぼ全てAWSで構築しているので問題にならないこと

### トラフィックコントロールについて
AWS App Meshは基本的にサービスメッシュ内のマイクロサービスからしか通信を受け付けることができません。
外からの通信を受け付ける際は、仮想的なゲートウェイ（[Virtual Gateway](https://docs.aws.amazon.com/app-mesh/latest/userguide/virtual_gateways.html)）を設置する必要があります。
Istioの世界でいうところのIstio ingress gatewayと同様のApp Meshが提供している機能です。
Virtual Gatewayはサイト内検索の仮想サービス（VirtualService）にルーティングをします。
Virtual ServiceはIstioの世界のVirtual Serviceとほぼ同等で、実態のサービスを抽象化することができます。
IstioであればVirtual ServiceにPodのラベルなどに対してルーティングなどを設定し、バージョンの振り分けなどを行うことができます。
App Meshでも同様にルーティングを設定し、抽象化されたサービス内でトラフィックコントロールをすることができます。
しかし、Istioと違い、Virtual Nodeという抽象化されたノードに対してルーティングを記述します。
App MeshのVirtual Nodeとは、ECSであればTask group、EKSであればDeploymentへの論理ポインタとなっています。
今回はサイト内検索AはECSのタスクA、サイト内検索BはECSのタスクBという形で構築することで実現しています。
実際にAとBの振り分けはApp MeshのVirtual Serviceに対して、図のような式を評価するルーティングを設定して振り分けています。


# 最後に
弊社ではApp Meshをプロダクト利用していますが、国内ではApp Meshを利用している事例が少ない状態です。
ぜひ皆様もこの記事をきっかけにApp Meshを選択肢の一つ、そして一緒に情報交換ができるような仲間が広がると良いなと思っております。