メルカリShops における Cloud Run service の Canary Deployment

こんにちは!ソウゾウの Software Engineer の @dragon3 です。連載:メルカリShops 開発の裏側 Vol.2 の4日目を担当させていただきます。

この記事では、メルカリShops において、Cloud Run service を安心して deploy するための Canary Deployment の仕組みについて紹介します。

Cloud Run service の deploy 方法とその変遷

まず、現在の Canary Deployment の仕組みに至るまでに、これまでどのように deploy をおこなってきたかの変遷について紹介します。

1. 開発初期は gcloud コマンドラインツール

メルカリShops の開発がはじまった直後は、素朴に gcloud コマンドを使った CI/CD (GitHub Actions) workflow で deploy をおこなっていました。この時期はまだ本番環境への deploy も無かったため、CPU や Memory は共通で、Service Account などをコマンドオプションに指定していました。

gcloud run deploy echo \
  --image gcr.io/awesome-project/go/services/echo \
  --service-account=cloud-run-{{ service_name }}@...
  ...

2. Cloud Run API を利用した deploy コマンドラインツール

開発が進み、microservice も増えていき、各 service 毎で異なるオプションや環境変数を指定する必要がでてきました。また、以前紹介した Pull Request 毎の環境を実現するためにも Cloud Run の revision tag を特定の deploy では設定する等、より細やかな柔軟な仕組みが必要になりました。

そこで、Cloud Run API を利用した deploy コマンドラインツールを実装し、gcloud コマンドから移行しました。この deploy コマンドラインツールは各 service のオプションや環境変数等を以下のような YAML 設定ファイルから取得し、deploy を実行します。

project: awesome-project
environment: production
location: asia-northeast1
services:
  - name: echo
    image: gcr.io/awesome-project/go/services/echo
    env:
      - name: LOG_LEVEL
        value: info
    auto_scaling:
      min: 0
      max: 100
    capacity:
      memory: 512Mi
      cpu: 1
      concurrency: 100
      request_timeout: 30
    connection:
      use_http2: true
...

なお、このコマンドラインツールは開発に必要な他の便利コマンド群を含んでおり、deploy はその1サブコマンドとして、以下のように実行しています。

# Production 環境に deploy
cli deploy main \
  --spec production/services.yaml \
  --service echo \
  --image-tag 1.0.0

# Pull Request 環境 #123 に deploy
cli deploy pr \
  --spec development/services.yaml \
  --service echo \
  --number 123

3. 現在: deploy コマンドラインツール + Canary Rollout

プレローンチ後から本格提供へと進むにつれて、開発もよりアクティブになり、deploy の頻度も増えてきました。それに伴い、不具合や依存する service の deploy 漏れなどによる障害が発生しはじめました。このような問題・障害による影響範囲を最小限にし、安心して deploy できるようにするための仕組みが必要になり、以下に紹介する Canary Deployment を実装しました。現在は、ほとんどの microservice でこの仕組みを利用しています。

Canary Deployment の仕組み

まず、上述した deploy コマンドラインツールが、Cloud Run service の新しい revision を deploy します。この際、新しい revision にはまったくトラフィックを流さない、かつ ”canary” という revision tag を付けます。次に、図中の canaryrollout という Cloud Run service が定期的に実行されて、対象となる Cloud Run service の設定を更新していく(トラフィックを増やしていく) 仕組みになっています。( 詳細は以下で説明していきます )

実はこの構成は、Cloud Run Release Manager をおおいに参考にしました。当初、Cloud Run Release Manager をそのまま利用できないかと検討しましたが、あくまで参照実装的なものであることと、カスタムメトリクスを rollforward/rollback の判断に利用したい等の理由で、独自に実装することにしました。

以下、この仕組みと実装について説明していきます。

"canaryrollout" service

Cloud Run には traffic control の機能が備わっており、GCP のドキュメント (Rollbacks, gradual rollouts, and traffic migration) に書かれている通り、特定の revision に流すトラフィックの割合を指定することができ、Canary Deployment はこの標準機能を使っているにすぎませんが、どういう条件で、どの程度のトラフィックを流すか、または rollback するか、については何かしらの方法でコントロールする必要があります。

そこで登場するのが canaryrollout という、これもまた Cloud Run として動作している service です。

canaryrollout service は Cloud Scheduler により定期的に実行され、特定の label が付いた service を対象として処理します。その service で新しい canary revision の存在を検知したら、トラフィックを増やします。現在進行中の canary revision があれば、Cloud Monitoring からそのメトリクス (Error Rate など) を取得・計算・確認し、トラフィックの割合を増やします (= roll forward) 。最終的に canary revision が 100% のトラフィックを受けるようになると、その revision tag が “canary” から “stable” に変更されます。

もし Error Rate 等がある閾値を越えてしまった場合は、自動的にすべてのトラフィックを現在の安定版 “stable” に戻し、canary を停止します (= rollback) 。

この rollout 状況は Slack に通知されるようにもなっています。

設定は metadata.annotations に保存

canaryrollout service は対象となる service のトラフィックをどのように増やすか、いつ rollback するか、等の設定を知る必要があります。これらをどこに保存して参照するかという問題がありますが、Cloud Run が Knative (Kubernetes ベースの Serverless 基盤) と互換であるおかげで、Knative の Service リソースの metadata.annotations を使うことができます。( この方法も Cloud Run Release Manager を参考にしました )

以下のような canaryrollout.souzoh.com/ prefix の独自 annotation を定義し、Canary のための設定を Service リソース自身に保存します。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: echo
...
  labels:
    canaryrollout: enabled
...
  annotations:
    canaryrollout.souzoh.com/rolloutPercentSteps: 10,30,60,100
    canaryrollout.souzoh.com/minRequests: '50'
    canaryrollout.souzoh.com/metricsProviderType: grpc
    canaryrollout.souzoh.com/timeBetweenRollouts: 300s
    canaryrollout.souzoh.com/maxErrorRate: '0.1'
...

上記の例の場合、以下のような Canary Deployment が実行されます。

  • トラフィックは 10% -> 30% -> 60% -> 100% と増やす
  • 次のステップに進むためには最低 50 リクエストは必要
  • gRPC のためのカスタムメトリクスを使う
  • Rollout の間隔は 300秒
  • Error Rate の閾値は 0.1% ( これを越えた場合 rollback する )

これらの annotations は、deploy コマンドラインツールのYAMLで指定できるようになっています。

services:
  - name: echo
...
    canary_rollout:
      enable: true
      rollout_percent_steps: [10, 30, 60, 100]
      min_requests: 50
      metrics_provider_type: grpc
      time_between_rollouts: 300s
      max_error_rate: 0.1

メトリクス

Canary revision を roll forward するか rollback するかの判断には、以下の Cloud Run のメトリクスを使用しています。

  • run.googleapis.com/request_count
  • run.googleapis.com/request_latencies

上記に加えて、gRPC の server および client call の件数や status を見るために、Cloud Logging の logs-based メトリクスも使用しています。( Cloud Run のメトリクスでは、gRPC の status 等はわからないため )

課題と今後

いまのところ canary revision の Error Rate のみ判断材料となっており、Latency の変化等には対応できていません。今後はより精度の高い Canary Analysis、たとえば、この記事にあるような、現在稼動中のバージョンも baseline として canary とともに deploy し、それらを比較するようなものも検討してみたいと思っています。

また、現在は自前のツール群で十分にやりたいことを実現できていますが、PipeCD のように Cloud Run の deployment をサポートする OSS プロダクトも増えつつありますので、自分達にとってより良いツールやソリューションがないか、追い求めていきたいです。

まとめ

GCP が提供する機能の組み合わせとそれらのAPIを使った自前の実装で、メルカリShops における Cloud Run service の Canary Deployment は動いております。この仕組みによって 、新しい機能等をリリースした直後に発生した不具合や問題の影響範囲を小さくすることができ、安心して deploy できるようになりました。同じように Cloud Run をメインにシステムを構築されているかたのちょっとした参考になれば幸いです。

ソウゾウではメンバーを大募集中です。ソウゾウに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。

またカジュアルに話だけ聞いてみたい、といった方も大歓迎です。こちらの申し込みフォームよりぜひご連絡ください!

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追加