毎年何十億人ものユーザーが Google のプロダクトやサービスを利用しています。検索、Gmail、マップ、YouTube、Chrome、などの主要なサービスには、現在では Google Cloud も含まれ、現代の生活にシームレスに統合されており、21 世紀のエクスペリエンスを定義できます。地球規模のこの影響は、Google のサービスが実証済みの品質を備えており、Google が常に利用可能であるという期待に起因しています。
Google Cloud では、プロダクトとサービスに継続的にコード変更を行い、可能な限り優れたユーザー エクスペリエンスを提供し、安全性と信頼度を向上させ、さらに規制とコンプライアンスの要件を遵守することを可能にしています。このような変更は、大小にかかわらず、バグが発生する原因となる場合があります。Google では、このリスクに対処するため、変更のライフサイクル全体で変更の安全性を優先しています。
このドキュメントでは、Google Cloud チームが Google の数十年にわたる開発の卓越性への投資に基づいて、開発のスピードと信頼性に対する Google Cloud のお客様の期待に応える信頼性に関するベスト プラクティスとエンジニアリング標準を実装する方法について説明します。
Google Cloud での変更のライフサイクル
Google Cloud プロダクト チームは、Google 全体の他のエンジニアリング チームと管理プロセスとツールの多くを共有しています。Google は、継続的インテグレーション(CI)と継続的デリバリー(CD)を優先する、チェンジ マネジメントのための標準的なソフトウェア開発アプローチを実装しています。CI では、特定のプロダクトやサービスに対して、頻繁に変更を提案、テスト、送信します。多くの場合、これらを 1 日に複数回行います。CD は CI の拡張機能であり、エンジニアはコードベースの最新の安定したスナップショットに基づいてリリース候補を継続的に準備します。
このアプローチでは、Google Cloud のお客様にできるだけ早く、かつできるだけ安全に変更を作成して段階的にロールアウトすることを優先しています。Google では、コードを記述する前に変更の安全性を考慮し、本番環境に変更をロールアウトした後も引き続き安全性に重点を置いています。Google のチェンジ マネジメント モデルには、設計、開発、認定、ロールアウトの 4 つの一般的なフェーズがあります。これらの 4 つのフェーズは次の図に示されています。このドキュメントでは、これらのフェーズについて詳しく説明します。
安全性を考慮した設計
Google は、開発プロセスの早い段階で小さなミスがあったとしても、後で大きな問題を引き起こし、カスタマー エクスペリエンスに大きな影響を与える可能性があることを認識しています。そのため、すべての大きな変更は、承認済みの設計ドキュメントから開始する必要があります。エンジニアリング チームが大きな変更を提案するための共通の設計ドキュメント テンプレートがあります。この共通の設計ドキュメントにより、Google Cloud プロダクト全体の大きな変更を一貫して評価できます。次の図は、大規模な変更の標準設計プロセスを示しています。
設計フェーズは、ソフトウェア デベロッパーがビジネス、技術、費用、メンテナンスの要件に対応する変更を提案したときに開始されます。変更を送信すると、シニア エキスパート(信頼性やセキュリティに関する専門家、技術リードなど)による包括的な審査と承認プロセスが開始されます。変更を実装する作業は、設計を提案したエンジニアが専門家からのフィードバックにすべて対応し、各専門家が設計を承認した後にのみ進めることができます。この設計とレビューのプロセスにより、本番環境でお客様に悪影響を及ぼす可能性のある変更に Google Cloud プロダクト チームが着手する可能性を低減します。
安全な開発の実施
Google のコード開発プロセスにより、コードの品質と信頼度が向上します。提案された変更が承認されると、開発プロセスは新しいエンジニアの包括的なオンボーディングから始まります。オンボーディングには、トレーニング、メンターシップ、提案されたコード変更に関する詳細なフィードバックが含まれます。手動テストと自動テストを組み合わせた多層の開発とテストのアプローチにより、開発のすべての段階でコードが継続的に検証されます。各コードの変更は、Google の高い標準を満たしていることを確認するために慎重に審査されます。
次の図は、開発プロセスの概要をイラスト化したワークフローを示しています。
開発フェーズは、エンジニアがコードと対応する単体テストとインテグレーション テストの作成を開始することから始まります。このフェーズを通して、エンジニアは、作成したテストと一連の送信前テストを実行して、コードの追加と変更が有効であることを確認できます。コード変更を完了してテストを実行した後、エンジニアはコードに精通している他の人に個別審査を依頼します。多くの場合、この人間による確認プロセスは反復的なものであり、コードの修正が追加される可能性があります。作成者と審査担当者が合意に達したら、作成者がコードを送信します。
コーディング標準により高品質な変更を実現
Google のエンジニアリング文化、プラクティス、ツールは、コードが正確、明確、簡潔かつ効率的である状態を確保するように設計されています。Google のコード開発は、世界最大の統合コード リポジトリである monorepo で行われます。monorepo には数百万個のソースファイルと数十億行のコードが保存され、変更リストと呼ばれる数億個の commit の履歴があります。急速に増加し続けており、平日は数万件の新しい変更リストが追加されています。monorepo の主なメリットは、コードの再利用が促進され、依存関係の管理が容易になるとともに、プロダクトとサービス全体で一貫したデベロッパー ワークフローが適用されることです。
コードの再利用は、再利用されたコードが本番環境でどのように動作するかをすでに把握しているため、メリットをもたらします。すでに存在する高品質のコードを利用することで、変更は本質的に堅牢になり、必要な標準で維持することが容易になります。この手法では、時間と労力を節約できるだけでなく、コードベースの全体的な健全性が高水準で維持され、より信頼性の高いプロダクトにつながります。
高品質のオープンソース ソフトウェアをベースとする Google Cloud サービスでは、monorepo に別のリポジトリ(通常は Git)を追加して、ブランチを使用しオープンソース ソフトウェアを管理できます。
トレーニングに関する注意事項
コード品質への投資は、エンジニアがチームに加わったときに開始されます。エンジニアが Google に新規入社した場合や、チームのインフラストラクチャとアーキテクチャに精通していない場合は、包括的なオンボーディングを受けます。このオンボーディングの一環として、スタイルガイド、ベスト プラクティス、開発ガイドを学習し、実践的な演習を手動で実施します。また、新しいエンジニアが変更リストを送信するには、その都度追加の承認レベルが必要になります。特定のプログラミング言語での変更の承認は、専門知識に基づく厳格な要件を満たし、対象のプログラミング言語に関する可読性を認定されたエンジニアによって行われます。どのエンジニアもプログラミング言語における可読性を認められることができます。ほとんどのチームには、コードを記述するプログラミング言語の承認者が複数います。
シフトレフトで安全に速度を向上させる
シフトレフトとは、テストと検証を開発プロセスの早い段階に移す原則です。この原則は、リリース プロセスの後半でバグが検出されて修正されると、コストが大幅に増加するという Google の調査に基づいています。極端な例として、お客様が本番環境で見つけたバグについて考えてみましょう。このバグはお客様のワークロードとアプリケーションに悪影響を及ぼす可能性があります。また、関連するエンジニアリング チームがバグを軽減する前に、お客様が Cloud カスタマーケアのプロセスを完了することが必要な場合もあります。問題を解決するために割り当てられたエンジニアが、バグを含む変更を最初に実施したエンジニアとは異なる場合、新しいエンジニアはコード変更について熟知する必要があります。その場合は、バグを再現して最終的に修正するまでに要する時間が増加する可能性があります。このプロセス全体には、お客様と Google Cloud のサポートが多大な時間を費やし、さらにエンジニアが作業を中断して問題を解決する必要があります。
逆に、エンジニアが開発中の変更に取り組んでいるときに、自動テストの失敗によって検出されたバグについて考えてみましょう。エンジニアはテストの失敗を確認すると、直ちに修正できます。Google のコーディング標準により、エンジニアはテストに失敗した変更を送信することさえできません。この早期検出により、エンジニアはお客様に影響を与えることなくバグを修正でき、コンテキスト切り替えのオーバーヘッドがなくなります。
後者のシナリオは、関係者全員にとってこのうえなく望ましいものです。そのため、Google Cloud は長年にわたり、この「シフトレフト」の原則に多大な投資を行い、従来は変更の認定とロールアウトのフェーズで行われていたテストを開発ループに直接移行してきました。現在、エンジニアがコード変更を提案している間、すべての単体テスト、最大規模のインテグレーション テストを除くすべてのインテグレーション テスト、広範な静的解析と動的解析が並行して完了します。
自動化された送信前テストでコーディング標準を適用する
送信前テストは、特定のディレクトリで変更が送信される前に実行されるチェックです。送信前テストは、変更に固有の単体テストとインテグレーション テスト、または任意の変更に対して実行される一般的なテスト(静的分析や動的分析など)として使用できます。これまで、送信前のテストは、誰かがコードベースに変更を送信する前の最後のステップとして実行されていました。現在、シフトレフトの原則と CI の実装を理由の一部として、エンジニアが開発環境でコードを変更する間、および変更を monorepo に統合する前に、送信前テストを継続的に実行しています。エンジニアは、開発 UI でワンクリックするだけで送信前テストスイートを手動で実行することもできます。すべての送信前テストは、人間によるコードレビューの前に各変更リストに対して自動的に実行されます。
通常、送信前テストスイートには、単体テスト、ファズテスト(ファジング)、密閉型のインテグレーション テスト、静的コード解析と動的コード解析が含まれます。Google 全体で広く使用されているコアライブラリやコードの変更については、デベロッパーはグローバルな送信前テストを実行します。グローバルな送信前テストでは、Google のコードベース全体に対して変更がテストされ、提案された変更が他のプロジェクトやシステムに悪影響を及ぼすリスクを最小限に抑えることができます。
単体テストとインテグレーション テスト
コード開発プロセスでは、徹底したテストが不可欠です。すべての社員がコード変更の単体テストを作成する必要があります。また、Google では、想定される動作を検証するために、プロジェクト レベルでコード カバレッジを継続的に追跡しています。また、クリティカル ユーザー ジャーニーには、必要なすべてのコンポーネントと依存関係の機能を検証するインテグレーション テストが必要です。
単体テストと、最も大規模なインテグレーション テストを除くすべてのインテグレーション テストは、迅速に完了するように設計されており、分散環境において高い並列性で段階的に実行されるため、迅速かつ継続的な自動開発フィードバックが得られます。
ファジング
単体テストとインテグレーション テストによって、事前定義された入力と出力で想定される動作を検証できますが、ファジングは、セキュリティ上の脆弱性やクラッシュにつながる隠れた欠陥や弱点を検出することを目的として、ランダムな入力をアプリケーションに大量に送信する手法です。ファジングにより、ソフトウェアの潜在的な弱点を事前に特定して対処できるため、お客様が変更を操作する前にプロダクトの全体的なセキュリティと信頼度を強化できます。このテストのランダム性は特に有用です。これは、ユーザーが Google のサービスを予想外の興味深い方法で操作する場合があるためです。また、ファジングは、手動で検討しなかったシナリオを考慮するのに有効です。
静的分析
静的分析ツールは、開発ワークフローでコード品質を維持するうえで重要な役割を果たします。静的分析は、問題のある C++ コードパターンを特定するために正規表現で lint チェックを行っていた初期の段階から大きく進化しています。現在、静的分析は Google Cloud のすべての本番環境言語を対象とし、誤りのあるコードパターン、非効率なコードパターン、非推奨のコードパターンを検出します。
高度なコンパイラ フロントエンドと LLM により、エンジニアがコードを記述している際に改善を自動的に提案できます。提案された各コード変更は、静的分析で精査されます。新しい静的チェックが追加されるにつれて、コードベース全体がコンプライアンスについて継続的にスキャンされ、修正が自動的に生成されて審査のために送信されます。
動的分析
静的分析は、問題につながる既知のコードパターンの特定に重点を置いていますが、動的分析は別のアプローチをとっています。コードをコンパイルして実行し、実行中にのみ表面化する問題(メモリ違反や競合状態など)を検出します。Google は動的分析を長年にわたって活用しており、広範囲のデベロッパー コミュニティと以下のようないくつかのツールを共有しています。
- AddressSanitizer: バッファ オーバーフローや use-after-free などのメモリエラーを検出します。
- ThreadSanitizer(C++、Go): データ競合やその他のスレッドバグを検出します。
- MemorySanitizer: 初期化されていないメモリ使用量を検出します。
これらのツールや同様のその他のツールは、静的分析だけでは検出できない複雑なバグを検出するために不可欠です。Google は、静的解析と動的解析の両方を使用して、コードが適切に構造化され、既知の問題がないこと、および実際のシナリオで想定どおりに動作することを確認しています。
人間によるコードレビューで変更とテスト結果を検証する
エンジニアがコードの重要なマイルストーンに達し、メイン リポジトリに統合する場合は、変更リストを提案してコードレビューを開始します。コードレビュー リクエストは次の要素で構成されます。
- 変更の目的と追加のコンテキストについての説明
- 実際に変更されたコード
- 変更されたコードの単体テストとインテグレーション テスト
- 自動化された送信前テストの結果
開発プロセスのこの段階で、別の人間が介入します。1 人以上の指定されたレビュー担当者が、添付されたテストと送信前のテスト結果をガイドとして、変更リストの正確性と明確性を慎重に確認します。各コード ディレクトリには、コードベースのサブセットの品質を確保する責任を負う一連の指定されたレビュー担当者がいます。このディレクトリ内で変更を行うには、そのレビュー担当者の承認が必要です。レビュー担当者とエンジニアが協力して、提案されたコード変更で発生する可能性のある問題を特定し、対処します。変更リストが Google の標準を満たしている場合、レビュー担当者は承認(「LGTM」、「Looks Good To Me」の略)を付与します。ただし、使用しているプログラミング言語のトレーニングをエンジニアが引き続き受けている場合は、プログラミング言語の可読性を認定されたエキスパートから追加の承認を得る必要があります。
変更リストがテストと自動チェックに合格し、LGTM を付与された後、変更を提案したエンジニアは、コードに最小限の変更のみを加えることが許可されます。大幅な変更を加えると、承認が無効になり、もう一度審査を受ける必要があります。小さな変更でも、元の審査担当者に自動的に報告されます。エンジニアが最終的なコードを送信すると、変更リストが monorepo にマージされる前に、もう一度すべての送信前テストが実施されます。テストが失敗した場合、送信は拒否され、デベロッパーと審査担当者には、変更を再度送信しようとする前に是正措置を講じるように求めるアラートが届きます。
安全なリリースの認定
送信前のテストは包括的なものですが、Google でのテストプロセスはこれで終わりではありません。多くの場合にチームには、大規模なインテグレーション テストなど、最初のコードレビュー中に実行できない追加のテストがあります(実行に時間を要するか、高忠実度のテスト環境が必要な場合があります)。さらに、チームは、外部依存関係の変更など、制御できない要因によって発生する障害に注意を払う必要があります。
そのため、Google では開発フェーズ後に認定フェーズを義務付けています。この認定フェーズでは、次の図に示すように、継続的なビルドとテストのプロセスが使用されます。
このプロセスでは、前回の実行以降に行われた直接的または間接的な変更による影響を受けたすべてのコードに対して、定期的にテストが実行されます。エラーは、担当のエンジニアリング チームに自動的にエスカレーションされます。多くの場合、システムは不具合の原因となった変更リストを自動的に特定し、ロールバックできます。これらの大規模なインテグレーション テストは、部分的にシミュレートされた環境から物理的なロケーション全体に至るまで、さまざまなステージング環境で実行されます。
テストには、基本的な信頼性と安全性からビジネス ロジックまで、適格性に関するさまざまな目標があります。これらの適格性テストには、次のようなコードのテストが含まれます。
- 必要な機能を付与するための機能。大規模なインテグレーション テストを使用してテストされます。
- ビジネス要件を満たすための機能。これは、お客様のワークロードの合成表現でテストされます。
- 基盤となるインフラストラクチャの障害を維持するための機能。これは、スタック全体に障害を挿入することによってテストされます。
- サービス容量を維持するための機能。これは、負荷テスト フレームワークでテストされます。
- 安全にロールバックするための機能
安全なロールアウト
最も強固な開発、テスト、認定プロセスを実施しても、本番環境に欠陥が潜在してユーザーに悪影響を及ぼす場合があります。このセクションでは、Google Cloud のロールアウト プロセスが欠陥のある変更の影響を制限し、リグレッションを迅速に検出する仕組みについて説明します。Google では、このアプローチを、バイナリ、構成、スキーマの更新、容量の変更、アクセス制御リストなど、本番環境にデプロイされるすべてのタイプの変更に適用しています。
変更の伝播と監督
Google Cloud 全体に変更をデプロイする際には、一貫したアプローチを適用して、お客様への悪影響を最小限に抑え、問題を個別の論理障害ドメインと物理障害ドメインに分離します。このプロセスは、Google が数十年にわたって培ってきた SRE 信頼性手法と地球規模のモニタリング システムに基づいており、不正な変更を可能な限り迅速に検出して軽減します。迅速な検出により、お客様への通知を迅速に行い、是正措置を講じて、同様の障害が再発しないように体系的に防止できます。
ほとんどの Google Cloud プロダクトはリージョンまたはゾーンタイプです。つまり、リージョン A で実行されているリージョン プロダクトは、リージョン B で実行されている同じプロダクトとは独立しています。同様に、リージョン A 内のゾーン C で実行されているゾーン プロダクトは、リージョン A 内のゾーン D で実行されている同じゾーン プロダクトとは独立しています。このアーキテクチャによって、他のリージョンまたは単一リージョン内の他のゾーンに影響するサービス停止のリスクを最小限に抑えます。IAM や Google Cloud コンソールなどの一部のサービスは、すべてのリージョンにわたってグローバルに整合性のあるレイヤを設定します。Google がこれらのサービスをグローバル サービスと呼んでいるのは、このことが理由です。グローバル サービスはリージョン間で複製されるため、単一障害点を回避し、レイテンシを最小限に抑えることができます。共有の Google Cloud ロールアウト プラットフォームは、サービスがゾーン内、リージョン内、グローバルのいずれであるかを認識し、本番環境の変更を適切にオーケストレートします。
Google Cloud のロールアウト プロセスでは、複数のターゲット ロケーションにデプロイされたサービスのすべてのレプリカがウェーブに分割されます。最初のウェーブには少数のレプリカが含まれ、更新は順番に行われます。最初のウェーブは、大部分のお客様のワークロードの保護と、ワークロードの多様性への露出を最大化することを適切なバランスで実施して、問題を可能な限り早期に検出するようにします。また、一般的なお客様のワークロード パターンを模倣した合成ワークロードが含まれます。
ターゲット ロケーションでサービス レプリカが更新されてもロールアウトが成功する場合、その後のロールアウト ウェーブのサイズは徐々に増加し、より多くの並列処理が導入されます。Google Cloud のロケーションの数を考慮するには、ある程度の並列処理が必要ですが、異なるウェーブに存在するロケーションの同時更新は禁止されています。ウェーブが夜間または週末にまで及ぶ場合は、進行を完了できますが、ロールアウトを管理するチームの営業開始時刻まで新しいウェーブを開始することはできません。
次の図は、Google Cloud 全体でリージョン内のプロダクトとサービスに Google が使用するロールアウト ロジックを示すワークフローの例です。
Google Cloud のロールアウト プロセスでは、Canary Analysis Service(CAS)を使用して、ロールアウト期間全体で A/B テストを自動化します。一部のレプリカはカナリア(サービス内の変更の部分的な時間制限のあるデプロイ)になり、残りのレプリカは変更を含まないコントロール グループを構成します。ロールアウト プロセスの各ステップには、サービスがすべての機能を適切に実行し、潜在的な異常が CAS によって検出されるように、次のステップに進む前に、顕在化するまでに時間を要する問題を検出するためのベーキング時間が設けられています。ベイク時間は、顕在化するまでに時間を要する問題の検出と開発速度のバランスをとるために慎重に定義されています。Google Cloud のロールアウトには通常 1 週間を要します。
次の図は、CAS ワークフローの概要を示しています。
ワークフローは、ロールアウト ツールがカナリア レプリカに変更をデプロイすることから始まります。ロールアウト ツールは、CAS に判定結果をリクエストします。CAS は、コントロール グループに対してカナリア レプリカを評価し、PASS または FAIL の判定結果を返します。ヘルスシグナルでエラーが発生した場合は、サービス オーナーにアラートが生成され、ロールアウトの実行中のステップが一時停止またはロールバックされます。変更によって外部ユーザーに対するサービス提供が停止した場合は、外部インシデントが宣言され、Personalized Service Health サービスを使用して影響を受けるユーザーに通知されます。インシデントによって内部審査もトリガーされます。Google のポストモーテム フィロソフィーでは、適切な一連の是正措置を特定して適用し、同様の障害が再発する可能性を最小限に抑えます。
シグナルとロールアウト後の安全性のモニタリング
ソフトウェアの欠陥は直ちに顕在化するとは限らず、発生するには特定の状況を必要とする場合もあります。そのため、Google はロールアウトが完了した後も本番環境システムを継続的にモニタリングしています。長年の取り組みにより、ロールアウトで直ちに問題が発生しなくても、ロールアウトの不具合が本番環境のインシデントの原因となる可能性が高いことが判明しています。そのため、Google の本番環境プレイブックでは、インシデント レスポンダーが最近のロールアウトと検出された問題を関連付け、インシデントの根本原因として最近の変更を除外できない場合は、デフォルトで最近のロールアウトをロールバックするように指示しています。
ロールアウト後のモニタリングは、ロールアウト期間中の自動 A/B 分析に使用されるのと同じ一連のモニタリング シグナルに基づいています。Google Cloud のモニタリングとアラートの考え方では、自己診断型(ホワイトボックス)と合成型(ブラックボックス)の 2 種類のモニタリングが組み合わされています。自己診断型のモニタリングでは、CPU 使用率、メモリ使用率、その他の内部サービスデータなどの指標が使用されます。合成モニタリングは、お客様の視点からシステムの動作を調査し、プローバー サービスから送信された合成トラフィックに対するサービスエラー率とレスポンスを追跡します。合成モニタリングは症状を重視し、アクティブな問題を特定します。一方、自己診断型のモニタリングでは、確認済みの問題を診断し、場合によっては差し迫った問題を特定できます。
一部のお客様にのみ影響するインシデントの検出を支援するため、Google はお客様のワークロードを関連するワークロードのコホートにクラスタ化します。コホートのパフォーマンスが標準から逸脱すると直ちに、アラートがトリガーされます。これらのアラートにより、集計パフォーマンスが正常に見える場合でも、お客様固有のリグレッションを検出できます。
ソフトウェア サプライ チェーンの保護
Google Cloud チームが変更を行うたびに、Binary Authorization for Borg(BAB)と呼ばれるセキュリティ チェックを使用して、ソフトウェア サプライ チェーンと Cloud のお客様をインサイダー リスクから保護しています。BAB はコードレビュー ステージから始まり、本番環境にデプロイされたコードと構成の監査証跡を作成します。本番環境の整合性を確保するため、BAB では次の条件を満たす変更のみを許可します。
- 改ざん防止と署名がされている
- 特定のビルドパーティと特定のソース ロケーションから送信されている
- コード作成者とは異なる第三者によって審査され、明示的に承認されている
独自のソフトウェア開発ライフサイクルで同じコンセプトの一部を適用することをお考えの場合は、ソフトウェア アーティファクトのためのサプライ チェーン レベル(SLSA)というオープン仕様に BAB の主なコンセプトが記載されています。SLSA は、本番環境でコードを開発して実行するためのセキュリティ フレームワークとして機能します。
まとめ
Google Cloud は、Google が数十年にわたって開発の卓越性に投資してきた成果に基づいて構築されています。コードの健全性と本番環境の健全性は、Google のすべてのエンジニアリング チームに浸透している文化の基本原則です。Google の設計レビュー プロセスでは、コードと本番環境の健全性への影響を早期の段階で検討します。Google の開発プロセスは、シフトレフトの原則と広範なテストに基づいており、設計アイデアが安全かつ正確に実装されるようにします。Google の認定プロセスでは、大規模な統合と外部依存関係を対象にテストをさらに拡張しています。最後に、ロールアウト プラットフォームにより、特定の変更が実際に想定どおりに動作するという確信を徐々に高めることができます。コンセプトから本番環境までをカバーするこのアプローチにより、Google Cloud のお客様が期待する開発速度と信頼度の両方を実現できます。