Google Cloud アーキテクチャ フレームワークのこのドキュメントでは、障害を許容し、顧客の需要に応じてスケールできるようにサービスを設計するための設計原則について説明します。信頼性の高いサービスは、サービスの需要が高い場合やメンテナンス イベントが発生した場合でも、顧客からのリクエストに継続的に対応します。次の信頼性設計の原則とベスト プラクティスは、システム アーキテクチャとデプロイの計画に組み込む必要があります。
冗長性を作成して高可用性を実現
信頼性の高いシステムには単一障害点がなく、リソースは複数の障害発生ドメイン間で複製される必要があります。障害発生ドメインとは、VM、ゾーン、リージョンなど、独立して障害が発生する可能性のあるリソースのプールです。複数の障害発生ドメイン間でレプリケーションを行うと、個別のインスタンスよりも達成できる集計のレベルが高くなります。詳細については、リージョンとゾーンをご覧ください。
システム アーキテクチャの一部である冗長性の具体的な例として、DNS 登録で発生した障害を個々のゾーンに隔離するには、同じネットワーク上のインスタンスが相互にアクセスできるようにゾーン DNS 名を使用します。
高可用性のためにフェイルオーバーを備えたマルチゾーン アーキテクチャを設計する
複数のゾーンに分散されたリソースのプールを使用し、アプリケーションにデータ レプリケーション、ロード バランシング、ゾーン間の自動フェイルオーバーを設計することで、ゾーン障害に対する復元力を高めます。アプリケーション スタックの各レイヤでゾーンレプリカを実行し、アーキテクチャ内のゾーン間の依存関係をすべて排除します。
障害復旧のために複数のリージョン間でデータを複製します。
リモート リージョンにデータの複製やアーカイブを行い、リージョンの停止やデータ損失が発生した場合に障害復旧を可能にします。レプリケーションを使用すると、レプリケーションの遅延で少量のデータ損失が発生する可能性がありますが、リモート リージョンのストレージ システムに最新のデータが保存されているため、復旧時間を短縮できます。継続的なレプリケーションの代わりに定期的なアーカイブを使用する場合、障害復旧では、新しいリージョンでバックアップやアーカイブからデータを復元します。この手順は通常、継続的に更新されるデータベース レプリカを有効にする場合よりもサービスのダウンタイムが長くなり、連続するバックアップ オペレーション間のギャップにより、失われるデータが増える可能性があります。どちらの方法を採用する場合も、アプリケーション スタック全体を再デプロイして新しいリージョンで起動する必要があります。この間、サービスは利用できなくなります。
障害復旧のコンセプトと手法の詳細については、クラウド インフラストラクチャの停止に対する障害復旧の設計をご覧ください。
リージョンの停止に対する耐障害性を確保するためにマルチリージョン アーキテクチャを設計します。
まれなケースですが、リージョン全体で障害が発生してもサービスを継続的に実行する必要がある場合は、異なるリージョンに分散されたコンピューティング リソースのプールを使用するようにサービスを設計します。アプリケーション スタックの各レイヤでリージョン レプリカを実行します。
リージョン間でのデータ レプリケーションを使用し、リージョンが停止したときに自動フェイルオーバーを使用します。Spanner など、一部の Google Cloud サービスにはマルチリージョンのバリアントがあります。リージョンの障害に対する復元力を確保するため、可能であれば、マルチリージョン サービスを設計してください。リージョンと利用可能なサービスの詳細については、Google Cloud のロケーションをご覧ください。
リージョンレベルの障害の範囲が対象リージョンに限定されるように、リージョン間の依存関係がない状態にしてください。
単一リージョンのプライマリー データベースなど、アクセスできないときにグローバルな停止を引き起こす可能性があるリージョンの単一障害点を排除します。マルチリージョン アーキテクチャではコストが高くなることが多いため、このアプローチを採用する前に、コストに対するビジネスニーズを検討してください。
障害ドメイン全体での冗長性の実装については、クラウド アプリケーションのデプロイ アーキタイプ(PDF)の調査報告をご覧ください。
スケーラビリティのボトルネックを排除する
単一の VM または単一のゾーンのリソース制限を超えて拡張できないシステム コンポーネントを特定します。垂直方向にスケーリングされるアプリケーションでは、負荷の増加に対応するため、単一の VM インスタンスにより多くの CPU コア、メモリ、ネットワーク帯域幅が追加される場合があります。これらのアプリケーションのスケーラビリティにはハードリミットがあり、成長に対応するために手動による構成が必要になります。
可能であれば、VM またはゾーン間でのシャーディングやパーティショニングなどの水平スケーリングを行うように、これらのコンポーネントを再設計してください。トラフィックや使用量の増加に対応するために、より多くのシャードを追加します。シャードごとの負荷の増加に対応するために、自動的に追加される標準の VM タイプを使用します。詳細については、スケーラブルで復元性の高いアプリのためのパターンをご覧ください。
アプリケーションを再設計できない場合は、ユーザーによって管理されるコンポーネントをユーザーのアクションなしで水平方向にスケールするように設計されたフルマネージド クラウド サービスに置き換えることができます。
過負荷状態の場合にサービスレベルを適切に下げる
過負荷状態に耐えられるようにサービスを設計します。過負荷を検出し、サービスレベルを下げてレスポンスをユーザーに返すか、一部のトラフィックをドロップするように設計し、過負荷状態で機能不全にならないようにします。
たとえば、サービスでは、静的ウェブページでユーザーのリクエストに応答し、処理に費用がかかる動的な動作を一時的に無効にできます。または、サービスでは、読み取り専用オペレーションを許可してデータの更新を一時的に無効にすることもできます。
サービスが低下した場合は、エラー状態を修正するために、オペレーターに通知する必要があります。
トラフィックの急増を回避、軽減する
クライアント間でリクエストを同期しないでください。同じタイミングでトラフィックを送信するクライアントの数が多すぎると、トラフィックが急増し、カスケード障害が発生する可能性があります。
スロットリング、キューイング、負荷制限や回路遮断、グレースフル デグラデーション、重要なリクエストの優先順位付けなど、トラフィック急増の軽減策をサーバー側に実装します。
クライアントでの軽減策には、クライアント側のスロットリングとジッターを伴う指数バックオフがあります。
入力をサニタイズして検証する
サービス停止やセキュリティ侵害の原因となる、誤入力、ランダム入力、不正な入力を防ぐため、API とオペレーション ツールの入力パラメータをサニタイズして検証します。たとえば、Apigee と Google Cloud Armor はインジェクション攻撃からの保護に役立ちます。
定期的にファズテストを使用する。そこでは、テストハーネスによって、ランダム入力、空の入力、または大きすぎる入力を使用して API が意図的に呼び出されます。隔離されたテスト環境でこれらのテストを実行します。
運用ツールは、変更がロールアウトされる前に構成変更を自動的に検証し、検証が失敗した場合は変更を拒否する必要があります。
機能を維持しながらフェイルセーフを実現する
問題により障害が発生した場合は、システム コンポーネントで障害が発生しても、システム全体は引き続き機能します。こうした問題としては、ソフトウェアのバグ、入力や構成の誤り、計画外のインスタンス停止、人的エラーなどが考えられます。サービス プロセスによって、制限を過度に厳格にするのではなく、制限を大幅に緩和する、または単純化する必要があるかどうかを判断できます。
次のシナリオ例と、障害への対応方法を検討します。
- 通常は、構成が不適切または空であるファイアウォール コンポーネントがフェイル オープンであり、オペレーターがエラーを修正している短時間の間、未承認のネットワーク トラフィックが通過できるようにすることをおすすめします。この動作により、フェイル クローズしてトラフィックの 100% をブロックする代わりに、サービスの可用性を維持できます。サービスは、すべてのトラフィックが通過している間、アプリケーション スタック内のより深い認証と認可チェックに依存して、機密領域を保護する必要があります。
- ただし、ユーザーデータへのアクセスを制御する権限サーバー コンポーネントがフェイル クローズして、すべてのアクセスをブロックするように設定することをおすすめします。この動作により構成が破損するとサービスが停止しますが、フェイル オープンした際に機密データが漏洩するリスクを回避できます。
どちらの場合も、オペレーターがエラー条件を修正できるように、障害によって優先度の高いアラートが生成される必要があります。サービス コンポーネントによって、ビジネスに対する極端なリスクを生じない限り、フェール オープン側でエラーが発生します。
再試行できるように API 呼び出しとオペレーション コマンドを設計する
API と運用ツールは、可能な限り呼び出しを安全に再試行できるようにする必要があります。多くのエラー条件に対する自然なアプローチは、前のアクションを再試行することですが、最初の試行が成功したかどうか不明な場合もあります。
システム アーキテクチャでは、アクションをべき等にする必要があります。オブジェクトに対して同じアクションを 2 回以上連続して実行した場合、1 回の呼び出しと同じ結果を生成する必要があります。べき等以外のアクションでは、システム状態の破損を防ぐため、より複雑なコードが必要になります。
サービスの依存関係を特定して管理する
サービス デザイナーとオーナーは、他のシステム コンポーネントへの依存関係の完全なリストを維持する必要があります。また、依存関係の障害からの復旧や完全な復旧が不可能な場合は、グレースフル デグラデーションもサービス設計に組み込む必要があります。システムで使用されるクラウド サービスと外部依存関係(サードパーティのサービス API など)への依存度を考慮し、どのシステムの依存関係にもゼロ以外の失敗率があることを認識する必要があります。
信頼性の目標を設定する場合、サービスの SLO は、すべての重要な依存関係の SLO によって数学的に制限されることに注意してください。いずれかの依存関係の最も低い SLO よりも信頼性が高くなることはありません。詳細については、サービス可用性の計算をご覧ください。
起動の依存関係
サービスは、起動時と定常動作では動作が異なります。起動の依存関係は、定常状態のランタイムの依存関係とは大きく異なる場合があります。
たとえば、起動時に、再度呼び出すことはめったにないユーザー メタデータ サービスからユーザー情報かアカウント情報を読み込むことが必要な場合があります。クラッシュまたは定期メンテナンスの後に多くのサービス レプリカが再起動すると、特にキャッシュが空で、再配置する必要がある場合に、レプリカが起動の依存関係の負荷を急激に増加する可能性があります。
負荷がかかった状態でサービスの起動をテストし、それに応じて起動の依存関係をプロビジョニングします。重要な起動の依存関係からサービスが取得するデータのコピーを保存することで、サービスレベルを正常に低下させるための設計を検討してください。この動作により、重要な依存関係が停止した際にサービスが起動不能にならずに、古い可能性があるデータでサービスが再起動できます。取得可能になった時点で、サービスは後から新しいデータを読み込み、通常の動作に戻ることができます。
新しい環境でサービスをブートストラップする際には、起動の依存関係も重要です。レイヤ間の循環依存関係なしに、階層化されたアーキテクチャでアプリケーション スタックを設計します。循環環依存関係は、単一のアプリケーションに対する増分変更をブロックしないため、許容できるかもしれません。ただし、循環依存関係では、障害でサービス スタック全体が停止した後に再起動することが困難または不可能になります。
重要な依存関係を最小化する
サービスの重要な依存関係、つまり、障害によって必然的にサービスが停止するコンポーネントの数を最小限に抑えます。依存する他のコンポーネントの障害や遅延に対するサービスの復元力を高めるには、次の設計手法と原則を考慮して、重要な依存関係を重要でない依存関係に変換することを検討してください。
- 重要な依存関係の冗長性レベルを上げます。レプリカが多いほど、コンポーネント全体が使用できなくなる可能性が低くなります。
- レスポンスをブロックする代わりに他のサービスへの非同期リクエストを使用するか、レスポンスでブロックするのではなく、パブリッシュ/サブスクライブ メッセージングを使用してレスポンスからリクエストを分離します。
- 他のサービスからのレスポンスをキャッシュに保存し、依存関係の短期的な使用不能状態から復元します。
サービスのエラーまたは低下によるサービスに依存するほかのコンポーネントへの悪影響を軽減するには、次のような設計手法と原則の例を検討してください。
- 優先順位の高いリクエスト キューを使用し、ユーザーがレスポンスを待機しているリクエストの優先度を高くします。
- レイテンシと負荷を軽減するために、キャッシュからレスポンスを配信する。
- 機能を維持しながらフェイルセーフを実現します。
- トラフィックが過負荷の状態に達した場合には、サービスレベルを適切に引き下げます。
すべての変更をロールバックできるようにする
特定のタイプの変更を元に戻せる方法が明確に定義できない場合は、ロールバックをサポートするようにサービスの設計を変更します。定期的にロールバック プロセスをテストします。API が進化しても前世代のクライアントが正しく動作するように、すべてのコンポーネントまたはマイクロサービスの API はバージョニングする必要があります。この設計原則は、API の変更を段階的にロールアウトし、必要に応じて迅速にロールバックできるようにするために不可欠です。
モバイルアプリの場合、ロールバックの実装にコストがかかる可能性があります。Firebase Remote Config は、機能のロールバックを容易にする Google Cloud サービスです。
データベース スキーマの変更をすぐにロールバックすることはできないため、複数のフェーズで実行します。各フェーズは、アプリケーションの最新バージョンと以前のバージョンで安全なスキーマの読み取りと更新を可能にするように設計します。この設計アプローチにより、最新バージョンで問題が発生した場合に安全にロールバックできます。
推奨事項
アーキテクチャ フレームワークのガイダンスを実際の環境に適用するには、次の推奨事項に従ってください。
- クライアント アプリケーションのエラー再試行ロジックで、ランダム化を使用して指数バックオフを実装します。
- 高可用性を実現する自動フェイルオーバーを備えたマルチリージョン アーキテクチャを実装します。
- 負荷分散を使用して、シャードとリージョンにユーザー リクエストを分散します。
- アプリケーションのサービスレベルが過負荷状態で適切に下がるように設計します。部分レスポンスを配信するか、機能を制限することによって完全に機能しなくなることを回避します。
- 容量計画のためのデータ駆動プロセスを確立し、負荷テストとトラフィック予測を使用して、リソースをプロビジョニングするタイミングを決定します。
- 障害復旧手順を確立し、定期的にテストします。
次のステップ
- 信頼性の高い運用プロセスとツールを作成する(このシリーズの次のドキュメント)
- アーキテクチャ フレームワークの他の柱の推奨事項を確認する。