😸

PipeCDにおけるECSのCanary Releaseに対して、ListenerRule対応のコントリビュートした話

2023/12/29に公開

1. はじめに

こんにちは、23卒でバックエンドエンジニアをしているたかしゅんです。
PipeCDにコントリビュートした時の話について共有したいと思います。

PipeCDについての詳細は公式ドキュメントや下記の記事でも紹介させていただいているので、もしご興味があればご覧になってください。

https://pipecd.dev/docs-v0.45.x/

https://qiita.com/takashun-poi/items/699e346ff2b37b297b07

2. ECSのCanary Release

ECSを使用したカナリアリリースの種類として、以下の4つが挙げられます

- ALB (Application Load Balancer) を使用したカナリアリリース
- Service Connect を使用したカナリアリリース
- DNS ルーティングを使用したカナリアリリース
- プロキシやAPIゲートウェイを使用したカナリアリリース

今回は一般的なALBを使ったカナリアリリースについてのみ解説させていただきます。
具体的な「ALB を使用したカナリアリリース」の手順は以下になります。

1. 新しいアプリケーションバージョンのインスタンスを受け入れる新しいターゲットグループを作成します。

2. ALBにトラフィックの一部を新しいターゲットグループにフォワードするように設定します。

3. トラフィックを新しいバージョンと既存のバージョンの間で重み付けします。初期には新しいバージョンへの割合を低く設定します。

4. パフォーマンスと安定性をモニタリングしながら、徐々に新しいバージョンへのトラフィックの割合を増やします。

5. 新しいバージョンが安定していることが確認できたら、リスナールールを更新し、全トラフィックを新しいターゲットグループにフォワードします。

6. 全トラフィックの移行が完了したら、古いターゲットグループとそれに関連するリソースを削除します。

この流れにより、リスクを最小限に抑えながら、新しいバージョンへの安全な移行が可能になります。
PipeCDでのCanary Releaseの流れは以下で詳しく説明していますので、気になる方はぜひ
https://qiita.com/takashun-poi/items/699e346ff2b37b297b07#ecsのcanary-release-or-blue-green-deployment

3. ListenerとListenerRuleについて

ListenerはALBに到着するトラフィックを監視し、定義されたポート(例えば、HTTP/80、HTTPS/443)でリクエストを待ち受けます。
トラフィックがListenerに到達すると、Listenerはリクエストを適切なルールに基づいて処理し、ターゲットグループにルーティングします。Ruleが設定されていない場合はDefault Ruleが使用されます。

ListenerRuleは、Listenerに紐づけられ、トラフィックを特定の条件(例えば、パスパターン、ホスト名)に基づいて適切なターゲットグループにルーティングするためのルールです。
リクエストがListenerに到着すると、ListenerRuleは条件に一致するかどうかを評価し、一致する場合には指定されたアクションを実行します。

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/load-balancer-listeners.html

4. コントリビュートの背景

PipeCDを導入し、カナリアリリースを実施した際に一点課題がありました。
デフォルトのリスナーに対してのみトラフィックルーティングが可能であり、リスナールールを使用している場合、これが対応外となっていました。

具体的な状況としては、複数のECSサービスが同一のALBに紐づいており、各サービスがリスナールールを通じてパスパターンでトラフィックを分岐している場合などです。

下記のようにListenerに紐ずくRuleには優先度が存在しており、デフォルト設定は一番低い優先度となっている。そのためListener Ruleが存在している場合は、デフォルトの設定が使われることはありません。

それぞれのECSサービスに対して個別のALBを割り当てることで問題を解決できるものの、このアプローチにはいくつかの大きな影響が伴います。ドメイン管理の複雑化,証明書管理の増加, リファクタリングの必要性(ECSサービス, Applicationコード)

5. いざコントリビュート!!

PipeCDはOSSであり、ソースコードは公開されております
https://github.com/pipe-cd/pipecd

今回の目的である, routingを行っているコードは以下になります
https://github.com/pipe-cd/pipecd/blob/1864cdb3fe699d04a55eb4fd1d53d5e9c055b087/pkg/app/piped/executor/ecs/ecs.go#L420-L477

具体的な流れとしては以下になります。

1. ECSクライアントの作成
2. トラフィックルーティングに関するステージ設定を検証
3. プライマリとカナリアのターゲットグループに対するトラフィックの分配
4. ルーティング設定
5. メタデータの更新
6. リスナーARNの取得 (GetListenerArns)
7. 変更 (ModifyListeners)

今回必要な処理は二つです

  • ListenerRuleを取得する
  • 指定されたTargetGroupに紐づく、ListenerRuleがある場合は変更。ない場合は今まで通りDefaultRuleを変更する

5.1 ListenerRuleを取得する ~GetListenerRuleArns~

app.pipecd.yamlから取得したTargetGroupArnを使用して、関連するターゲットグループの情報を取得し、続いてそのターゲットグループに紐づくリスナー情報を取得しています。
https://github.com/moko-poi/pipecd/blob/318cca9d9e537214f739711ca27514b0da6216cd/pkg/app/piped/platformprovider/ecs/client.go#L395-L433

使用しているAWSのAPIは以下のとおりです:

DescribeTargetGroups
https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_DescribeTargetGroups.html

DescribeListeners
https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_DescribeTargetGroups.html

そのため今回はListenerに関連するRuleを取得するGetListenerRuleArnsを定義して解決しました
https://github.com/moko-poi/pipecd/blob/2650922af2c52c64cac7429973eb22966dd4b6d8/pkg/app/piped/platformprovider/ecs/client.go#L421-L443

Listener情報を取得するためにAWS APIのDescribeRulesを使用しました。
https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_DescribeRules.html

5.2 ListenerRule対してRoutingする ~ModifyRules~

以下の手順で実装しました。

1. トラフィックルーティング設定が2つのターゲットグループを含んでいるかを確認
2. 引数として渡されたリスナールールARNのリストをループ処理します
3. DescribeRules APIを使用して、現在のリスナールールのアクションを取得
4. ルールのアクションのうち、タイプがForwardのものについて、新しいトラフィックルーティング設定を適用します。他のアクションタイプは変更せずに保持します
5. 変更されたアクションを使用して、ModifyRule APIを呼び出し、リスナールールを更新します

https://github.com/moko-poi/pipecd/blob/2650922af2c52c64cac7429973eb22966dd4b6d8/pkg/app/piped/platformprovider/ecs/client.go#L513-L564

5.3 最終的なコード

https://github.com/moko-poi/pipecd/blob/2650922af2c52c64cac7429973eb22966dd4b6d8/pkg/app/piped/executor/ecs/ecs.go#L416-L475

※routingだけではなく、rollbackの際も同じような手順になります

6. 最後に

OSSへのコントリビュートは今回が初めてでしたが、一連の流れを経験できて良かったと感じています。特に、AWS APIの仕様やクライアントのコードについて深く理解することができ、貴重な経験になりました。

今回の対応は緊急性が高かったため、公式のリリースを待たずに自分たちのリポジトリをフォークしてSelf Hostingする形で対応しました。
(セルフホスティングした時の記事も書けたらと思っています)

この経験が皆さんにとって少しでも参考になれば幸いです。

Discussion