Kubernetes初心者が数万QPS環境でのカナリアリリース導入に挑戦
CyberAgentのインターン生が、数万QPSを処理する広告配信システムにおいて、ローリングアップデートのリスクを低減するため、KubernetesネイティブなArgo Rolloutsを用いたカナリアリリースの導入に挑戦した実践記録である。
キーポイント
高トラフィック環境におけるリリースリスクの課題
既存のローリングアップデート方式では、リリース中にPod数が倍増し、問題発生時に全クライアントに影響が及ぶリスクがあった。
カナリアリリースによる段階的リリースの導入
新バージョンを一部のトラフィック(例:10%)から段階的に導入し、問題を検知しながら影響範囲を限定するカナリアリリース方式を検討・導入した。
技術選定としてのArgo Rolloutsの採用理由
既存のKubernetes運用への載せやすさと、手動操作・分析の柔軟性を重視し、FlaggerやSpinnakerではなくArgo Rolloutsを選択した。
カナリアリリース実現のための要件
トラフィックの新旧制御と、問題検知のための観測(監視・ログ・トレース)およびメトリクスが必要であると指摘している。
Argo Rolloutsの段階的導入とトラフィック制御
Argo Rolloutsは段階的なリリース手順と分析を明示でき、Dashboardからの手動操作も可能なため、半自動から段階的に自動化へ移行する方針に合致している。トラフィック管理にはIstioの代わりにALBのターゲットグループ制御を採用した。
分析用メトリクスの選定と比較アプローチ
安定しないメトリクスや外部要因に影響されやすいメトリクスは分析に適さないと判断し、ad_return_rateを採用。canaryとstableの比較により、トラフィック量やポッド数の変化の影響を受けずに分析できる。
半自動カナリアリリースの採用理由
全自動方式では分析が適切に機能しない場合に毎回デプロイ中にロールバックされてしまうリスクを避けるため、一時停止ステップを設けた半自動方式を採用した。
影響分析・編集コメントを表示
影響分析
この記事は、大規模な実稼働環境において、Kubernetesを用いたより安全なリリースプロセスを構築するための具体的な実践例を提供している。特に、高トラフィックサービスを運用するエンジニアにとって、リリースリスクの低減と段階的導入の実装方法に関する貴重な知見となる。
編集コメント
Kubernetes初心者が実践的な課題に挑戦し、具体的な技術選定の理由とともにその過程を詳細に記録した、実務者にとって参考価値の高い実践レポートである。
まず、分析パラメータのチューニングをさらに進める必要があります。設定可能なパラメータについては、Dry Runモードで得られた分析結果などをもとに最適な値を見つけていくことができます。そして、ある程度固まった設定で長期的に運用し、実績を積んだ上で、最終的な目標である全自動カナリアリリースの導入を検討できれば良いと思います。また、今回はシステムメトリクス(CPU・メモリ使用率、レイテンシなど)を用いた分析は含めませんでしたが、余裕を持たせた閾値を設定することで「これを下回れば明らかに問題がある」という補助的な分析は可能です。このような追加分析項目の検討も有効だと考えます。
「なぜそうしたのか」を説明できることが重要視される文化の中で、これまで以上に情報収集に時間をかけ、理由にこだわった開発を行えたことが自身の成長につながりました。
広告配信サーバという大規模トラフィックを扱う環境には、エンジニアとして成長できる要素が多いと感じました。
広告系サービスは他のWebサービスと比較して、開発した機能が会社の売上に直結しやすいことが分かりました。
Kubernetesの機能やマニフェストの設定項目を表面的に追うだけでなく、実装レベルでどのように動作しているかを理解できるようになりたいと思いました。
今回のインターンシップでは、メインタスクとして「カナリアリリースの導入」に、要件定義→技術選定→設計→構築→動作確認という流れで取り組みました。特に、チームの意向を尊重しつつも自身の考えを持ってタスクを遂行できた点は良かったと思います。今回はProduction環境での動作確認までは至りませんでしたが、Argo Rolloutsの仕様を詳細に調査してチームに共有したり、既存のSSPのインフラ構成にカナリアリリースの要素を組み込む設計までを行えたことが、自身の貢献だったと考えています。
最後に、JOBインターンシップを受け入れてくださったAJAの皆様、開発をサポートしてくださったSSP開発チームの皆様、大変お世話になりました。ありがとうございました。
原文を表示
こんにちは、奈良先端科学技術大学院大学 修士 1 年 の 東迎健太郎 です。 2025年 12月 2日 〜 12月 26日の 1ヶ月間、CyberAgent (CA) が主催するインターンシップ「CA Tech JOB」に参加しました。 私は バックエンドエンジニア として CA の 広告配信サービス を運用している AJA の Supply Side Platform (SSP) チームに配属していただきました。
インターンシップに参加した目的
SSP の広告配信システムについて
取り組みプロセス テスト用の環境を構築
インターンシップに参加した目的
今回、以下の目的でインターンシップに参加しました。
開発現場を知るため 私は同年9月に CA で開催された「Universe」というハッカソンに参加しました。そこで、CA の社風を知り、実際の開発現場の雰囲気や文化も知りたいと思っていました。
高トラフィック環境におけるシステムの開発 これまで高トラフィックをさばくシステムの開発経験がなかったため、このような環境での開発に関わり自己解決力をさらに高めたいと思っていました。 また、スケーラビリティや可用性を意識した開発をしたかったので、Kubernetes の参考書を読んだ程度の知識しかなかったのですが、自分から Kubernetes 周辺のタスクを求めました。
SSP の広告配信システムについて
取り組んだタスクについて触れる前に、私が配属された AJA が提供している SSP サービスについて簡単に説明します。
広告配信はまずストリーミングプラットフォームやテレビなどの メディア から広告のリクエストを受け、DSP と呼ばれる広告主・代理店が広告の入札をするためのシステムへ ビッド(入札)リクエスト をします。そして、取得した入札から最適なものをセレクションし、選ばれた広告をメディアに送信します。この一連のフローの仲介的役割を担っているのがSSPです。
出典: 新卒が挑む、数万 QPS をさばく広告配信サーバのリクエスト制御
SSPの広告配信サーバ(アドサーバ)では新バージョンのリリース方式としてローリングアップデートを採用しています。理由は Kubernetes の Deployment リソースがサポートしているリリース方式であるため実装が容易であったことが挙げられます。
ローリングアップデートは以下のように Pod を一つずつ旧バージョンから新バージョンに移行させることができます。

これはローリングアップデートを説明する一般的な図ですが、実際の動きは少し異なります。以下はモニタリングツール(New Relic)での実際のPod数遷移グラフです。

これを見るとリリース時にポッド数が倍増しながらほぼ同時に新バージョンの Pod に置き換わっていることがわかります。 したがってリリース後に本番環境特有の問題が発生した場合、ロールバック(旧バージョンに戻すこと)するまでに全てのクライアントに影響を与えることになってしまいます。
これを解決するとともに更なる恩恵を受けるためにカナリアリリースの導入を検討しました。
カナリアリリースは、新バージョンをいきなり全ユーザに反映するのではなく、まずは一部のトラフィック(例:10%)だけを新バージョンに流し、問題がないことを確認しながら段階的に比率を上げていくリリース方式です。
段階的に流すことで、もし本番環境特有の問題(特定のリクエストパターンでのエラー、レイテンシ悪化、依存サービスとの相性など)が発生しても、影響範囲を小さく保ったまま検知し、ロールバック(旧バージョンに戻すこと)できます。特に、数万RPSのような高トラフィック環境では「気づいてから戻すまで」の短時間でも影響が大きくなりやすいため、影響範囲を限定できる点が重要になります。
一方で、カナリアリリースを成立させるには「どのトラフィックが新旧どちらに流れているか」を制御できること、そして「問題が起きた/起きていない」を判断できる観測(監視・ログ・トレース)と、判定に使えるメトリクスが必要です。

今回のゴールは「ローリングアップデートのまま全配信されてしまうリスクを減らしつつ、段階的なリリースを安全に運用できる状態を作ること」でした。そのため、技術選定では以下を重視しました。
Kubernetesネイティブに扱える(マニフェストで管理でき、既存運用に載せやすい)
段階的なトラフィック配分(10%→20%→…)を表現できる
メトリクスに基づく判定(分析)や、手動判断を挟む運用に対応できる
導入・運用コストが過度に高くない(学習コスト、依存コンポーネント、変更影響)
将来的に GitOps(例:Argo CD)と組み合わせやすい
候補としては、段階的リリースを自動化する Flagger、より包括的なCD基盤としての Spinnaker なども検討しました。ただし、今回のスコープでは「1. 既存のKubernetes運用に最小限の追加で載せられること」「2. 手動操作と分析の両方を柔軟に組み合わせられること」を優先し、Argo Rollouts を採用しました。Argo Rollouts と比較したときに、Flagger は 2 の面で劣り、Spinnaker は 1 がネックとなったというのが理由です。
Argo Rollouts は Rollout リソースで段階的なリリース手順(steps)と分析(analysis)を明示でき、さらに Dashboard から Promote / Undo の手動操作もできるため、まずは半自動の形で安全に導入し、将来的な自動化へ段階的に移行する方針に合っていました。 外部サービス連携については、SSP の分析基盤である Prometheus と New Relic の両方にも対応しています。 また、Kubernetes のコアオブジェクトでは厳密なトラフィック管理を行う機能は備わっていないため Argo Rollouts に合わせてトラフィックプロバイダーの操作も必要になります。Istio の VirtualService を使用すれば実現可能ですがカナリアリリースをするためだけに Istio の導入をするのは過剰であるため、他の手段として ALB のターゲットグループを制御することで実現します。
Argo Rolloutsは、Kubernetes上で高度なデプロイ戦略(カナリアリリースやブルーグリーンデプロイ)を実現するためのコントローラーです。 これにより、段階的なリリースや自動分析、トラフィックの制御が可能になります。
主な設定項目として以下の3つがあります。
steps デプロイの進行を細かく制御するための手順。 例:10%→30%→100%と段階的にリリース。インターバルを入れる。
analysis 各ステップでメトリクス(例:エラー率、レスポンスタイム)を自動で分析し、問題があればロールバック。
trafficRouting ALBやIstioなどと連携し、トラフィックの一部を新バージョンに振り分ける制御が可能。
また、Argo RolloutsにはWeb UI(Argo Rollouts Dashboard)があり、進行状況の可視化や手動操作(Pause/Resume/Rollbackなど)ができます。
出典: Argo Rollouts Dashboard
手動操作(Promote / Undo)
UI: Argo Rollouts Dashboard で対象 Rollout を選択し、Promote
kubectl argo rollouts promote canary-rollout
UI: Argo Rollouts Dashboard で対象 Rollout を選択し、Undo
kubectl argo rollouts undo canary-rollout
Argo Rollouts の動作を確認したり、マニフェストファイルに細かな設定をしながら設計を考えていきたかったので、他のメンバーの開発に影響を与えないようなテスト環境を構築しました。
New Relic で収集しているメトリクスの一覧を見たところ、以下の特徴を一つ以上持つものがほとんどでした。
グラフの波形が安定していない 例)リクエスト数、ポッド数など
実装や最適化によって変化が容易 例)CPUやメモリ使用率、WarnやErrorログ数、レイテンシ
ビジネスによって変化が容易、外部依存など 例)各DSPが設定しているBidリクエスト数制限の影響を受けているなど
これらのメトリクスを用いると分析結果を判定するための閾値を決めづらい又は頻繁な修正が必要になるため、適さないと判断しました。
他のブログ記事には 5XX エラーレスポンス率などを使用していることが多かったが、アドサーバの実装では 5XX エラーが起きる確率はほぼ0%であり分析で問題を発見できるメトリクスではないと判断しました。
このようにメトリクス単体だと分析しづらいことが分かったため、「canary と stable で比較する」という要素も加えることにしました。
結局、ad_return_rate というメトリクスを使用することにしました。 $$ \text{ad_return_rate}=\frac{\text{ad_return_total}\quad\quad}{\text{ad_request_total}\quad\quad} $$ このメトリクスは、メディアからの広告リクエストに対して広告を返した割合を意味します。 広告リクエストはいくつかのリクエストパスに分かれているため全て分析に使用します。 選んだ理由は、このメトリクスは割合であるため canary と stable それぞれの ad_return_rate を比較することが可能な点にあります。カナリアリリースでは canary と stable の各トラフィック量が変化したり、それぞれにアタッチされた HPA リソースによりポッド数も変化するので、それに影響しないので適すると思いました。 また、ad_return_rate は先ほど挙げた「分析に適さないメトリクスの特徴 3 つ」について、以下のように対処することができます。
グラフの波形が安定していない ✓ ad_return_rate の波形は安定していないが、canary と stable で比較するので波形は影響しなくなる。
実装や最適化によって変化が容易 ✓ 実装によって canary の ad_return_rate が stable より良くなる分(または同程度)には許容し、大きく低下する場合は分析失敗と見なすことができる。
ビジネスによって変化が容易、外部依存など ✓ メディアからの広告リクエスト数増減などが起きても ad_return_rate は変化しない。
ad_return_rate のグラフ(リクエストパスごとに色分け)

分析の判定式はこのようになります。式の範囲によって $\text{ad_return_rate}\hspace{1pt}_{canary}$ と $\text{ad_return_rate}\hspace{1pt}_{stable}$ が同程度の値になることを期待しています。
$$ \frac{\text{ad_return_rate}\hspace{1pt}_{canary}\quad}{\text{ad_return_rate}\hspace{1pt}_{stable}\quad} =\frac{\frac{\text{ad_return_total}\hspace{1pt}_{canary}}{\text{ad_request_total}\hspace{1pt}_{canary}}} {\frac{\text{ad_return_total}\hspace{1pt}_{stable}}{\text{ad_request_total}\hspace{1pt}_{stable}}} \quad ( \ge 0.95,\ \le 1.05 ) $$
チームメンバーからの要望もあり、これまで同様にデプロイ時に担当者が目視でメトリクスを確認してその後は手動操作でロールアウトまたはロールバックする方式にしました。つまり、半自動的なカナリアリリースになります。理由としては、全自動のカナリアリリース導入を行うとこれまでのデプロイフローと差が大きくなってしまうため慎重になりたいためです。私も同じ考えで、もし全自動カナリアリリースを Production 環境で本格導入した後に分析が適切に機能していなかったことが判明すると、毎回デプロイ中にロールバックされてしまいます。そのため、今回は一時停止のステップを設けてその後段階的リリースをできるだけでも大きな貢献になると考えました。
しかし、実際に Production 環境で分析も含めて長期間運用しないと本当にメトリクスが正しいのか、パラメータチューニングが上手くいっているのかは分からないため、たとえ分析が失敗してもリリースには影響を与えない(ロールバックさせない)方法はないのか調査したところ、Argo Rollouts では分析を Dry Run モードで実行するとこの要求を満たせるようです。
これらを元に、カナリアリリースにおける各ステップのトラフィック割合とインターバルを以下のように設定しました。
stable 90, canary 10
stable 80, canary 20
stable 60, canary 40
stable 40, canary 60
stable 20, canary 80
stable 100, canary 0
ステップ 1 ではロールアウトを一時停止することによりリリース担当者が New Relic でメトリクスを確認できる。そして問題がなければ Rollout(Promote) 、問題があれば Rollback(Undo) を手動操作で行う。
ステップ 2 以降は canary サービスへのトラフィックを 20% ごと増やしていく。
各ステップ(3 5 7 9)はインターバル 2 分間で、その間 30 秒間隔で分析を繰り返す。
各ステップでの分析の最大失敗数は 2 回である。(Dry Run モードであるため最大に達してもロールバックは発生しないが、全自動方式を本格導入した際の目安として設定している)
マニフェストファイルでの Rollout リソース定義と AnalysisTemplate リソース定義を以下に示します。
apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: name: test-adserver-canary-rollout spec: selector: matchLabels: app: adserver workloadRef: apiVersion: apps/v1 kind: Deployment name: adserver-deployment strategy: canary: canaryService: adserver-canary-svc stableService: adserver-stable-svc trafficRouting: alb: ingress: adserver-ingress rootService: adserver-root-svc stableMetadata: labels: rolloutRole: stable canaryMetadata: labels: rolloutRole: canary analysis: templates: - templateName: dryrun-analysis startingStep: 2 steps: - setWeight: 10 - pause: {} - setWeight: 20 - pause: {duration: 2m} - setWeight: 40 - pause: {duration: 2m} - setWeight: 60 - pause: {duration: 2m} - setWeight: 80 - pause: {duration: 2m}
Rolloutリソースを定義するときの注意点
Deployment は既存のものを参照すれば良い。
Service は既存のものに加えて Canary 用と Stable 用を用意する必要がある。ただし、スペック定義は同じで良い。
HPA や VPA を Deployment にアタッチしていた場合は Rollout にアタッチし直す必要がある。
AnalysisTemplateリソース定義
apiVersion: argoproj.io/v1alpha1 kind: AnalysisTemplate metadata: name: dryrun-analysis spec: dryRun: - metricName: ad_return_rate metrics: - name: ad_return_rate interval: 30s successCondition: "all(result, # >= 0.95 && # <= 1.05)" failureLimit: 2 provider: prometheus: address: PROMETHEUS_ADDRESS query: | sum by (ad_request_path) ( rate(ad_return_total{rollout_role="canary"}[30s]) ) / sum by (ad_request_path) ( rate(ad_request_total{rollout_role="canary"}[30s]) ) / ( sum by (ad_request_path) ( rate(ad_return_total{rollout_role="stable"}[30s]) ) / sum by (ad_request_path) ( rate(ad_request_total{rollout_role="stable"}[30s]) ) )
AnalysisTemplateリソースを定義するときの注意点
分析は interval の時間間隔で繰り返し行われ、同一ステップ中に分析失敗が failureLimit に達すると通常はロールバックされる(全てのポッドが stable バージョンに戻る)。
query のレスポンスが配列であり全ての要素に対して判定したい場合は"all(result, # >= 閾値)"
dryRun モードにするとたとえ分析失敗した場合でもロールアウトに影響を与えない(ロールバックされない)。分析結果は AnalysisRun に残される。
AnalysisTemplate リソース定義では PromQL で Canary ポッド(rollout_role="canary"
rollout_role="stable"
apiVersion: monitoring.coreos.com/v1 kind: PodMonitor ... ... podMetricsEndpoints: - port: prometheus path: /metrics interval: 10s scrapeTimeout: 5s + relabelings: + - sourceLabels: [__meta_kubernetes_pod_label_rolloutRole] + targetLabel: rollout_role
既存のモニタリング環境と構成したカナリアリリース環境

今回のインターンシップ期間の半ばあたりでコードフリーズがありました(年末ということで)。よって Production 環境で試すことはできなかったため、Staging 環境内に構築したテスト環境で設計したカナリアリリースの動作確認を行なった。
以下のような簡易的なリクエストパターンを作成して、実環境を模した形で試してみました。Staging 環境では実際の広告トラフィックが流れていないので通常はアドサーバポッドは 1 個ですが、カナリアリリースによって Canary と Stable のポッド数が遷移するのが分かるように 10 個に設定しました。
テスト用のリクエストパラメータでテスト用の広告を取得するリクエスト
0 ~ 10 RPS のランダム性(Production 環境では数万 RPS ですがその規模は不要であるため)
結果:期待したカナリアリリースの動作を確認できました。
上:Ingress詳細画面 下:ロールアウト状況モニタリング画面

Canary および Stable 用の各サービスへのトラフィック割合は ALB のターゲットグループの Weight として設定されていることが分かります。 Canary ポッドが徐々に増えており、AnalysisRun も実行されていることが分かります。
Argo Rollouts ダッシュボード

ダッシュボードでも Canary ポッドが徐々に増えていく様子を確認できました。 Pause 後、UI からロールアウトを再開する流れを確認できました。
まず、分析のパラメータチューニングがさらに行う必要があります。設定可能なパラメータについては Dry Run モードによって得た分析結果などから最適な値を見つけていくことができます。そしてある程度固まった設定で長期的に運用し、動作実績を作った上で一番理想的な全自動カナリアリリースを検討していくことができれば良いと思います。また今回はシステムメトリクス(CPU・メモリ使用率、レイテンシなど)での分析は含みませんでしたが、余裕を持った閾値に設定すると「未達であれば明らかに問題がある」というような補助的な分析は行えるので、このような追加の分析項目の検討も有効だと思います。
「なぜそうしたか」を説明できるようにすることが重要な文化なので、これまで以上に情報収集にかける時間を多くして、理由にこだわる開発ができたのが成長に繋がりました。
広告配信サーバの大規模トラフィックの中でエンジニアとして成長できる要素が多い開発現場だと感じた。
広告系サービスは他の Web サービスと比較して、開発した機能が会社の売り上げに直結しやすいことが分かりました。
Kubernetes の機能やマニフェストの設定項目だけを見るのではなく、実装レベルでどう動いているかを考えれるようになりたいと思いました。
今回のインターンシップではメインのタスクとして「カナリアリリースの導入」というテーマで、要件定義 – 技術選定 – 設計 – 構築 – 動作確認 という流れで取り組みました。特にチームの意思を尊重した上で自分なりの考えを持ってタスクを遂行できたことが良かったと思います。今回は Production 環境での動作を見ることは叶いませんでしたが、Argo Rollouts の仕様を詳細に調査しチームに共有したり、現状の SSP のインフラ構成にカナリアリリースのための要素を組み込むところまでを行えたのが貢献になったと思っております。 最後に、JOBインターンシップを受け入れてくださった AJA の皆さま、一緒に開発のサポートをしていただいた SSP 開発チームの皆さま、大変お世話にならせていただきました。ありがとうございました。
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み