リリースエンジニアリングという分野および役職についての章。
- ソフトウェアのビルドとデリバリ
- ソフトウェア工学の中でも比較的新しく成長著しい分野
- 知識
- ソースコードマネジメント
- コンパイラ
- ビルド設定言語
- 自動ビルドツール
- パッケージマネージャ
- インストーラ
- スキルセット
- 開発
- 設定管理
- 統合テスト
- システム管理
- カスタマーサポート
- 信頼性の高いサービス運用のためには信頼性の高いリリースプロセスが必要
- リリースが繰り返し可能・再現可能かつ自動化された方法で行われることが保証されるべき
- リリースプロセスのあらゆる面に対する変更は予想外のものでなく、意図されたものであるべき
- リリースエンジニアリングは Google 内における特定の役職
- 製品開発において SWE (ソフトウェアエンジニア) と協働
- リリースに必要なすべてのステップを定義するために SRE と協働
- ソースコードリポジトリへのソースコードの格納方法
- コンパイル伸びるどルール
- テスト方法
- パッケージング
- デプロイ方法
- Google はデータドリブンな企業であり、リリースエンジニアも同様 (データドリブンである)
- コードの変更を本番環境にデプロイするのにかかる時間 (release velocity)、ビルド設定ファイルでどの機能が使用されているかをレポートするツールを持っている
- そのほとんどがリリースエンジニアによって企画、開発されたもの
- 一貫性があり繰り返し可能な方法でプロジェクトがリリースされることを保証するためのベストプラクティスを定義
- コンパイラフラグ
- ビルド ID タグのフォーマット
- ビルドに必要なステップ
- ツールが正しく動作し、ドキュメントが書かれていることで、チームが機能やユーザにフォーカスし続けられる
- Google にはプロダクトを安全にデプロイし、サービスを動かし続けることに責任をもつ SRE がたくさんいる
- リリースプロセスがビジネス要件にそっていることを保証するため、リリースエンジニアと SRE で協力して 変更を canary する方法、サービスを止めずに新しいリリースを出す方法、問題の発生した機能をロールバックする方法を開発している
リリースエンジニアリングは、工学と、以下の4つの大きな概念からなるサービス哲学によって概観される
- 大規模な環境に対応するため、チームは自給自足でなければならない
- リリースエンジニアリング (チーム) は、プロダクト開発チームが自身のリリースプロセスを制御し実行することを可能にする ベストプラクティスやツールを開発してきた
- 個々のチームがプロダクトの新バージョンのリリース頻度やタイミングを独自に決定できるため、高いリリース速度を達成できている
- リリースプロセスはエンジニアがほとんど関わらなくてよい程に自動化
- 自社開発の自動ビルドシステムやデプロイツールを組み合わせてビルド・リリースされている
- 問題発生時以外エンジニアは全く関与しない
- ユーザに直接公開されるソフトウェア (Google 検索の多くのコンポーネントなど) は頻繁にリビルドされる
- 新機能を出来る限り早めに公開したいという願いのため
- 頻繁なリリースはバージョン間の変更をより少なく留めることができるという哲学に基づく
- その方がテストやトラブルシューティングはより簡単になる
- 幾つかのチームは毎時ビルドを行い、それらのビルドのプールの中から実際に本番にデプロイするバージョンを選んでいる
- 選択基準は、テスト結果と、そのビルドに含まれる (新) 機能
- 他のあるチームは、 "Push on Green" リリースモデルを採用
- ひと通りのテストに通ったビルドを全てデプロイしている
(Hermetic はキーワードとして後でも出てくる。訳さないほうが良かったかも)
- ビルドツールは一貫性や再現性の保証を可能にするものでなければならない
- 2人の人が同じソースコードリポジトリにある同じリビジョン番号の同一のプロダクトを別々のマシンでビルドした際、同一の結果になる
- Google のプロダクトのビルドは外部の影響を受けない
- ビルドマシンにインストールされているライブラリや別のソフトウェアを考慮する必要が無い
- 代わりにコンパイラなどのビルドツールや依存ライブラリなどの既知のバージョンに依存する
- ビルドプロセスは自己完結的であり、ビルド環境の外部のサービスに依存してはならない
- 本番環境で動いているソフトウェアのバグに対処する必要が生じた際、過去のリリースをリビルドするのはチャレンジング
- Google では、オリジナルのビルドと同じリビジョンをベースに、その後追加された変更を取り込んだ上でリビルドすることでこれを実現している
- "cherry picking" と呼んでいる
- ビルドツールはビルド対象プロジェクトのソースコードリポジトリのリビジョンをベースに (独自に) バージョニングされている
- 先月ビルドされたプロジェクトで cherry pick が必要になった場合に今月時点で使用しているコンパイラのバージョンを使用しない
- 非互換の、または意図しない機能を含んでいる可能性があるため
- リリースの際に特定の操作を実行可能なメンバはいくつかのレイヤのセキュリティやアクセスコントロール
により決定される。制限付きの操作は以下のようなもの:
- ソースコード変更の承認 - この操作はコードベースに散らばった設定ファイルを通じて管理される
- リリースプロセス中に行われるアクションの指定
- 新しいリリースの作成
- initial integration proposal (ソースコードリポジトリの特定のリビジョン番号のビルドリクエスト) の承認と、その後の cherry pick
- 新しいリリースのデプロイ
- プロジェクトのビルド設定の修正
- コードベースへのほぼ全ての変更にはコードレビューが必要
- 洗練された形で? 自然な流れとして通常の開発ワークフローに組み込まれている
- 自動リリースシステムにより、リリースに含まれる全ての変更点のレポートが別のビルド成果物とともにアーカイブされる
- プロジェクトのリリースにどのような変更が含まれているか SRE が把握可能になることで、リリースで問題が発生した場合のトラブルシューティングが素早く行える
- Google は Rapid と呼ばれる自動リリースシステムを開発した
- スケーラブルで外部要因の影響を受けず、信頼性の高いリリースを行うための枠組みを提供する Google の技術を活用したシステム
- 以下のセクションで、 Google のソフトウェアライフサイクルと、それらが Rapid や関連する他のツールを用いてどのように実現されているかについて説明する
- Blaze2 は Google イチオシ (? オープンソースじゃ無さそうだった) のビルドツール
- Google の標準語である C++, Java, Python, Go, JavaScript を含む複数の言語のバイナリのビルドに対応
- エンジニアは Blaze をビルドターゲット (ビルド生成物、たとえば JAR など) の定義や、それらの依存関係の指定に使用
- ビルド実行時 Blaze はその依存ターゲットも自動でビルドする
- バイナリやユニットテストのためのビルドターゲットは Rapid のプロジェクト設定ファイル上に定義される
- プロジェクト特有のフラグ、例えば一意のビルド ID などは Rapid から Blaze に渡される
- 全てのバイナリはビルド日時、リビジョン番号、ビルド ID を表示するフラグをサポートしているので、あるバイナリから対応するビルド履歴に容易にたどりつける
- 全てのコードはソースコードツリーのメインブランチ (mainline) に check in される (マージ?)
- ただし、主要なプロジェクトのほとんどは mainline から直接はリリースしない
- リリースは mainline の特定のバージョンからブランチを作り、 mainline には決してマージしない
- バグフィックスは mainline に対して提出され、その後リリースに含めるために (リリース用の) ブランチに cherry pick される
- (リリース用 branch で?) 最初のビルドが行われた後に mainline に提出された無関係な変更をうっかりピックアップしてしまうのを防げる
- 継続的テストシステムは、 mainline に変更が提出される度にユニットテストを実施する
- ビルドやテストの失敗にすぐに気付ける
- リリースエンジニアリングチームは、プロジェクトのリリース判断 (gate) のためのものと同じテストターゲットを継続テストで実行することを推奨している
- 全てのテスト項目に通った最新の継続テストビルドのリビジョンをリリースに用いることを推奨している
- これらの基準により、 mainline に加わった後続の変更がリリース時のビルドで失敗を引き起こす可能性を減らせる
- リリースプロセス内で、リリース用 branch に対して再度ユニットテストを実行し、全てのテストに成功したことを示す監査証跡を作成する
- リリースが cherry pick を取り込んでいる場合にリリース branch が mainline のどこにも存在しないコードのバージョンを含む可能性があるため重要
- リリースされるコードそれ自体のコンテキストでテストに通ったことを保証
- 継続テストシステムを補完するため、パッケージ化されたビルド成果物に対してシステムレベルのテストを実行するための独立したテスト環境を使用している
- 手動、または Rapid により (自動で) 開始できる
- ソフトウェアは Midas Package Manager (MPM) を経由して本番環境のマシンに配布される
- MPM は Blaze のルール (含める成果物と、その所有者やパーミッションのリスト) をもとにパッケージを作成する)
- パッケージには名前がつけられ (例: search/shakespeare/frontend)、一意のハッシュによりバージョニングされ、真正性を保証するため署名される
- MPM はパッケージの特定のバージョンへのラベルの付与をサポートしている
- Rapid はビルド ID を含むラベルを付与し、それによりパッケージがパッケージ名とラベル名を用いて一意に参照される事が保証される
- 図 8-1 は Rapid システムのメインコンポーネントを示している (書籍参照)
- Rapid は blueprint と呼ばれるファイルにより設定する
- 内部的な(?)設定言語で書かれるビルドやテストのターゲットの定義、デプロイルール、管理情報 (プロジェクトオーナーなど) を定義するために使用される
- ロールベースアクセス制御リストにより、 Rapid プロジェクトに対する特定のアクションの実行権限を決めることができる
- それぞれの Rapid プロジェクトはリリースプロセス中に実行するアクションを定義するワークフローを持つ
- ワークフローアクションは直列、または並列に実行でき、有るワークフローは別のワークフローを開始できる
- Rapid は本番環境で Borg job (?) として動作するタスクに対する実行リクエストを送信する
- (Borg job というのがいわゆる PaaS 上で動くアプリケーションのようなもので、それを Rapid が立ち上げるということ? 前の章に出てきたりしたのかな)
- Rapid は Google の本番環境インフラを使用するため、何千ものリリースリクエストを同時に捌くことができる
- 典型的なリリースプロセスは以下の様なもの
- Rapid はリクエストされた統合リビジョン番号 (しばしば、継続テストシステムから自動的に取得する) を使用してリリース branch を作成する
- Rapid は Blaze を使用して全てのバイナリをコンパイルし、ユニットテストを実行する
- これらテストはしばしば並列実行される
- コンパイルやテストは Rapid のワークフロー実行環境で行われる Borg job とは対照的に、専用の環境で実行される。この分離により作業の並列化を容易に行える
- ビルド成果物はその後システムテストや canary 版のデプロイに利用できる状態になる
- 典型的な canary のデプロイには、システムテストの完了後に本番環境にて数個のジョブを開始するというのが含まれる
- プロセスのそれぞれのステップの結果はログに保存される。前回のリリース作成時からの全ての変更点のレポートが作成される
- Rapid により、 release branch や cherry pick の管理が容易になる。個別の cherry pick リクエストをリリースに含めるかどうかについて、承認またはリジェクトすることができる
- Rapid はしばしばシンプルなデプロイを直接行うために使用される
- blueprint ファイルに書かれたデプロイ定義や専用のタスク実行ツールをもとに、新しくビルドされた MPM パッケージを使うために Borg ジョブを更新する
- もっと複雑なデプロイ向けに、Sisyphus を使用する
- SREにより開発された汎用のロールアウト自動化フレームワーク
- ロールアウトとは、1つ以上の個別のタスクで構成される論理的な作業の単位
- あらゆるデプロイプロセスをサポートするために拡張することが可能な Python クラスの集合を提供する
- ロールアウトの実行方法の制御やロールアウトの進捗状況のモニタリングを可能にするダッシュボードを備えている
- 典型的なインテグレーションでは、 Rapid は長時間の Sisyphus ジョブにおいてロールアウトを作成する
- Rapid は、作成された MPM パッケージに紐付くビルドラベルを知っており、 Sisyphus でロールアウトを作る際にビルドラベルを指定できる
- Sisyphus はデプロイすべき MPM パッケージのバージョンを指定するためにビルドラベルを使用する
- Sisyphus により、ロールアウトプロセスは必要に応じてシンプルにも複雑にすることができる
- 例えば、関連するジョブ全てを直ちにアップデートするか、数時間かけて後続のクラスタ (Blue Green 的な意味で?または分散デプロイシステム的な意味で?) に新しいバイナリをロールアウトすることができる
- 我々のゴールは開発プロセスを各サービスのリスク特性にフィットさせること
- 開発環境や pre-production 環境では全てのテストに通れば毎時でビルドを走らせ自動でリリースを push できるかもしれない
- ユーザ向けの大きなサービスでは、まず1つのクラスタに push してから全てのクラスタに更新が行き渡るまで指数関数的に展開するかもしれない
- インフラの繊細な機能の場合は、ロールアウト数日間かけて徐々に複数の異なる地理的リージョンに展開していくかもしれない
- 設定管理は、リリースエンジニアと SRE の間で特に密なコラボレーションが行われる領域の一つ
- 一見シンプルな問題のように見えるが、設定の変更は不安定化の潜在的な原因になる
- Google のリリースや管理システムやサービス設定のアプローチは時とともに大きく発達してきた
- 以下に述べる通り、現在 Google は設定ファイルの配布に幾つかのモデルを用いている。全てのスキームにおいて、設定をプライマリソースコードリポジトリに保存し、厳格なコードレビューを必須としている
- 設定には mainline を使用する
- Borg (や Borg より前のシステム) でサービスを設定するために最初に導入した方法
- 開発者や SRE はメイン branch の head において設定ファイルを修正する
- 変更はレビューされ、稼働中のシステムに適用される
- 結果、バイナリリリースと設定変更 (のリビジョン?) は分離される
- 概念的、手続き的にシンプルだが、しばしばチェックインされた設定ファイルと稼働中の設定ファイルのバージョン間にずれを生じる
- なぜなら、変更を取り込むためにはジョブ (リリースジョブ? branch?) のアップデートが必要になるため
- 設定ファイルとバイナリを同じ MPM パッケージに入れる
- 設定ファイルがほとんど無いプロジェクトや、リリース毎にファイル (またはそのサブセット) が変更されるプロジェクトでは、設定ファイルはバイナリとともに MPM パッケージに入れることが可能
- バイナリと設定ファイルを密結合させるため柔軟性が失われるが、デプロイは1つのパッケージをインストールするだけでよくなるのでシンプルになる
- 設定ファイルを MPM "設定パッケージ" にパッケージングする
- 設定管理に "hermetic" の原則を適用することができる
- バイナリの設定は特定のバージョンのバイナリと密結合する傾向にあるので、ビルド・パッケージングシステムを活用して設定ファイルをそのバイナリと並行してスナップショットを撮り、リリースする
- バイナリの扱いと同様に、特定の日時時点での設定を再構築するためにビルド ID を使用できる
- 例えば、新しい機能を実装した変更を、その機能を設定するフラグの設定項目とともにリリースできる。バイナリ用と設定用の2つの MPM パッケージを生成することで、それぞれのパッケージを独立に変更できる機能性を維持できる。これは例えば、新しい機能が
first_folio
というフラグ設定とともにリリースされたが、実はそれがbad_quarto
の間違いだったと気づいたら、その設定変更をリリース branch に cherry pick し、設定パッケージをリビルドし、デプロイするといったことができる- 新しいバイナリのビルドを必要としないという利点がある
- インストールすべき MPM パッケージのバージョンを特定するために、 MPM のラベル機能が活用できる
- 前の段落で説明した MPM パッケージに
much_ado
といったラベルを付与し、このラベルを使用して両方の (バイナリと設定) パッケージを取得できる - プロジェクトの新しいバージョンがビルドされたら、
much_ado
ラベルはそれら新しいパッケージに付与される - このタグ (ラベル?) は MPM パッケージの名前空間内で一意であるため、そのタグの付いた最新のパッケージのみが使われる
- 前の段落で説明した MPM パッケージに
- 設定ファイルを外部のストアから読む
- 幾つかのプロジェクトは頻繁に、または動的に (例えばバイナリの稼働中に) 変化する必要のある設定ファイルを持つ
- それらのファイルは Chubby, Bigtable, またはソースベースファイルシステム (Google のプロダクト?) に保存できる
- 設定には mainline を使用する
- 要約すると、プロジェクトオーナーは設定を配布・管理する幾つかの異なる方法を検討し、ケースバイケースで最適 (と思われる) なものを採用する
特に Google のリリースエンジニアリングのアプローチと、リリースエンジニアがどのように SRE とコラボレーションしているかについて述べたが、このプラクティスはもっと幅広く (様々な組織に) 適用できると思われる
- 正しいツールと、適切な自動化と、適切に定義されたポリシーで武装することで、開発者と SREはソフトウェアのリリースで心配する必要はなくなるはず
- リリースはボタンを押すだけの簡単な作業になる
- 殆どの企業は、規模や使用しているツールにかかわらず、同じリリースエンジニアリングの問題を抱えている
- パッケージのバージョニングをどうすべきか?
- 継続的ビルド・デプロイモデルを使用すべきか、または定期的ビルドを行うべきか?
- どれくらいの頻度でリリースすべきか?
- どのような設定管理ポリシーを使うべきか?
- どのリリースメトリックが役立つか?
- オープンソースやベンダ提供のツールは Google の規模で上手く動かないため、必要にかられて独自のツールを開発してきた
- 独自ツールのため、リリースプロセスのポリシーをサポートする (にとどまらず強制までする) 機能を組み込める
- しかし、ツールに組み込む前にまずそれらポリシーを定義する必要があり、そしてすべての企業は、自動化するか否か、強制するか否かにかかわらず、リリースプロセスを定義するために努力するべき
- リリースエンジニアリングはしばしば結果論であり、またプラットフォームやサービスの規模や複雑性が上がるに連れてその考え方は必ず変化する
- チームは、プロダクト開発サイクルの初期の段階でリリースエンジニアリングリソースのための予算を用意すべき
- 良いプラクティスやプロセスを早い段階で導入するのは、後日システムに組み込むよりも低コスト
- 開発者、 SRE 、リリースエンジニアの協働は必須
- リリースエンジニアはどのような意図でコードがビルド、デプロイsれているかを理解する必要がある
- 開発者はビルドして "結果をフェンスの無効に投げ入れ" て、後はリリースエンジニアに任せっきりという姿勢でいるべきではない
- いつリリースエンジニアを呼びこむかは各プロジェクトチームが決める
- リリースエンジニアはまだ比較的新しい分野のため、マネージャはプロジェクトの早い段階で常にリリースエンジニアリングの計画や予算を組んでくれるわけではない
- 自分のプロダクトやサービスのライフサイクル全般に適用可能なものであることを確認すべき。プロジェクトの早い段階においては特に