インフラのリリース自動化戦略とその行き着く先

こんにちは、@ueokandeです。 本番リリースってドキドキしますよね。 本日はkintone.comのリリース自動化と、その戦略についてお話します。

kintone.comのCI/CDパイプライン

kintone.comのインフラ構成はモノレポで管理しており、AWSの構成や、Kubernetes上にデプロイするサービスなどが1つのレポジトリに存在します。 現在のkintone.comは、開発環境、ステージング環境、本番環境の3つがあります。 適用タイミングをずらすことによる環境間の乖離を防ぐため、各リリースはすべての環境に適用することとしました。 開発環境でしばらく寝かせたい変更は、機能フラグやカナリアによって切り替えます。

CI/CDパイプラインは以下のようになっています。 それぞれの環境に順に適用し、本番環境適用後にテストが通れば無事リリース完了です。

kintone.comのCI/CDパイプライン
kintone.comのCI/CDパイプライン

kintone.comのローンチ前に本番環境を新しく作るとき、安全に本番リリースする仕組みが必要だという議論がありました。 その議論では以下の要件が挙がりました。

  • インフラの構成やサービスの設定値はコードで表現されている
  • オペミスや属人化を防ぐため適用作業は自動化されている
  • デプロイに失敗したり問題が発生したら即座に切り戻すことができる

リリースの自動化だけではこれらの要件を満たせません。 ある人のリリース作業がほかの人に影響する可能性を考慮する必要があります。 これらの要件を満たすリリース戦略は、開発が進むに連れて改善と進化を遂げてきました。

戦略1: リリースロック

まず最初の戦略では、すべてのリリース作業をロックして排他制御しました。 誰かのリリースが適用されているときは、他の人はリリースできません。 単純な実装ではありますが、いくつかのメリットがあります。

  • リリース中に問題が発生しても修正するまで他のリリースをブロックできる
  • 切り戻し作業がgit revertで確実にできる

リリースの状態管理はGitHub Issuesにラベルをつけて管理します。 GitHub Issuesを見ると、現在適用中の変更や、誰が適用してるかをひと目で確認できます。

GitHub Issues上でリリースをロックする
GitHub Issues上でリリースをロックする

マージ前にロックを確保する必要があるため、Pull Requestのマージボタンは使いません。 代わりに、以下の処理を行うCLIツールを作りました。

  1. GitHub Issues上でロックを取る
  2. ロック取得に成功したらPull Requestをマージする
  3. 本番適用後にテストが通れば、Issueを閉じてロックを開放する

戦略1.1: リリースロック + GitHub Actions

CLIツールを使うと、GitHubの画面とターミナルを往復する必要があり、それが面倒だったのでリリースロックの仕組みをGitHub Actionsに移行しました。 Pull Request上でマージ命令をコメントすると、GitHub ActionsのBotが自動でマージします。

GitHub Actionsによるリリースロックの拡張
GitHub Actionsによるリリースロックの拡張

GitHub ActionsのYAMLファイルの一部を紹介します。 Issueコメント (IssueコメントとPull Requestコメントは同じイベントが発火する)がつけられたときにGitHub Actionsが実行され、CLIツールを呼び出します。

name: Merge and Release
on:
  issue_comment:
    types: created
jobs:
  merge-and-release:
    name: Merge and release a pull request
    runs-on: ubuntu-latest
    if: github.event.issue.state == 'open' && startsWith(github.event.comment.body, '/')
    steps:
      # on_going_releaseラベルのあるIssueがあるかチェックして、
      # なければロックを取得してリリース開始

チーム内でリリース頻度が上がってくると、リリースのロック確保に失敗したり、他の人のリリースが終わるのをじっと待つことが多くなりました。 この戦略のままでは、チーム全体のリリース速度が上がらないということに気付きました。 そこでリリース速度を上げるために、ロックする戦略から、 キューイング する戦略へと移行しました。

戦略2: リリースキュー

リリースロックは、他のリリース状況の確認や、ロック中の待ち時間が課題でした。 そこでリリースするPull Requestをキューに積むことで、ロックの確認によるストレスや待ち時間を減らします。 キューに積まれたリリースは順次適用され、あるリリースが本番環境まで適用されたら、次のリリースが開始します。

これも同じく、GitHub Issue上でリリースのキューを作りました。 あるリリースの本番適用が完了してIssueが閉じられると、GitHub Actionsが発火して次のリリースを開始します。

GitHub Actions上でリリースをキューイング
GitHub Actions上でリリースをキューイング

GitHub ActionsのYAMLファイルの一部を紹介します。 on_going_release のIssueが閉じられたタイミングで、ready_to_release のあるIssueの先頭1つを on_going_release に切り替えます。

name: Release queued PRs
on:
  issues:
    types: [closed]
jobs:
  do-merge-commands:
    name: Release queued Pull Request
    runs-on: ubuntu-latest
    if: contains(github.event.issue.labels.*.name, 'on_going_release')
    steps:
      # `ready_to_release` のあるIssueの先頭1つを `on_going_release` に
      # 切り替えてリリースを開始する。

リリースキューもロックの戦略と同じで、本番環境へのリリースは排他的に行われます。 そのため問題が生じたとしても、他のリリースによって問題が悪化することを防ぎます。

GitHub社の例

ここまでkintone.comのリリース戦略を見てきました。 kintone.comはリリース戦略が、ロックからキューへと進化してきました。 実はGitHub社のリリース戦略も、同じ進化をたどってきたようです。

GitHub社は、リリースキューよりも更に速度を出すために、リリーストレイン という戦略でリリースしています。 この戦略は、いくつかのリリース候補を「トレイン」に乗せて、時刻になればトレインに積まれた変更が本番にリリースされます。 キューの待ちを減らして、リリースする本人が適用タイミングを選ぶことができます。

GitHub社のCI/CDに関する情報は、以下のスライドで説明されています。

おわりに

この記事ではkintone.comのリリース自動化戦略と、GitHub社のリリース戦略を紹介しました。 我々はまだリリースキューでとどまっていますが、リリース回数が多い日はキューでも待たされることがあります。 一方で本番適用の影響を最小限にするため、複数の変更を同時に適用するのを避けて、リリーストレインに踏み切れないという気持ちもあります。

将来kintone.comの規模がさらに大きくなるとリリーストレイン、または別の戦略が必要になるかもしれません。 その時は一体どのような戦略でリリースするのでしょうか。 時が来たら改めて記事を書きたいと思います。