週1回の夜間リリースから自律的デリバリーへ──SpeedaのCD改善の歴史

週1回の夜間リリースから自律的デリバリーへ──SpeedaのCD改善の歴史

ユーザベース Speeda事業でフェローを務めるあやぴーです。

私がユーザベースに入社し、現在のProduct Teamで働き始めたのは2018年9月のことです。当時のSpeedaはリリース作業が重く、週に一度、ユーザーが利用していない夜間帯にリリースを行っていました。

そのため開発者がテンポよく自由に機能開発を行い、デリバリーしていくのが難しい状況にありました。ユーザーへの大きな影響を避けるため夜間リリースとしていた側面もありますが、技術的な制約が大きかったのは否めません。

しかし現在では、自律分散的に独立したチームがそれぞれの判断で本番環境へデリバリーをすることが可能になっています。

この間にどのような取り組みが行われてきたのか。その歴史を紐解いていくことで、私たちの継続的デリバリー(Continuous Delivery / CD)に関する取り組みを追体験していただきたいです。

モノリス環境がもたらしていたリリースの制約

改善の歴史に進む前に2018年当時を振り返るところから始めてみましょう。

まず前提として、SpeedaはJavaで開発されたモノリスなアプリケーションで、オンプレミス(オンプレ)のサーバー上で稼働しています。一部検索機能に使われているAPIなどは独立して開発されていますが、ほとんどの機能がSpeeda上で開発されています。

このSpeedaのリリース作業が重たくなる原因は以下に集約されます。

  • アプリケーションのビルドやデプロイそのものに時間がかかる
  • 検証するために用意されているオンプレミスの検証環境にデプロイする必要がある
  • ユニットテストは実装されているがE2Eテストがほとんど実装されていなかった

開発した機能をリリースしたいチームはいくつかの手順に従う必要がありました。まず検証したいのですが、チームが開発した機能を含むブランチをビルドしてデプロイするのに1時間ほどかかります。そして検証環境が有限個のため、他のチームとタイミングが被るとなかなか検証することができないという状況が生まれていました。

先ほど述べたようにE2Eテストがほとんど整備されていなかったため、検証環境にデプロイした後の確認も手動になります。チームがブランチをマージするには厳しい制約がありました。例えば、

  • 週半ばまでにマージしなければ、リリースは翌週へ延期
  • 本番用実行ファイルの作成後、QA環境で半日かけたQAチームによる検証が行われる
  • QAチームによる確認の途中で問題が見つかった場合、該当チームは急いで修正して再マージし、QAをやり直す

……というサイクルを繰り返す日々でした。

こうして作られた本番リリース用の実行ファイルは、週末の夜間にリリースされます。現在でもこの作業には2~3時間かかりますが、当時は待機系と呼ばれる環境にデプロイした後に、以下の確認作業を行っていました。

  • 目視による動作確認
  • QAチームによる主要機能の確認

確認作業で設定漏れなどの不備が見つかると、その場でリリースを中止することもありました。

こうした制約の多いリリースフローから、私たちの「自律的に開発を行い、デリバリーをし続けることができる開発チーム」を目指す旅が始まったのです。

マイクロサービス化とIstioによるブルーグリーンデプロイの実現

実は私が入社した頃のSpeedaはマイクロサービス、マイクロフロントエンドを適用して機能開発を始めた時期に近く、新しい機能に関しては全てマイクロサービスとして開発するようになっていました。

マイクロサービスを採用し始めたのは、「独立進化が可能になる」という利点が、私たちProduct Teamが組織として目指している「自律分散的な開発チーム」と重なる点が多いからです。まさに私たちにとってのマイクロサービス黎明期とでも言うべき時代です。

この頃、Istioを活用したブルーグリーンデプロイを導入し、デリバリーの改善が行われていました。ブルーグリーンデプロイ自体は枯れた概念のため説明を割愛しますが、これをIstioのVirtualServiceのレベルで行うというものです。

apiVersion: apps/v1
kind: Deployment
metadata:
 name: nginx-blue
spec:
 replicas: 1
 selector:
   matchLabels:
     app: nginx
     version: blue
# 略
---
apiVersion: apps/v1
kind: Deployment
metadata:
 name: nginx-green
spec:
 replicas: 1
 selector:
   matchLabels:
     app: nginx
     version: green
 template:
   metadata:
     name: nginx
     labels:
       app: nginx
       version: green
# 略
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: nginx-ds
 namespace: default
spec:
 gateways:
 - sample-gateway
 hosts:
 - "sample.hoge.com"
 http:
 - match:
   - headers:
       x-version:
         exact: green
   route:
   - destination:
       host: nginx-svc.sample-ns.svc.cluster.local
       subset: green
       port:
         number: 40001
 - match:
   - uri:
       prefix: /
   route:
   - destination:
       host: nginx-svc.sample-ns.svc.cluster.local
       subset: blue
       port:
         number: 40001

2系統(ブルーとグリーン)のDeploymentをデプロイして、VirtualServiceを切り替えてデプロイし直すことで、ブルーグリーンデプロイを実現しています。これにより、開発者の誰もが簡単に安全なデリバリーを行えるようになりました。

またマイクロサービスを適用した機能は単独でリリースできるようになり、Speeda本体のリリースサイクルに左右されなくなったため、開発者の裁量によってデリバリーすることが可能になったのです。

従来はユーザー影響などを顧みる必要がありましたが、マイクロサービスになったことで影響範囲も縮小することができたため、日中のリリースも実現しています。自律分散的に独立して開発を行い、デリバリーを行える体制へと大きく前進しました。

▼ 当時のIstio導入詳細はこちら tech.uzabase.com

CDCテストによるデリバリー範囲の縮小

2020年初頭までマイクロサービス、マイクロフロントエンドを適用した機能は、デリバリーの最小単位が「機能」でした。イメージとしては一連の機能が詰まった画面というと分かりやすいでしょうか。

デリバリーの最小単位
デリバリーの最小単位

私たちはE2Eテストを書くことを徹底していましたが、当時はフロントエンドから末端のAPIまでE2Eテスト用にデプロイして、文字通り「エンドツーエンド」のテストを実行していました(もちろん単体のAPIのみに対応するE2Eテストも書いています)。

当初はあまり問題がなかったのですが、新しく開発される機能が増えるとそれぞれの機能から参照される共通のAPIというものが登場します。そのため当時は、依存する機能の全体に対するE2Eを全て通してからリリースする運用が行われていました。

この結果、せっかくマイクロサービスを適用しているにも関わらず、パイプラインの実行時間が長くなってしまったり、依存関係が増えることによって関係ないところで失敗してしまったりすることが増えてしまったのです。

そこで私たちはデリバリーの最小単位を「API」にするために、CDC(Consumer Driven Contract)テストに取り組み始めました。

CDCテストがない場合、あるAPI αをリリースしたいときに、API αを利用している全てのAPI(βやγ)とAPI αの組み合わせ(α+β、α+γ)が適切に動作するかをテストしなければ安心してリリースすることができません。ですが、CDCテストを導入することでAPI αには、実際にどのような使われ方をされるのか、という情報が実行可能なテストの形で残るようになります。

CDCの導入でE2Eテストの信頼度が高まり、開発者はパイプラインを実行するだけで自信を持ってリリースできるようになりました。

Argo Rolloutsによるロールバック可能性とスモークテストの自動化

前述したIstioによるブルーグリーンデプロイは2023年ごろまで続きました。しかしIstioを利用したブルーグリーンデプロイには2つの課題がありました。

一つはカラーラベルを覚える必要があったこと。この問題は現在デプロイされているVirtualServiceのマニフェストが分かれば、次にデプロイするカラーラベルを取ることができたので解決しやすかったのですが、毎回パイプラインにそれを解決するためだけのコードを書く必要があり煩雑になりやすかったです。

もう一つはリリース後にロールバックすることができなくなる可能性があったこと。Deploymentにはリビジョン機能があるため本来は簡単にロールバックができるのですが、ブルーとグリーンのそれぞれでDeploymentを作っているためKubernetesの機能を利用してロールバックを行うことができませんでした。

また何かの拍子に(例えば)ブルーとしてデプロイしたがリリースしなかったものが残ってしまっていると、グリーンにデプロイしていたものを前のバージョンに戻したいときに戻せないということが考えられます。

もちろんIstioの運用で回避することも可能だったのですが、デプロイ手順のさらなる煩雑化を招くため現実的ではありませんでした。

そこで上記の問題を解決するためにArgo Rolloutsを導入することにしました。今まではブルーとグリーンそれぞれに対応するDeploymentを用意していましたが、Argo Rolloutsによるブルーグリーンデプロイを実践することで、単一のRolloutというリソースに集約できるようになりました。Rollout自体がDeploymentのようにリビジョンを保持しているためにロールバックも容易になりました。

デリバリーの最小単位
「Argo Rollouts」と「Istio」のリソース管理の違い

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: example-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: example-api
  template:
    metadata:
      labels:
        app: example-api
// 略
  strategy:
    blueGreen:
      activeService: example-api-svc
      previewService: example-api-preview-svc
      autoPromotionEnabled: true
      prePromotionAnalysis:
        templates:
          - templateName: smoke-test

実はArgo Rolloutsの導入は、既に手に馴染んだブルーグリーンデプロイの仕組み(Istio)があったため時間がかかりました。既存のDeploymentの kindRollout と書き換えるだけなので簡単に移行できるはずだったのですが、その有用性がProduct Team内に伝わりにくく、勉強会やスモールスタートでの実績作りを繰り返すことで、ようやく浸透が進んだのです。

また、Argo Rolloutsの利用を始めたことでラストワンマイルとも言える自動のスモークテストを実現できるようになりました。Argo RolloutsではAnalysisTemplateというリソースを定義することによって、さまざまなプレビューからアクティブへのプロモーションを自動的に行うことができます。

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: health-check
spec:
  metrics:
  - name: webmetric
    successCondition: result == "ok"
    provider:
      web:
        url: "http://{{ .Values.previewService }}/health"

従来はE2Eテストを実行して、本番環境にデプロイしてもリリース前に動作確認をすることがありましたが、スモークテストの導入によって、アプリケーションが適切にリリース可能な状態であるかを、自動で判断できる仕組みが整いました

パイプラインの進化が組織にもたらした効果と課題

これまで本稿で述べてきたことを実践してきたことによって、私たちProduct Teamは以下を手に入れることができたと考えています。

  • デリバリーのリズム
    • 1週間に一度しかないリリースチャンスを待つことなく、自分たちのリズムで本番環境までデリバリーすることができるようになった
    • カンバンに本来はDONEになっているストーリーが滞留してしまうことがなくなった(ストーリーのDONEをシンプルにリリースとすることができる)
  • ステークホルダーとの協調
    • 開発者がリリースのタイミングをコントロールすることができるため、ビジネスの要望に合わせてリリース時期を調整することが容易になった
  • チーム間のコミュニケーション
    • マイクロサービスの適用が進み各チームが独立して動ける分、全体感を損なわないように開発者同士がコミュニケーションを取りながら連携する文化が醸成された

パイプラインが進化していくことで、より独立してフロントエンドやAPIを開発することができるようになり、各開発チームの自由度は格段に上がりました。

しかしその一方で、各チームが独立してデリバリーできるようになった結果、「サイロ化」に陥る可能性と常に隣り合わせになっています。自律分散的に開発できるチームであるからこそ、サイロ化を防ぎ、プロダクト全体の方向性を見失わないように、適度にコミュニケーションによる同期が不可欠なのです。

一朝一夕でコミュニケーションを取り続ける文化や技術が身に付くわけではないのですが、そこに対応するアクションとして私たちは「チームシャッフル」や「レンタル移籍」ということを行っています。

▼ 「チームシャッフル」や「レンタル移籍」の取り組みはこちら agilejourney.uzabase.com agilejourney.uzabase.com

まとめ:なぜ私たちはパイプラインを改善し続けるのか

マイクロサービス化とIstio、CDCテスト、Argo Rollouts……。なぜ私たちのチームはパイプラインを改善し続けるのでしょうか。

根源にはXPの価値であるフィードバックがあります。私たちは素早くリリースしてフィードバックを受けられるようにしたいのです。

デリバリーする対象が小さくなれば自然と影響範囲が予測できるようになりますし、ユーザーに公開するときの心理的な負担が小さくなります。1週間に一度しかないチャンスで「失敗したら来週にリリースが持ち越しになるかも」という不安を抱えてリリース作業に取り組みたいとは誰も思っていないのです。

今回紹介したような改善を通じて、リリースに対する不安が小さくなれば、デプロイしてリリースする頻度も増えフィードバックも増えるはずです。

XPの価値に沿って考え続けた結果、私たちは主体性を持ってリリースタイミングをコントロールする術を手に入れ、ステークホルダーとの期待値を調整することも容易になり、本番環境へと心理的な負担なくデプロイしリリースしていく文化を醸成することができました。

それでも私たちのパイプラインにはまだまだ解決したい課題が残っています。アプリケーションあたりのパイプラインの実行時間は10〜15分に落ち着いていることが多いのですが、これを数分程度まで縮めることができれば、開発のテンポがより良くなるはずです。Googleで導入されていると言われている「効率的にテストを選択して実行する仕組み」などを構築できれば、実行時間を短縮できる可能性を秘めています。

このように未着手の領域はたくさんあるので、これからも私たちの取り組みは続いていきます。

私たちのパイプライン改善の取り組みが、願わくば本稿を読んでいる皆さんの環境を改善していく一助になれば幸いです。

編集・制作:はてな編集部

デリバリーを加速させる技術を知る

あやぴー
あやぴー GitHub: ayato-p
Lispが好きで2015年より業務でのClojureを用いた開発を始める。2018年にユーザベースへ参画し、現在はSpeedaの開発に従事。ClojureやF#などの関数型言語を軸に、日々プロダクトの開発に取り組んでいる。