高可用性を確保する為のデザイン パターン

本デザイン パターンのユースケース

ほとんどのワークロードで可用性を考慮した設計は重要ですが、求められる要件はワークロードによって異なるため、ワークロードのアーキテクチャを考える上で、そのワークロードがどのレベルの可用性を実現する必要があるかを考えることが重要となります。Google Cloud では可用性を高めるためのソリューションをいくつか提供しており、これらを適切に採用し、各ワークロードに適した構成を選択すること可能です。 

本節では自然災害によるリージョンやゾーン障害などを前提にした高可用性設計について、 Compute Engine などによるアプリケーション サーバと DB を組み合わせた一般的な ウェブサービスのワークロードを想定したアーキテクチャについて解説します。また、 Google Cloud の各ソリューションの網羅的な高可用性構成の解説については対象外のため、必要に応じてそれぞれのソリューションのドキュメントを参照ください。

Google Cloud において可用性を高めるには、ゾーンとリージョン、グローバル リソースの違いを意識する必要があります。各リージョンには 1 つ以上のゾーンがあり、ほとんどのリージョンには 3 つ以上のゾーンがあります。たとえば、asia-northeast1 リージョンは東京に位置し、asia-northeast1-a 、 asia-northeast1-b 、 asia-northeast1-c の 3 つのゾーンがあります。

仮想マシン インスタンスやゾーン永続ディスクなど、特定のゾーンに閉じたリソースはゾーンリソースと呼ばれます。静的外部 IP アドレスなど、同じリージョン内であれば、ゾーンをまたいで使用できるリソースは、リージョンリソースと呼ばれます。グローバル リソースは、場所を問わずすべての他のリソースで使用できるリソースです。詳細は リージョンとゾーン | Compute Engine ドキュメント など、利用しようとするサービスのページを確認してください。 

また、Compute Engine のメンテナンス イベントでは、ホスト メンテナンスと呼ばれるホストカーネルのアップグレード、ハードウェアの修理またはアップグレードのような通常 VM の再起動を伴うような作業が行われますが、インスタンス 可用性ポリシーでこのようなメンテナンス イベントが発生した場合は、デフォルトでインスタンスをライブ マイグレーションするように構成されており、このようなメンテナンス イベントにおいても VM を再起動する事なく稼働を続ける事が可能です。

参考ドキュメント : アーキテクチャ フレームワーク | 信頼性

信頼性の要件を定義する

高可用性を確保する為の構成を検討する際は、まず最初に各ワークロードにそれぞれどのレベルの可用性が求められるかを特定することが重要となります。この要件定義は、障害発生時に許容可能なビジネスへの影響の分析から着手します。この分析で主要な指標は以下の 2 つです。

  • 復旧時間目標( RTO )
    アプリケーションがオフラインである状態が許容される最大時間です。通常、この値はサービスレベル契約(SLA)の一部として定義されます。
  • 復旧時点目標( RPO )
    重大なインシデントが原因でアプリケーションからデータが失われている状態が許容される最大時間です。この指標はデータの用途によって異なります。たとえば、頻繁に変更されるユーザーデータの場合、 RPO はわずか数分になる可能性があります。対照的に、重要度が低く、変更頻度が低いデータの場合、 RPO は数時間になることもあります(この指標は、失われたデータの量や質ではなく時間の長さのみを表します)。 

通常、RTO と RPO の値が小さいほど(つまり、アプリケーションを中断状態から復旧する時間が急がれるほど)、アプリケーションの実行コストは高くなります。次のグラフは RTO / RPO に対するコストの割合を示しています。 

graph

RTO と RPO の値が小さいと構成の複雑性が増す傾向にあるため、それに伴う管理コストも同様の曲線を描きます。高可用性のアプリケーションを維持するには、地理的に離れた 2 つのデータセンターへの分散やレプリケーションなどを実施したりすることが求められるためです。また、 RTO と RPO にビジネス上求められる要件を正しく把握し、必要以上に要件を厳しくしすぎて複雑な構成を採用したり、コストを上げすぎないことが信頼性の要件を定義する上で重要なことの一つです。

参考ドキュメント : 障害復旧計画ガイド

また、信頼性の要件の一環として、自然災害等の大規模災害などによるリージョンの障害を前提とするかどうかも重要となります。例えば後述するマルチゾーン ウォーム スタンバイの構成パターンでは、リージョン内の異なるゾーンにリソースを配置する事になり、物理インフラストラクチャやハードウェア や ソフトウェアで障害が発生した場合に、短い時間でワークロードを復旧することができますが、大規模災害時にリージョンで障害が発生した際にはワークロードを復旧することができません。この場合、マルチリージョンのストレージ ロケーションにスナップショットを取得するバックアップ & リストアと組み合わせることで、リージョンで障害が発生した際にも別のリージョンでワークロードを復元し、ワークロードの稼働を継続させることが可能となります。ただし、この場合リージョンの障害に備えるための追加のコストが必要となるため、コストとビジネス上要求される信頼性要件のバランスを最適化する必要があります。

RTO や RPO 、大規模災害の考慮などの具体的な整理の方法は本稿では省略しますが、 IPA が公開している非機能要求グレードなどを参考にして整理するのも良いでしょう。

信頼性の要件を満たすことの出来るソリューション、構成パターンを選択する

可用性を高める構成パターンを決める場合は、整理した信頼性の要件に基づき以下の表を参照して検討するのが良いでしょう。また、前述の通りゾーン障害からの短時間の復旧のためにマルチゾーン ウォーム スタンバイの構成と、リージョンの障害からの復旧のためにバックアップ & リストアの構成を組合わせて構成することも可能ですが、あくまで RTO と RPO に関してはぞれぞれの定義となるため注意が必要です。

構成パターン マルチサイト マルチゾーン
ウォーム
スタンバイ
パイロット
ライト
バックアップ & リストア
RTO 即時 小( > 15 分※1 大( > 1 日※1 大( > 1 日※1
RPO データ損失無し データ損失無し※2 データ損失無し※2 ※スナップ ショットの取得頻度による
リージョン障害への対応  対応可能 対応不可 対応可能 対応可能

※1 記載の時間については構成、運用体制などにより変動する為、あくまで目安です。
※2 リージョン永続ディスクに保管されたデータのみ、書き込み前のデータについてはデータが失われます。

以下それぞれの構成パターンについて考慮点やアーキテクチャなどを解説します。 ※ 未記載の構成パターンについては今後追加予定です。 

デザイン パターン詳細

マルチゾーン ウォーム スタンバイ

マルチゾーン ウォーム スタンバイとは、Compute Engine 上に構築したワークロードなどに対し、ゾーン障害が発生した際に HA クラスタ ソフトウェアでフェイルオーバーを管理し、別ゾーンのウォーム スタンバイのインスタンスにて処理を引き継ぐ構成で、主にステートフルなデータベース サービス(MySQL 、 Postgres など)向けの HA ソリューションを構築する際など用いられる構成です。Compute Engine で利用可能な HA クラスタ ソフトウェアとしては、次のようなソリューションが使用できます。

また、Google Cloud 上でこの構成を検討する際には以下が考慮点となります。

  • マルチゾーン構成の採用
    Google Cloud において、各ゾーンは 1 つ以上のクラスタ内でホストされ、クラスタごとに、独立したソフトウェア インフラストラクチャ、電源、冷却、ネットワーク、セキュリティ インフラストラクチャがあり、コンピューティング リソースとストレージ リソースのプールが含まれています。リージョン内の異なるゾーンにウォーム スタンバイを配置する事により、この物理インフラストラクチャやハードウェア ソフトウェアで障害が発生した場合に、正常稼働している他のゾーンで処理を引き継ぐことが可能となります。また、マルチゾーン構成を採用する際は単一障害点が存在しないよう、片方のゾーン障害の際に、もう片方のゾーンで必要なリソースにアクセス出来るよう、必要に応じて冗長構成を採用する必要があります。具体的な指針としては、リージョナルリソースであれば単一ゾーンの障害の際に影響を受けることはない為、特別な考慮は不要ですが、 Compute Engine のインスタンスなど、ゾーンリソースはマルチゾーンへ冗長配置するなど、考慮が必要となります。
    参考ドキュメント : ゾーンとクラスタゾーンとリージョン、グローバル リソース
  • マネージド インスタンス グループの活用
    ステートレスなウェブサイト フロントエンドやアプリケーション サーバなどに関しては、マネージド インスタンス グループ( MIG )を採用することで、高可用性だけではなく、スケーラビリティやインスタンス テンプレートの利用による運用負荷の軽減など、様々なメリットが有るため、可能な限り採用を検討することをおすすめします。
    参考ドキュメント : マネージド インスタンス グループ
  • HA クラスタの採用と、フローティング IP の代替構成
    前述のステートレスなウェブサイトのフロントエンドやアプリケーションサーバ以外のステートフルな DB サーバなどでは、共有または仮想 IP アドレスとも呼ばれるフローティング IP アドレスと HA クラスタ ソフトウェアを組み合わせた HA クラスタと呼ばれる高可用性構成が採用されることが多いですが、Compute Engine ではこのフローティング IP アドレスを直接実装することができないため、以下のいずれかの同等の構成と HA クラスタ ソフトウェアで必要な切り替えの処理を実装する必要があります。また、この選択肢の中でも多くのケースでは内部 TCP / UDP 負荷分散を使用したフェイルオーバーの構成で実装が可能なことが多いため、まずはこちらで実装が可能か検討するのが良いでしょう。
    参考ドキュメント : フローティング IP アドレスのおすすめの方法
    • 内部 TCP / UDP 負荷分散を使用したフェイルオーバー
      稼働系、待機系のインスタンスを内部 TCP / UDP 負荷分散のバックエンドとしてマネージド インスタンス グループに配置し、ヘルスチェック機能で稼働系を識別し、内部 TCP / UDP 負荷分散 IP アドレスを仮想 IP アドレスとして使用することにより稼働系を切り替えることが可能となります。
    • ルート API 呼び出しを使用したフェイルオーバー
      フローティング IP 相当の IP として、ネットワークの有効なサブネットの外にある IP を IP 転送を有効にした VM に設定し、稼働系インスタンスをその IP への VPC ネットワーク内の静的ルートのネクストホップとして設定し、フェイルオーバーの際にはこの静的ルートのネクストホップを待機系インスタンスに再設定することで稼働系を切り替えます。
    • Cloud DNS の限定公開ゾーンなどを利用したフェイルオーバー
      この構成ではフローティング IP を利用せず、 Cloud DNS の限定公開ゾーン等に作成した A レコードを稼働系のインスタンスの IP アドレスに設定し、フェイルオーバーの際に待機系インスタンスの IP へ変更することで稼働系の切り替えを実現します。ただし、この実装においては接続元となるアプリケーション サーバの DNS キャッシュの無効化や対象の A レコードの TTL を数秒など短期間程にすることで短い時間での切り替えを実現するなどの考慮が必要となります。
      参考ドキュメント : 限定公開ゾーンの作成
  • スプリットブレインを防ぐことが可能な構成を採用する
    Google Cloud における HA クラスタ構成でも、オンプレミス環境での同構成と同様に、ゾーンのネットワーク コンポーネントの障害などで稼働系と待機系のネットワークが分断されてしまい、同一資源に複数のインスタンスがアクセスすることでデータの不整合を引き起こしてしまうスプリットブレインと呼ばれる現象が発生してしまう事があります。これを防ぐためにオンプレミス環境では、共有ディスクのロックステータスによるスプリットブレインを防止する構成を採用することが多いですが、 Google Cloud ではこの方式が採用できないため、代替構成として別のゾーンに配置したクォーラム デバイス ノードを利用した投票メカニズム等を検討する必要があります。以下は HA クラスタ ソフトウェアと代表的なスプリットブレイン防止する方法の例です。

本構成におけるアンチパターンについて以下にて補足します。

  • シングルゾーンのウォーム スタンバイ構成
    前述の通り、マルチゾーンのウォーム スタンバイ構成を採用することで、ゾーンのインフラストラクチャ等で障害が発生した場合に、正常稼働している他のゾーンで処理を引き継ぐことが可能となりますが、同一のゾーンに稼働系と待機系のインスタンスを配置してしまうと、これらの障害の際に同時に影響を受けてしまい、ワークロードを復旧する事が出来ないため、特別な要件が無い限りマルチゾーンでのウォーム スタンバイ構成を採用するのが良いでしょう。
    また、シングルゾーンの構成が必要な場合は、待機系インスタンス分の追加コストが発生するウォーム スタンバイ構成以外にも、可用性ポリシーのインスタンスの再起動動作として、インスタンスがクラッシュまたは停止した場合にインスタンスを自動的に再起動する構成にしたり、マネージド インスタンス グループを構成することで、アプリケーション ベースのヘルスチェックを使用する自動修復ポリシーを設定することも可能ですので、こちらもご検討下さい。
  • スプリットブレインに対する対策が採用されていない構成
    マルチゾーンでのウォーム スタンバイ構成を採用する場合に懸念となるのが、ネットワーク機器の障害などによるスプリットブレインの発生と、それに伴うデータの不整合の発生です。こちらに関しては、前述の各 HA クラスタ ソフトウェアのスプリットブレインを防止するための構成を採用することで回避可能となるため、可能な限りこちらを採用することを推奨します。 
  • フローティング IP の代替構成としてエイリアス IP の利用
    フローティング IP の代替構成の選択肢としてエイリアス IP の利用も可能ですが、マルチゾーンの HA のデプロイには、1 つのゾーンに障害が発生した場合に、エイリアス IP を別のゾーンのインスタンスに再割り振りするには時間がかかる可能性があるため、前述の別の方法を採用することを推奨します。

アーキテクチャの解説

マルチゾーン ウォーム スタンバイ について、様々な構成要素の組み合わせが可能となりますが、本項では以下の OS / MW を利用し、DB サーバの HA クラスタ構成を中心にした、一般的な ウェブサービスのワークロードのサンプル アーキテクチャと各構成要素の解説を行います。なお、各構成要素の解説については本構成における考慮点の解説のみを行うため、概要や詳細などはそれぞれ記載した参考リンクや他のデザイン パターンの解説を参照ください。

  • CentOS 8
  • Pacemaker + Corosync
  • MySQL 8
  • Apache + php-fpm

本構成におけるポイントとサンプル アーキテクチャ図は以下となります。

  • フロントエンドに関しては、ゾーン障害においても別のゾーンのインスタンスで処理を継続、必要に応じたオートスケーリングが可能なリージョン マネージド インスタンス グループを採用 
  • DB インスタンスに関しては、リージョン永続ディスクを利用した HA クラスタ構成を採用し、ゾーン障害の際にも処理が引き継げる構成を採用
architecture1

また、 D ~ H の部分についてはマネージド サービスである Cloud SQL を利用する事が可能です。この場合のサンプル アーキテクチャ図は以下となります。

architecture2

アルファベット記号に応じたサンプル アーキテクチャの各要素について、以下に解説を記載します。なお、ここでは本構成における特記事項のみの解説を行うため、詳細については各参照ドキュメントを参照ください。

A. 外部 HTTP(S) 負荷分散(External Load Balancing)
外部からの通信を負荷状況や障害発生状況に合わせて、適切に稼働しているバックエンド サービスに振り分けます。本構成ではバックエンド サービスとしてリージョン内の複数のゾーンにインスタンスを分散配置するリージョン マネージド インスタンス グループを採用する為、ゾーン障害発生時にも稼働が続いているインスタンスへ通信を振り分けます。また、外部 HTTP(S) 負荷分散自体もグローバル リソースの為、特別な構成を行うことなく、ゾーンやリージョンの障害の影響を受けずにサービス稼働を継続させることが可能です。
参考ドキュメント : 外部 HTTP(S) 負荷分散の概要

B. VPC ネットワーク( VPC Network )
Google Cloud では、 VPC を構成することにより、選択したリージョン内に共通のネットワークである、サブネットを構成することが可能で、このネットワークを利用し、マネージド インスタンス グループや Compute Engine のインスタンスなどを展開することが可能です。また、関連ルート、ファイアウォール ルールを含む VPC ネットワークはグローバル リソースの為、ゾーンやリージョンの障害の影響を受けることはありません。
参考ドキュメント : VPC ネットワークの概要

C. リージョン マネージド インスタンス グループ (Regional Managed Instance Group)
リージョン マネージド インスタンス グループを使用すると、アプリケーションを 1 つのゾーンに制限することや、異なるゾーンで複数のインスタンス グループを管理することなく、アプリケーションの負荷を複数のゾーンに分散できます。複数のゾーンを使用することで、ゾーン障害の発生時にも、アプリケーションは同じリージョンの別のゾーンで実行しているインスタンスからトラフィックの処理を続行する事が可能です。 また、 リージョン マネージド インスタンス グループを利用することで、ゾーン障害に対応可能な高可用性だけではなく、自動修復Updater 機能を使用したアップデートのロールアウトによる運用負荷の軽減、負荷に応じた自動スケーリングの構成など様々なメリットがあります。
参考ドキュメント : リージョン MIG を使用したインスタンスの分散

D. 内部 TCP / UDP 負荷分散 (Internal Load Balancing)
本構成ではフローティング IP の代替構成として、内部 TCP / UDP 負荷分散を使用したHA クラスタ構成を採用し、フロントエンド インスタンスから DB インスタンスへの接続は内部 TCP / UDP 負荷分散の IP を利用します。また、内部 TCP / UDP 負荷分散では 3306 / TCP ポートへのヘルスチェックを構成し、 MySQL のインスタンスが稼働しているインスタンスへ通信を振り分けます。
参考ドキュメント : 内部 TCP / UDP 負荷分散の概要

E. 非マネージド インスタンス グループ (Unmanaged Instance Group) 
内部 TCP / UDP 負荷分散の振り分け先として、マネージド インスタンス グループの設定が必要となりますが、前述のリージョン マネージド インスタンス グループの利用とは違い、 1 インスタンスのみを含むマネージド インスタンス グループをゾーンごとに 1 セットずつ構成することで単一のノードへ通信を振り分ける構成とし、HA クラスタの稼働系、待機系のインスタンスを構成します。
また、非マネージド インスタンス グループではマネージド インスタンス グループと違い、共通のインスタンス テンプレートを共有しない固有の VM として、通常の Compute Engine のインスタンス として構成した後に非マネージド インスタンス グループへ参加する手順となります。
なお、 MySQL のデータ保存に関しては後述のリージョン永続ディスクへ保存することでリージョン内のレプリケーションを行いますが、それぞれのインスタンスのブートディスクに関してはゾーン永続ディスクの利用で問題ありません。
参考ドキュメント : 非マネージド インスタンス グループ非マネージド インスタンス グループの作成Google Compute Engine で MySQL をセットアップする方法 

F. Compute Engine - Quorum Server
このノードは Corosync のスプリットブレイン防止構成におけるクォーラム デバイスとして corosync-qnetd デーモンが実行され、HA クラスタの稼働系、待機系 と通信を行うことでネットワーク パーティションの検知とクォーラム ルール(多数決)による稼働インスタンスの決定を構成します。
参考ドキュメント : DRBD を使用した高可用性 MySQL 5.6 クラスタを Compute Engine にデプロイする

G. リージョン永続ディスク(Regional Persistent Disk)
リージョン永続ディスクを利用すると、同じリージョン内の 2 つのゾーン間でのデータの耐久性の高いストレージとレプリケーションを構成することが可能となり、両方のレプリカが使用可能な場合、両方のレプリカで書き込み内容が永続化されたときに、書き込みの確認応答が VM に返される為、同期の遅延などは通常発生しません。 また、ゾーン障害の際には、接続コマンドに --force-attach フラグを使用することにより、別のゾーンのインスタンスに接続することが可能です。 なお、リージョン永続ディスクはゾーン永続ディスクのパフォーマンス性能とは別定義となり、特にリージョン永続ディスクはインスタンスあたりの書き込みスループットが低くなるため、利用の際には注意が必要です。
参考ドキュメント : リージョン永続ディスクリージョン永続ディスクのフェイルオーバーリージョン PD を使用した高可用性オプション

H. 永続ディスクのスナップショット(Persistent Disk Snapshot)
スケジュールによる定期的なスナップショットの作成により、ゾーン永続ディスクまたはリージョン永続ディスクからデータを定期的にバックアップすることが可能です。スナップショットのストレージ ロケーションとして、マルチリージョンや永続ディスク が配置されたリージョンとは別のリージョンへのバックアップの保管が可能であり、これによりリージョンの障害の際にもワークロードを別のリージョンで復元することが可能となります。加えて、HA クラスタ構成だけでは対応できないデータの破壊など、論理障害に対する備えにもなるため、可能な限りスケジュールによるスナップショットの定期作成を構成することを推奨します。
また、スナップショットでバックアップされるデータは永続ディスクに書き込みが完了しているデータのみとなるため、永続ディスクのスナップショットに関するベスト プラクティスを参考にアプリケーションのデータや、ディスク バッファをフラッシュしてファイル システムを同期した上でスナップショットの作成を行う事も検討ください。
参考ドキュメント : 永続ディスクのスナップショットの作成

I. Cloud SQL 高可用性構成
前述の通り HA クラスタ構成には複数のコンポーネントの組み合わせによる複雑な構成が必要となりますが、Cloud SQL HA 構成を採用することにより以下のようなメリットがあります。

  • HA クラスタリング構成や、マルチリージョン バックアップについても事前定義されたマネージド サービスの機能を利用可能であり、複雑な構成を管理する必要がない。
  • データベース プロビジョニング、ストレージ容量の管理など、構築、運用作業が自動化されている。
  • バグの修正やセキュリティ パッチの反映などが自動反映される。 

高可用性構成の Cloud SQL インスタンスはリージョン内のプライマリ ゾーンとセカンダリ ゾーンに配置され、各ゾーンの永続ディスクへの同期レプリケーションにより、プライマリ インスタンスへの書き込みのすべてがスタンバイ インスタンスにも反映されます。インスタンスまたはゾーンで障害が発生した場合、この構成によりダウンタイムが短縮され、クライアント アプリケーションで引き続きデータを使用できます。
参考ドキュメント : 高可用性構成の概要

J. プライベート サービス アクセス (Private Service Access)
VPC ネットワークでホストされている内部 IP アドレスでサービスを提供でき、プライベート サービス アクセスを使用すると、これらの内部 IP アドレスにアクセスできます。これは、VPC ネットワーク内の VM インスタンスから内部 IP アドレスで Cloud SQL のインスタンスに接続する際に利用可能です。
参考ドキュメント : プライベート サービス アクセスプライベート サービス アクセスの有効化プライベート サービス アクセスの構成

K. Cloud SQL インスタンスのバックアップ
Cloud SQL インスタンスの自動バックアップを構成することにより、定期的なバックアップを取得し、問題が発生しているインスタンスをバックアップから復元することができます。バックアップの保存先としてデフォルトでは冗長性を確保するためにバックアップ データを 2 つのリージョンに保存します。これにより、リージョンの障害が発生した場合でも別のリージョンでインスタンスを復元することが可能です。
また、バイナリログを有効にすることで、ポイントインタイム リカバリ(PITR)を使用することも可能です。ただし、ポイントインタイム リカバリではバイナリログが使用され、ログが作成される為、保存容量を使用します。バイナリログは、関連する自動バックアップによって自動的に削除されます。バイナリログによる予期しないストレージの問題を回避するには、バイナリログの使用時に合わせてストレージの自動増量を有効にすることをおすすめします。
参考ドキュメント : バックアップの概要ポイントインタイム リカバリの構成方法 

そのほかの選択肢

  • リージョン永続ディスク以外のデータ同期の方法
    本サンプル アーキテクチャではゾーン障害に備えた別ゾーンへのデータ同期はリージョン永続ディスクを利用した構成としていますが、 リージョン永続ディスク以外にも以下のようなデータ同期の方法を採用することが出来ますので、必要に応じて検討ください。
    • DRBD の利用
      Distributed Replicated Block Device(DRBD)を利用する事で、ネットワークを介したブロック デバイス レベルでのデータ同期を構成することが可能となり、Pacemakerと組み合わせる事で、レプリケーション構成の管理も含めた HA クラスタリングを構成することが可能です。
      参考ドキュメント : DRBD を使用した高可用性 MySQL 5.6 クラスタを Compute Engine にデプロイする
    • DB のレプリケーション機能の利用
      SQL Server AlwaysOn や MySQL Group Replication 等、利用する DB のレプリケーション機能を利用しデータ同期を構成することも可能です。この場合、HA クラスタリング構成も合わせて MW で実装、管理する事が可能なことが多く、単一ソリューションでのシンプルな構成を実現できるのもメリットとなります。
      参考ドキュメント : SQL Server AlwaysOn 可用性グループの構成GCEとMySQLで実現する高可用性システム 

サンプル コンフィグ

前述のサンプル アーキテクチャ図のサンプル コンフィグについて、手順やコマンド例などを以下にて解説します。

複数のノードにまたがって作業を行うため、以下ターミナルの表記で作業先の環境を補足します。 

  • @cloudshell $ :Cloud Shell での作業
  • @database1 $ :database1 インスタンスでの作業
  • @database2 $ :database2 インスタンスでの作業
  • @qdevice $ :qdevice インスタンスでの作業
  • @mysql-client $ :MySQL 動作確認用の VM インスタンス

始める前に

MySQL 用インスタンス とリージョン永続ディスクの作成

  • 環境変数を設定したファイルを作成します
@cloudshell $ cat < ~/.varsrc
# Cluster instance names
DATABASE1_INSTANCE_NAME=database1
DATABASE2_INSTANCE_NAME=database2
QUORUM_INSTANCE_NAME=qdevice
CLIENT_INSTANCE_NAME=mysql-client
# Cluster IP addresses
DATABASE1_INSTANCE_IP="10.146.0.110"
DATABASE2_INSTANCE_IP="10.146.0.111"
QUORUM_INSTANCE_IP="10.146.0.112"
ILB_IP="10.146.0.100"
# Cluster zones and region
DATABASE1_INSTANCE_ZONE="asia-northeast1-a"
DATABASE2_INSTANCE_ZONE="asia-northeast1-b"
QUORUM_INSTANCE_ZONE="asia-northeast1-c"
CLIENT_INSTANCE_ZONE="asia-northeast1-c"
CLUSTER_REGION="asia-northeast1"
PD_DISK_NAME="mysql-disk" EOF
  • 現在のセッションで環境変数を読み込みます
@cloudshell $ source ~/.varsrc
 
  • クラスタ インスタンス用のサービス アカウントを設定します
@cloudshell $ gcloud iam service-accounts create mysql-instance \
--display-name "mysql-instance"

 

  • 作成したサービス アカウントへ必要な権限を付与します
@cloudshell $ gcloud projects add-iam-policy-binding ${DEVSHELL_PROJECT_ID} \
--member=serviceAccount:mysql-instance@${DEVSHELL_PROJECT_ID}.iam.gserviceaccount.com \
--role=roles/compute.instanceAdmin.v1
@cloudshell $ gcloud projects add-iam-policy-binding ${DEVSHELL_PROJECT_ID} \
--member=serviceAccount:mysql-instance@${DEVSHELL_PROJECT_ID}.iam.gserviceaccount.com \
--role=roles/compute.viewer
@cloudshell $ gcloud projects add-iam-policy-binding ${DEVSHELL_PROJECT_ID} \
--member=serviceAccount:mysql-instance@${DEVSHELL_PROJECT_ID}.iam.gserviceaccount.com \
--role=roles/iam.serviceAccountUser
@cloudshell $ gcloud projects add-iam-policy-binding ${DEVSHELL_PROJECT_ID} \
--member=serviceAccount:mysql-instance@${DEVSHELL_PROJECT_ID}.iam.gserviceaccount.com \
--role=roles/compute.networkAdmin
@cloudshell $ gcloud projects add-iam-policy-binding ${DEVSHELL_PROJECT_ID} \
--member=serviceAccount:mysql-instance@${DEVSHELL_PROJECT_ID}.iam.gserviceaccount.com \
--role=roles/compute.loadBalancerAdmin
@cloudshell $ gcloud projects add-iam-policy-binding ${DEVSHELL_PROJECT_ID} \
--member=serviceAccount:mysql-instance@${DEVSHELL_PROJECT_ID}.iam.gserviceaccount.com \
--role=roles/compute.securityAdmin

 

  • 3 つの各クラスタノード用に内部 IP アドレスを予約します
@cloudshell $ gcloud compute addresses create ${DATABASE1_INSTANCE_NAME} ${DATABASE2_INSTANCE_NAME} ${QUORUM_INSTANCE_NAME} \
--region=${CLUSTER_REGION} \
--addresses "${DATABASE1_INSTANCE_IP},${DATABASE2_INSTANCE_IP},${QUORUM_INSTANCE_IP}" \
--subnet=default 

 

  • MySQL のデータ保存用のリージョン永続ディスクを作成します 
@cloudshell $ gcloud compute disks create mysql-disk \
--size 100 \
--type pd-ssd \
--region ${CLUSTER_REGION} \
--replica-zones ${DATABASE1_INSTANCE_ZONE},${DATABASE2_INSTANCE_ZONE} 

 

  • asia-northeast1-a ゾーンに database1 という名前の VM インスタンスを作成します
@cloudshell $ gcloud compute instances create ${DATABASE1_INSTANCE_NAME} \
--zone=${DATABASE1_INSTANCE_ZONE} \
--machine-type=n1-standard-2 \
--network-tier=PREMIUM \
--maintenance-policy=MIGRATE \
--image-family=centos-8 \
--image-project=centos-cloud \
--boot-disk-size=50GB \
--boot-disk-type=pd-standard \
--boot-disk-device-name=${DATABASE1_INSTANCE_NAME} \
--private-network-ip=${DATABASE1_INSTANCE_NAME} \
--tags=mysql --service-account=mysql-instance@${DEVSHELL_PROJECT_ID}.iam.gserviceaccount.com \
--scopes="https://www.googleapis.com/auth/compute,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/service.management.readonly" \
--metadata="DATABASE1_INSTANCE_IP=${DATABASE1_INSTANCE_IP},DATABASE2_INSTANCE_IP=${DATABASE2_INSTANCE_IP},DATABASE1_INSTANCE_NAME=${DATABASE1_INSTANCE_NAME},DATABASE2_INSTANCE_NAME=${DATABASE2_INSTANCE_NAME},QUORUM_INSTANCE_NAME=${QUORUM_INSTANCE_NAME},DATABASE1_INSTANCE_ZONE=${DATABASE1_INSTANCE_ZONE},DATABASE2_INSTANCE_ZONE=${DATABASE2_INSTANCE_ZONE}" 

 

  • asia-northeast1-b ゾーンに database2 という名前の VM インスタンスを作成します
@cloudshell $ gcloud compute instances create ${DATABASE2_INSTANCE_NAME} \
--zone=${DATABASE2_INSTANCE_ZONE} \
--machine-type=n1-standard-2 \
--network-tier=PREMIUM \
--maintenance-policy=MIGRATE \
--image-family=centos-8 \
--image-project=centos-cloud \
--boot-disk-size=50GB \
--boot-disk-type=pd-standard \
--boot-disk-device-name=${DATABASE2_INSTANCE_NAME} \
--private-network-ip=${DATABASE2_INSTANCE_NAME} \
--tags=mysql \
--service-account=mysql-instance@${DEVSHELL_PROJECT_ID}.iam.gserviceaccount.com \
--scopes="https://www.googleapis.com/auth/compute,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/service.management.readonly" \
--metadata="DATABASE1_INSTANCE_IP=${DATABASE1_INSTANCE_IP},DATABASE2_INSTANCE_IP=${DATABASE2_INSTANCE_IP},DATABASE1_INSTANCE_NAME=${DATABASE1_INSTANCE_NAME},DATABASE2_INSTANCE_NAME=${DATABASE2_INSTANCE_NAME},QUORUM_INSTANCE_NAME=${QUORUM_INSTANCE_NAME},DATABASE1_INSTANCE_ZONE=${DATABASE1_INSTANCE_ZONE},DATABASE2_INSTANCE_ZONE=${DATABASE2_INSTANCE_ZONE}"
 
  • asia-northeast1-c ゾーン内に Pacemaker で使用するクォーラム ノードを作成します
@cloudshell $ gcloud compute instances create ${QUORUM_INSTANCE_NAME} \
--zone=${QUORUM_INSTANCE_ZONE} \
--machine-type=n1-standard-1 \
--network-tier=PREMIUM \
--maintenance-policy=MIGRATE \
--image-family=centos-8 \
--image-project=centos-cloud \
--boot-disk-size=20GB \
--boot-disk-type=pd-standard \
--boot-disk-device-name=${QUORUM_INSTANCE_NAME} \
--private-network-ip=${QUORUM_INSTANCE_NAME} 

 database1 のセットアップと、MySQLのインストール

  • リージョン永続ディスクを database1 に接続します
@cloudshell $ gcloud compute instances attach-disk ${DATABASE1_INSTANCE_NAME} \
--disk mysql-disk --disk-scope regional --zone asia-northeast1-a 

 

  • database1 へ SSH で接続し、環境変数のセットを行います
@database1 $ sudo bash -c cat < ~/.varsrc
DATABASE1_INSTANCE_IP=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE1_INSTANCE_IP" -H "Metadata-Flavor: Google")
DATABASE2_INSTANCE_IP=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE2_INSTANCE_IP" -H "Metadata-Flavor: Google")
DATABASE1_INSTANCE_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE1_INSTANCE_NAME" -H "Metadata-Flavor: Google")
DATABASE2_INSTANCE_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE2_INSTANCE_NAME" -H "Metadata-Flavor: Google")
DATABASE2_INSTANCE_ZONE=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE2_INSTANCE_ZONE" -H "Metadata-Flavor: Google")
DATABASE1_INSTANCE_ZONE=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE1_INSTANCE_ZONE" -H "Metadata-Flavor: Google")
QUORUM_INSTANCE_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/QUORUM_INSTANCE_NAME" -H "Metadata-Flavor: Google")
EOF

@database1 $ source ~/.varsrc 

 

@database1 $ sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/sdb

@database1 $ sudo mount /dev/sdb /srv/

@database1 $ sudo mkdir /srv/mysql/

@database1 $ sudo sed -i -e 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config

@database1 $ sudo setenforce 0 

 

  • MySQL 8.0 のインストールと初期セットアップを行います
@database1 $ sudo dnf install -y @mysql:8.0

@database1 $ sudo systemctl disable mysqld

@database1 $ sudo chown -R mysql:mysql /srv/mysql

@database1 $ sudo sed -i -e 's/datadir=\/var\/lib\/mysql/datadir=\/srv\/mysql\//g' /etc/my.cnf.d/mysql-server.cnf

@database1 $ sudo systemctl start mysqld

@database1 $ sudo mysql_secure_installation
> root password の設定などを行う

@database1 $ cat < ~/create.sql
CREATE USER 'test'@'%' IDENTIFIED BY 'P@ssw0rd'; GRANT ALL ON *.* TO 'test'@'%';
EOF

@database1 $ mysql -u root -p < ~/create.sql 

 

  • 作成したユーザで SQL が実行可能か確認します
@database1 $ mysql -u test -p"P@ssw0rd" -e 'SHOW VARIABLES WHERE Variable_name = "hostname";'

 

以下のように稼働ノードが表示されることを確認します

+------------------------+-----------------+
| Variable_name | Value         |
+------------------------+-----------------+
| hostname         | database1 |
+-----------------------+-------------------+ 

 

  • MySQL の停止とファイルシステム、リージョン永続ディスクの切り離しを行います
@database1 $ sudo systemctl stop mysqld

@database1 $ sudo umount /srv/

@database1 $ gcloud compute instances detach-disk ${DATABASE1_INSTANCE_NAME} \
--disk mysql-disk --disk-scope regional --zone asia-northeast1-a 

 

  • Pacemaker のインストールと起動時の有効化を行います
@database1 $ sudo dnf --enablerepo=HighAvailability -y install pacemaker corosync pcs corosync-qdevice

@database1 $ sudo systemctl enable pacemaker

@database1 $ sudo systemctl enable corosync

@database1 $ sudo systemctl enable pcsd 

 

  • 認証用のクラスタ ユーザー パスワードを設定します
@database1 $ sudo bash -c 'echo "h@clust3r" | passwd --stdin hacluster'

 

  • corosync-keygen スクリプトを実行します。これにより、128 ビットのクラスタ認証キーが生成され /etc/corosync/authkey に書き込まれます
@database1 $ sudo corosync-keygen -l

 

  • authkey を database2 インスタンスにコピーします。パスフレーズの入力については、そのまま Enter キーを押します
@database1 $ sudo chmod 444 /etc/corosync/authkey

@database1 $ gcloud compute scp /etc/corosync/authkey ${DATABASE2_INSTANCE_NAME}:/tmp/authkey \
--zone=${DATABASE2_INSTANCE_ZONE} --internal-ip

@database1 $ sudo chmod 400 /etc/corosync/authkey 

 

  • Corosync クラスタ構成ファイルを作成します
@database1 $ sudo bash -c "cat < /etc/corosync/corosync.conf
totem{
  version: 2
  cluster_name: mysql_cluster
  transport: udpu
  interface {
    ringnumber: 0
    Bindnetaddr: ${DATABASE1_INSTANCE_IP}
    broadcast: yes
    mcastport: 5405
  }
}
quorum {
  provider: corosync_votequorum
  two_node: 1
}
nodelist {
  node {
    ring0_addr: ${DATABASE1_INSTANCE_NAME}
    name: ${DATABASE1_INSTANCE_NAME}
    nodeid: 1
  }
  node {
    ring0_addr: ${DATABASE2_INSTANCE_NAME}
    name: ${DATABASE2_INSTANCE_NAME}
    nodeid: 2
  }
}
logging {
  to_logfile: yes
  logfile: /var/log/corosync.log
  timestamp: on
}
EOF"

 

このセットアップでの重要な設定は次のとおりです。

  • transport : ユニキャスト モード(udpu)を指定します。
  • Bindnetaddr : Corosync のバインド先のネットワーク アドレスを指定します。
  • nodelist : クラスタ内の特定のノードへの通信方法を定義します。この例では、database1 ノードと database2 ノードへの通信方法を定義しています。
  • quorum / two_node : デフォルトでは、2 ノードクラスタ内のノードはどちらもクォーラムを獲得しません。これをオーバーライドするには、quorum セクション内の two_node に値「1」を指定します。 

この設定により、クラスタを構成して、今後 3 つ目のノードをクォーラム デバイスとして追加するときに備えることができます。

  • Pacemaker を認識するように Corosync を構成します
@database1 $ sudo mkdir -p /etc/corosync/service.d

@database1 $ sudo bash -c 'cat < /etc/corosync/service.d/pcmk
service {
  name: pacemaker
  ver: 1
}
EOF' 

 

  • Corosync サービスと Pacemaker サービスを再起動します
@database1 $ sudo systemctl start pcsd

@database1 $ sudo systemctl start corosync

@database1 $ sudo systemctl start pacemaker 

 

  • 永続ディスクの Resource Agent をインストールします
@database1 $ sudo pip2 install --upgrade google-api-python-client

@database1 $ sudo dnf install -y git

@database1 $ git clone https://github.com/ClusterLabs/resource-agents.git

@database1 $ sudo cp ./resource-agents/heartbeat/gcp-pd-move.in /usr/lib/ocf/resource.d/heartbeat/gcp-pd-move

@database1 $ sudo sed -i -e 's/@PYTHON@/\/usr\/bin\/python2/g' /usr/lib/ocf/resource.d/heartbeat/gcp-pd-move

@database1 $ sudo chmod 755 /usr/lib/ocf/resource.d/heartbeat/gcp-pd-move

 

  • database2 へ SSH で接続し、環境変数のセットを行います
@database2 $ sudo bash -c cat < ~/.varsrc
DATABASE1_INSTANCE_IP=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE1_INSTANCE_IP" -H "Metadata-Flavor: Google")
DATABASE2_INSTANCE_IP=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE2_INSTANCE_IP" -H "Metadata-Flavor: Google")
DATABASE1_INSTANCE_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE1_INSTANCE_NAME" -H "Metadata-Flavor: Google")
DATABASE2_INSTANCE_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE2_INSTANCE_NAME" -H "Metadata-Flavor: Google")
DATABASE2_INSTANCE_ZONE=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE2_INSTANCE_ZONE" -H "Metadata-Flavor: Google")
DATABASE1_INSTANCE_ZONE=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/DATABASE1_INSTANCE_ZONE" -H "Metadata-Flavor: Google")
QUORUM_INSTANCE_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/QUORUM_INSTANCE_NAME" -H "Metadata-Flavor: Google")
EOF

@database2 $ source ~/.varsrc 

 

  • SE Linux の無効化などを行います
@database2 $ sudo sed -i -e 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config

@database2 $ sudo setenforce 0 

 

  •  MySQL をインストールします
@database2 $ sudo dnf install -y @mysql:8.0

@database2 $ sudo systemctl disable mysqld

@database2 $ sudo sed -i -e 's/datadir=\/var\/lib\/mysql/datadir=\/srv\/mysql\//g' /etc/my.cnf.d/mysql-server.cnf 

 

  • Pacemaker のインストールと起動時の有効化を行います
@database2 $ sudo dnf --enablerepo=HighAvailability -y install pacemaker corosync pcs corosync-qdevice

@database2 $ sudo systemctl enable pacemaker

@database2 $ sudo systemctl enable corosync

@database2 $ sudo systemctl enable pcsd 

 

  • 前にコピーした Corosync authkey ファイルを /etc/corosync/ に移動します
@database2 $ sudo mv /tmp/authkey /etc/corosync/

@database2 $ sudo chown root: /etc/corosync/authkey

@database2 $ sudo chmod 400 /etc/corosync/authkey 

 

  • 認証用のクラスタ ユーザー パスワードを設定します
@database2 $ sudo bash -c 'echo "h@clust3r" | passwd --stdin hacluster'

 

  • Corosync 構成ファイルを作成します
@database2 $ sudo bash -c "cat < /etc/corosync/corosync.conf
totem {
  version: 2
  cluster_name: mysql_cluster
  transport: udpu
  interface {
    ringnumber: 0
    Bindnetaddr: ${DATABASE1_INSTANCE_IP}
    broadcast: yes
    mcastport: 5405
  }
}
quorum {
  provider: corosync_votequorum
  two_node: 1
}
nodelist {
  node {
    ring0_addr: ${DATABASE1_INSTANCE_NAME}
    name: ${DATABASE1_INSTANCE_NAME}
    nodeid: 1
  }
  node {
    ring0_addr: ${DATABASE2_INSTANCE_NAME}
    name: ${DATABASE2_INSTANCE_NAME}
    nodeid: 2
  }
}
logging {
  to_logfile: yes
  logfile: /var/log/corosync.log
  timestamp: on
}
EOF" 

 

  • Pacemaker を認識するように Corosync を構成します
@database2 $ sudo mkdir -p /etc/corosync/service.d

@database2 $ sudo bash -c 'cat < /etc/corosync/service.d/pcmk
service {
  name: pacemaker
  ver: 1
}
EOF' 

 

  • Corosync サービスと Pacemaker サービスを再起動します
@database2 $ sudo systemctl start pcsd

@database2 $ sudo systemctl start corosync

@database2 $ sudo systemctl start pacemaker 

 

  •  永続ディスクの Resource Agent をインストールします
@database2 $ sudo pip2 install --upgrade google-api-python-client

@database2 $ sudo dnf install -y git

@database2 $ git clone https://github.com/ClusterLabs/resource-agents.git

@database2 $ sudo cp ./resource-agents/heartbeat/gcp-pd-move.in /usr/lib/ocf/resource.d/heartbeat/gcp-pd-move

@database2 $ sudo sed -i -e 's/@PYTHON@/\/usr\/bin\/python2/g' /usr/lib/ocf/resource.d/heartbeat/gcp-pd-move

@database2 $ sudo chmod 755 /usr/lib/ocf/resource.d/heartbeat/gcp-pd-move

 

 クラスタの起動

  • クラスタノードに対して認証を行います
@database2 $ sudo pcs host auth ${DATABASE1_INSTANCE_NAME} ${DATABASE2_INSTANCE_NAME} -u hacluster -p h@clust3r

 

  • クラスタの起動とステータス確認を行います
@database2 $ sudo pcs cluster start --all
@database2 $ sudo pcs status 

 

以下のように各ノードが Online と表示されることを確認します 

Node List:
* Online: [ database1 database2 ] 

 

クラスタ リソースを管理するように Pacemaker を構成する

  • SSH を使用して database1 インスタンスに接続し、環境変数を読み込みます
@database1 $ source ~/.varsrc

 

  • Pacemaker の pcs ユーティリティを使用して、いくつかの変更をファイルに行い、後で Cluster Information Base(CIB)に一括反映可能にします
@database1 $ sudo pcs cluster cib clust_cfg

 

  • STONITH は無効化します
@database1 $ sudo pcs -f clust_cfg property set stonith-enabled=false

 

  • クォーラムの構成として、投票数が過半数を下回った場合にクラスター パーティション内の全リソースが停止される構成にします 
@database1 $ sudo pcs -f clust_cfg property set no-quorum-policy=stop

 

  • ノードが障害からの復元後に Pacemaker でリソースが戻されないように構成します
@database1 $ sudo pcs -f clust_cfg resource defaults resource-stickiness=INFINITY

 

  • リージョン永続ディスクの接続の為のリソースを作成します
    なお、監視操作の無効化を設定することで、 Google Cloud の API の障害の際などに不必要なフェイルオーバーを防ぐ事が可能です
@database1 $ sudo pcs -f clust_cfg resource create regional_persistent_disk ocf:heartbeat:gcp-pd-move \
disk_name="mysql-disk" \
disk_scope="regional" \
op monitor enabled=false

 

  • /srv をマウントするファイル システム リソースを作成し、リージョン永続ディスクが接続されたインスタンスに配置され、リージョン永続ディスクの接続後にリソースを起動するようにクラスタを構成します
@database1 $ sudo pcs -f clust_cfg resource create mystore_FS Filesystem \
device="/dev/sdb" \
directory="/srv" \
fstype="ext4"

@database1 $ sudo pcs -f clust_cfg constraint colocation add mystore_FS with regional_persistent_disk INFINITY

@database1 $ sudo pcs -f clust_cfg constraint order regional_persistent_disk then mystore_FS

 

  • MySQL サービスを作成し、ファイル システムがマウントされたインスタンスに配置され、ファイル システムのマウント後にリソースを起動するようにクラスタを構成します
@database1 $ sudo pcs -f clust_cfg resource create mysql_service ocf:heartbeat:mysql \
binary="/usr/libexec/mysqld" \
config="/etc/my.cnf" \
datadir="/srv/mysql/" \
pid="/run/mysqld/mysqld.pid" \
socket="/var/lib/mysql/mysql.sock" \
additional_parameters="--bind-address=0.0.0.0" \
op start timeout=60s \
op stop timeout=60s \
op monitor interval=20s timeout=30s

@database1 $ sudo pcs -f clust_cfg constraint colocation add mysql_service with mystore_FS INFINITY

@database1 $ sudo pcs -f clust_cfg constraint order mystore_FS then mysql_service

 

  • 変更をクラスタに commit し、リソースのステータスを確認します
@database1 $ sudo pcs cluster cib-push clust_cfg

@database1 $ sudo pcs status 

 

以下のようにリソースが Started となっていれば正常に稼働しています 

Full List of Resources:
* regional_persistent_disk (ocf::heartbeat:gcp-pd-move): Started database1
* mystore_FS (ocf::heartbeat:Filesystem): Started database1
* mysql_service (ocf::heartbeat:mysql): Started database1

クォーラム デバイスを構成する

  • SSH を使用して qdevice インスタンスに接続します

 

  • pcs と corosync-qnetd をインストール、起動します
@qdevice $ sudo dnf --enablerepo=HighAvailability -y install corosync-qnetd pcs

@qdevice $ sudo systemctl enable pcsd

@qdevice $ sudo systemctl start pcsd 

 

  • 認証用のクラスタ ユーザー パスワードを設定します
@qdevice $ sudo bash -c 'echo "h@clust3r" | passwd --stdin hacluster'

 

  • クォーラム デバイスの起動と、ステータスを確認します
@qdevice $ sudo pcs qdevice setup model net --enable --start

@qdevice $ sudo pcs qdevice status net --full 

 

以下のように起動を確認します

QNetd address: *:5403
TLS: Supported (client certificate required)
Connected clients: 0
Connected clusters: 0
Maximum send/receive size: 32768/32768 bytes 

 

  • database1 へ SSH で接続し、環境変数を読み込みます
@database1 $ source ~/.varsrc
  • クラスタのクォーラム デバイスノードを認証します
@database1 $ sudo pcs host auth ${QUORUM_INSTANCE_NAME} -u hacluster -p h@clust3r
  • クォーラム デバイスをクラスタに追加します。ffsplit アルゴリズムを使用します。このアルゴリズムにより、50% 以上の得票に基づいてアクティブ ノードが決定されます
@database1 $ sudo pcs quorum device add model net host=${QUORUM_INSTANCE_NAME} algorithm=ffsplit

 

  • クォーラム設定を corosync.conf に追加します ※ quorum { } の項目が変更されています
@database1 $ sudo bash -c "cat < /etc/corosync/corosync.conf
totem {
  version: 2
  cluster_name: mysql_cluster
  transport: udpu
  interface {
    ringnumber: 0
    Bindnetaddr: ${DATABASE1_INSTANCE_IP}
    broadcast: yes
    mcastport: 5405
  }
}
quorum {
  provider: corosync_votequorum
  device {
    votes: 1
    model: net
    net {
      tls: on
      host: ${QUORUM_INSTANCE_NAME}
      algorithm: ffsplit
    }
  }
}
nodelist {
  node {
    ring0_addr: ${DATABASE1_INSTANCE_NAME}
    name: ${DATABASE1_INSTANCE_NAME}
    nodeid: 1
  }
  node {
    ring0_addr: ${DATABASE2_INSTANCE_NAME}
    name: ${DATABASE2_INSTANCE_NAME}
    nodeid: 2
  }
}
logging {
  to_logfile: yes
  logfile: /var/log/corosync.log
  timestamp: on
}
EOF"

 

  • Corosync クォーラム デバイス デーモンを起動し、システム起動時に有効化します
@database1 $ sudo systemctl restart corosync

@database1 $ sudo systemctl enable corosync-qdevice

@database1 $ sudo systemctl restart corosync-qdevice

 

  • database2 へ SSH で接続し、環境変数を読み込みます
@database2 $ source ~/.varsrc

 

  • クォーラム設定を corosync.conf に追加します ※ quorum { } の項目が変更されています
@database2 $ sudo bash -c "cat < /etc/corosync/corosync.conf
totem {
  version: 2
  cluster_name: mysql_cluster
  transport: udpu
  interface {
    ringnumber: 0
    Bindnetaddr: ${DATABASE1_INSTANCE_IP}
    broadcast: yes
    mcastport: 5405
  }
}
quorum {
  provider: corosync_votequorum
  device {
    votes: 1
    model: net
    net {
      tls: on
      host: ${QUORUM_INSTANCE_NAME}
      algorithm: ffsplit
    }
  }
}
nodelist {
  node {
    ring0_addr: ${DATABASE1_INSTANCE_NAME}
    name: ${DATABASE1_INSTANCE_NAME}
    nodeid: 1
  }
  node {
    ring0_addr: ${DATABASE2_INSTANCE_NAME}
    name: ${DATABASE2_INSTANCE_NAME}
    nodeid: 2
  }
}
logging {
  to_logfile: yes
  logfile: /var/log/corosync.log
  timestamp: on
}
EOF"

 

  • Corosync クォーラム デバイス デーモンを起動し、システム起動時に有効化します
@database2 $ sudo systemctl restart corosync

@database2 $ sudo systemctl enable corosync-qdevice

@database2 $ sudo systemctl restart corosync-qdevice

 

 

クラスタのステータス確認

  • database1 へ SSH で接続し、環境変数を読み込みます
@database1 $ source ~/.varsrc

 

  • クラスタのステータスを確認します
@database1 $ sudo pcs status

 

  • クォーラムのステータスを表示します
@database1 $ sudo pcs quorum status

 

クラスタ IP として内部ロードバランサを構成する

  • Cloud Shell を開き、環境変数を読み込みます
@cloudshell $ source ~/.varsrc

 

  • database1 用の非マネージド インスタンス グループを作成します
@cloudshell $ gcloud compute instance-groups unmanaged create ${DATABASE1_INSTANCE_NAME}-instance-group \
--zone=${DATABASE1_INSTANCE_ZONE} \
--description="${DATABASE1_INSTANCE_NAME} unmanaged instance group"

 

  • database1 用の非マネージド インスタンス グループに database1 を追加します
@cloudshell $ gcloud compute instance-groups unmanaged add-instances ${DATABASE1_INSTANCE_NAME}-instance-group \
--zone=${DATABASE1_INSTANCE_ZONE} \
--instances=${DATABASE1_INSTANCE_NAME} 

 

  • database2 用の非マネージド インスタンス グループを作成します
@cloudshell $ gcloud compute instance-groups unmanaged create ${DATABASE2_INSTANCE_NAME}-instance-group \
--zone=${DATABASE2_INSTANCE_ZONE} \
--description="${DATABASE2_INSTANCE_NAME} unmanaged instance group" 

 

  • 3306 / TCP ( MySQL ) のヘルスチェックを作成します
@cloudshell $ gcloud compute health-checks create tcp mysql-backend-healthcheck \
--port 3306

 

  • バックエンド サービスを作成します
@cloudshell $ gcloud compute backend-services create mysql-ilb \
--load-balancing-scheme internal \
--region ${CLUSTER_REGION} \
--health-checks mysql-backend-healthcheck \
--protocol tcp

 

  • バックエンド サービスに、2 つのインスタンス グループを追加します
@cloudshell $ gcloud compute backend-services add-backend mysql-ilb \
--instance-group ${DATABASE1_INSTANCE_NAME}-instance-group \
--instance-group-zone ${DATABASE1_INSTANCE_ZONE} \
--region ${CLUSTER_REGION}

@cloudshell $ gcloud compute backend-services add-backend mysql-ilb \
--instance-group ${DATABASE2_INSTANCE_NAME}-instance-group \
--instance-group-zone ${DATABASE2_INSTANCE_ZONE} \
--region ${CLUSTER_REGION} 

 

  • ロードバランサの転送ルールを作成します
@cloudshell $ gcloud compute forwarding-rules create mysql-ilb-forwarding-rule \
--load-balancing-scheme internal \
--ports 3306 \
--network default \
--subnet default \
--region ${CLUSTER_REGION} \
--address ${ILB_IP} \
--backend-service mysql-ilb

 

  • 内部ロードバランサのヘルスチェックを許可するファイア ウォール ルールを作成します
@cloudshell $ gcloud compute firewall-rules create allow-ilb-healthcheck \
--direction=INGRESS --network=default \
--action=ALLOW --rules=tcp:3306 \
--source-ranges=130.211.0.0/22,35.191.0.0/16 --target-tags=mysql

 

(Optional)MySQL への接続テスト

  • 接続テスト用インスタンスを作成します
@cloudshell $ gcloud compute instances create ${CLIENT_INSTANCE_NAME} \
--image-family=centos-8 \
--image-project=centos-cloud \
--tags=mysql-client \
--zone=${CLIENT_INSTANCE_ZONE} \
--boot-disk-size=20GB \
--metadata="ILB_IP=${ILB_IP}" 

 

  • mysql-client へログインし以下にて接続をテストします
@mysql-client $ ILB_IP="10.146.0.100"

@mysql-client $ sudo dnf install -y mysql

@mysql-client $ mysql -u test -p"P@ssw0rd" -h ${ILB_IP} -e 'SHOW VARIABLES WHERE Variable_name = "hostname";' 

 

(Optional)Cloud SQL HA 構成の作成

アーキテクチャの解説で記載したとおり、 MySQL の部分を HA 構成の Cloud SQL にて行うことも可能です。その場合のサンプル コンフィグについて解説します

  • 以下のコマンドにて Service Networking API の有効可と、Private Service Access を構成します
@cloudshell $ gcloud services enable servicenetworking.googleapis.com

@cloudshell $ gcloud compute addresses create google-managed-services-default \ --global \ --purpose=VPC_PEERING \ --prefix-length=16 \ --network=default

@cloudshell $ gcloud services vpc-peerings connect \ --service=servicenetworking.googleapis.com \ --ranges=google-managed-services-default \ --network=default 

 

  • HA 構成の Cloud SQL インスタンスの作成
    ※ インスタンスの作成と同時にバックアップの設定が行われ、 UTC 午後 6 時(日本時間 午前 3 時)に日次バックアップ取得が開始されます
@cloudshell $ gcloud beta sql instances create cloud-sql-ha \
--database-version=MYSQL_8_0 \
--tier=db-n1-standard-2 \
--zone=${DATABASE1_INSTANCE_ZONE} \
--network=default \
--storage-type=SSD \
--storage-size=100 \
--availability-type=REGIONAL \
--backup-start-time=18:00 \
--no-assign-ip \
--enable-bin-log \
--root-password=password123

 

作成が成功すると、以下のように VPC からアクセス可能なインスタンスのPRIVATE_ADDRESS が表示されます

NAME DATABASE_VERSION LOCATION TIER PRIMARY_ADDRESS PRIVATE_ADDRESS STATUS cloud-sql-ha MYSQL_8_0 asia-northeast1-a db-n1-standard-2 - 10.95.0.2 RUNNABLE

 

リージョン マネージド インスタンス グループの作成

  • Cloud Shell を起動し、マネージド インスタンス グループの起動スクリプトを作成します
    ※ 作業簡略化の為、DB のパスワードをハードコーディングしておりますが、本番構成の場合は Cloud KMS 等の利用を検討ください。
@cloudshell $ cat << END > ./startup.sh
sudo dnf -y install httpd php php-mbstring php-xml php-xmlrpc php-gd php-pdo php-mysqlnd
cat << EOF > /var/www/html/index.php
<!php
\\\$mysqli = new mysqli("10.146.0.100", "test", "P@ssw0rd");
if (\\\$mysqli->connect_error) {
   echo \\\$mysqli->connect_error;
    exit();
} else {
\\\$mysqli->set_charset("utf8");
}
\\\$sql = "SHOW VARIABLES WHERE Variable_name = \"hostname\"";
if (\\\$result = \\\$mysqli->query(\\\$sql)) {
  while (\\\$row = \\\$result->fetch_assoc()) {
    echo "MySQL service is running on " . \\\$row["Value"] . " . "<br>";
  }
  \\\$result->close();
}
\\\$mysqli->close();
?>
EOF
sudo setenforce 0
sudo systemctl start php-fpm
sudo systemctl start httpd
END

 

  • インスタンス テンプレートを作成します
@cloudshell $ gcloud compute instance-templates create frontend-http \
--machine-type=n1-standard-2 \
--network-tier=PREMIUM \
--maintenance-policy=MIGRATE \
--image-family=centos-8 \
--image-project=centos-cloud \
--boot-disk-size=50GB \
--boot-disk-type=pd-standard \
--tags=allow-health-check \
--metadata-from-file startup-script=./startup.sh

 

  • リージョン マネージド インスタンス グループを作成します
@cloudshell $ gcloud compute instance-groups managed create frontend-http-rmig \
--template frontend-http --base-instance-name frontend-http \
--size 8 --region asia-northeast1 --zones asia-northeast1-a,asia-northeast1-b

 

  • リージョン マネージド インスタンス グループ の Named Port を定義します
@cloudshell $ gcloud compute instance-groups managed set-named-ports frontend-http-rmig\
--named-ports=http:80 \
--region=asia-northeast1 

 

外部 HTTP(S) 負荷分散の作成

  • ヘルスチェック用のファイアウォール ルールを定義します 
@cloudshell $ gcloud compute firewall-rules create fw-allow-health-check \
--network=default \
--action=allow \
--direction=ingress \
--source-ranges=130.211.0.0/22,35.191.0.0/16 \
--target-tags=allow-health-check \
--rules=tcp

 

  • 外部 IP アドレスを予約します
@cloudshell $ gcloud compute addresses create lb-ipv4-1 \
--ip-version=IPV4 \
--global 

 

また、以下コマンドで表示される予約された 外部 IP アドレスを記録します

@cloudshell $ gcloud compute addresses describe lb-ipv4-1 --global

 

  • ヘルスチェックを作成します
@cloudshell $ gcloud compute health-checks create http http-basic-check \
--port 80 

 

  • バックエンド サービスを作成します
@cloudshell $ gcloud compute backend-services create web-backend-service \
--protocol=HTTP \
--port-name=http \
--health-checks=http-basic-check \
--global 

 

  • リージョン マネージド インスタンス グループ をバックエンド サービスに追加します
@cloudshell $ gcloud compute backend-services add-backend web-backend-service \
--instance-group=frontend-http-rmig \
--instance-group-region=asia-northeast1 \
--global 

 

  • デフォルトのバックエンド サービスに受信リクエストをルーティングする URL マップを作成します
@cloudshell $ gcloud compute url-maps create web-map-http \
--default-service web-backend-service 

 

  • URL マップにリクエストをルーティングするターゲット HTTP プロキシを作成します
@cloudshell $ gcloud compute target-http-proxies create http-lb-proxy \
--url-map web-map-http 

 

  • 受信リクエストをプロキシにルーティングするグローバル転送ルールを作成します
@cloudshell $ gcloud compute forwarding-rules create http-content-rule \
--address=lb-ipv4-1\
--global \
--target-http-proxy=http-lb-proxy \
--ports=80

 

動作確認

  • ブラウザを開き、先程確認した外部 IP アドレスへ http でアクセスすると以下のように表示され、database1 または database2 の部分は MySQL インスタンスへクエリを行った結果として稼働しているインスタンスのホスト名が表示されています
MySQL service is running on database1

 

  • 次のテストを実施する事で、デプロイ済みクラスタの HA 機能をテストします
    • database1 インスタンスをシャットダウンして、マスター データベースが database2 インスタンスにフェイルオーバーするかどうかをテストします
    • database1 インスタンスを起動して、database1 がクラスタに正常に再参加できるかどうかを確認します
    • database2 インスタンスをシャットダウンして、マスター データベースが database1 インスタンスにフェイルオーバーするかどうかをテストします
    • database2 インスタンスを起動して、database2 がクラスタに正常に再参加できるかどうか、database1 インスタンスが引き続きマスターのロールを保持するかどうかを確認します
    • database1 をファイアウォール ルールで孤立させ、ネットワーク パーティション状態を作りだし、スプリット ブレインの問題をシミュレートします
  • Cloud Shell を開き、環境変数を読み込みます
@cloudshell $ source ~/.varsrc

 

  • クラスタのステータスを確認します
    ※ database1 でサービスが稼働していることを確認
@cloudshell $ gcloud compute ssh ${DATABASE1_INSTANCE_NAME} \
--zone=${DATABASE1_INSTANCE_ZONE} \
--command="sudo pcs status" 

 

  • database1 インスタンスを停止します
@cloudshell $ gcloud compute instances stop ${DATABASE1_INSTANCE_NAME} \
--zone=${DATABASE1_INSTANCE_ZONE}

 

  • クラスタのステータスを確認します
    ※ database2 にサービスがフェイルオーバーしていることを確認
@cloudshell $ gcloud compute ssh ${DATABASE2_INSTANCE_NAME} \
--zone=${DATABASE2_INSTANCE_ZONE} \
--command="sudo pcs status" 

 

  • database1 インスタンスを起動します
@cloudshell $ gcloud compute instances start ${DATABASE1_INSTANCE_NAME} \
--zone=${DATABASE1_INSTANCE_ZONE} 

 

  • クラスタのステータスを確認します
    ※ database1 が Online に復旧していることを確認
@cloudshell $ gcloud compute ssh ${DATABASE2_INSTANCE_NAME} \
--zone=${DATABASE2_INSTANCE_ZONE} \
--command="sudo pcs status" 

 

  • database2 インスタンスを停止します
@cloudshell $ gcloud compute instances stop ${DATABASE2_INSTANCE_NAME} \
--zone=${DATABASE2_INSTANCE_ZONE} 

 

  • クラスタのステータスを確認します
    ※ database1 にサービスがフェイルオーバーしていることを確認
@cloudshell $ gcloud compute ssh ${DATABASE1_INSTANCE_NAME} \
--zone=${DATABASE1_INSTANCE_ZONE} \
--command="sudo pcs status" 

 

  • database2 インスタンスを起動します
@cloudshell $ gcloud compute instances start ${DATABASE2_INSTANCE_NAME} \
--zone=${DATABASE2_INSTANCE_ZONE} 

 

  • クラスタのステータスを確認します
    ※ database2 が Online に復旧していることを確認
@cloudshell $ gcloud compute ssh ${DATABASE1_INSTANCE_NAME} \
--zone=${DATABASE1_INSTANCE_ZONE} \
--command="sudo pcs status" 

 

  • 以下のファイアウォール ルールを作成し、 database1 をネットワーク パーティション状態にします
@cloudshell $ gcloud compute firewall-rules create cluster-network-partitioning \
--description="disable any communications from database1" \
--action=DENY \
--rules=all \
--source-ranges=${DATABASE1_INSTANCE_IP} \
--priority=800 

 

  • 数分後、クラスタのステータスを確認します
    ※ database1 が OFFLINE となり、サービスが database2 にフェイルオーバーされていることを確認
  @cloudshell $ gcloud compute ssh ${DATABASE2_INSTANCE_NAME} \
--zone=${DATABASE2_INSTANCE_ZONE} \
--command="sudo pcs status"

 

  • 続いて database1 のクラスタのステータスを確認します
    ※ database1 は Online ですが、クォーラムの投票により、サービス停止状態になっていることを確認 ( no-quorum-policy=stop 構成時の挙動 )
@cloudshell $ gcloud compute ssh ${DATABASE1_INSTANCE_NAME} \
--zone=${DATABASE1_INSTANCE_ZONE} \
--command="sudo pcs status"

 

  • 続いて database1 のクォーラムのステータスを確認します
    ※ Qdevice の Vote も含め Total votes が 2 となっている事を確認
@cloudshell $ gcloud compute ssh ${DATABASE2_INSTANCE_NAME} \
--zone=${DATABASE2_INSTANCE_ZONE} \
--command="sudo pcs quorum status" 

 

  • 最後にファイアーウォール ルールを削除し、ネットワーク パーティション状態を解除します
@cloudshell $ gcloud compute firewall-rules delete cluster-network-partitioning

 

  • クラスターのステータスを確認します
    ※ database1 が復帰してもフェイルオーバーは発生しておらず、 database1 が Online に戻っていることを確認
@cloudshell $ gcloud compute ssh ${DATABASE1_INSTANCE_NAME} \
--zone=${DATABASE1_INSTANCE_ZONE} \
--command="sudo pcs status" 

 

(Optional)スナップショットの作成

前述の通りスナップショットの作成を組み合わせることにより、リージョンの障害や論理障害に備えることが可能となります。必要に応じて以下のサンプル コマンドを参考にスナップショットの作成を構成下さい
※ 以下の手順については永続ディスクへのデータ同期については十分考慮されていない可能性がある為、必要に応じて永続ディスクのスナップショットに関するベスト プラクティスを参照し、要件に応じた実装方法を検討下さい

  • Cloud Shell を開き、環境変数を読み込みます
@cloudshell $ source ~/.varsrc

 

  • スナップショットのスケジュールを作成します、以下のコマンド例ではリテンション期間は 7 日間で、日次バックアップが UTC 午後 6 時(日本時間 午前 3 時)に作成開始されます
@cloudshell $ gcloud compute resource-policies create snapshot-schedule mysql-daily-schedule \
--region=asia-northeast1 \
--start-time=18:00 \
--daily-schedule \
--max-retention-days=7 

 

  • 作成したスナップショット スケジュールを database1 と database2 の ブートディスクとクラスタの共有ディスクにスケジュールを割り当てます
@cloudshell $ gcloud compute disks add-resource-policies ${PD_DISK_NAME} \
--resource-policies mysql-daily-schedule \
--region ${CLUSTER_REGION}

@cloudshell $ gcloud compute disks add-resource-policies ${DATABASE1_INSTANCE_NAME} \
--resource-policies mysql-daily-schedule \
--zone ${DATABASE1_INSTANCE_ZONE}

@cloudshell $ gcloud compute disks add-resource-policies ${DATABASE2_INSTANCE_NAME} \
--resource-policies mysql-daily-schedule \
--zone ${DATABASE2_INSTANCE_ZONE} 

クリーンアップ

注意: プロジェクトを削除すると、次のような影響があります。

プロジェクト内のすべてのものが削除されます。このチュートリアルで既存のプロジェクトを使用した場合、それを削除すると、そのプロジェクトで行った他の作業もすべて削除されます。

カスタム プロジェクト ID が失われます。このプロジェクトを作成したときに、将来使用するカスタム プロジェクト ID を作成した可能性があります。そのプロジェクト ID を使用した URL(たとえば、appspot.com)を保持するには、プロジェクト全体ではなくプロジェクト内の選択したリソースだけを削除します。 

 

  • プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします

 

  • ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します 

利点

この構成の主な利点は以下です。

  • 本構成を採用することで、同一リージョン内で、可用性を高め、かつ運用も多くの範囲で自動化できます。多くの場合、インスタンスの自動リカバリー、 メンテナンスに対するライブマイグレーション、レプリケーションなどの運用の機能や監視の機能も提供されます。
  • Compute Engine のインスタンスの SLA は >= 99.5% と定義されていますが、本構成を採用すると、 Instances in Multiple Zones に該当するため >= 99.99 % の SLA が適用されます。ただし、注意点として SLA は稼働を保証するものではない点、サービスにより異なる点、常に更新される点を考慮して、設計段階で要求仕様に合わせて確認してください。
  • フェイルオーバーの条件や、前後の処理など、きめ細やかな設定が HA クラスタリング ソフトウェアにて実装可能です。 

注意事項

  • 前述の通り、マルチゾーン ウォーム スタンバイではリージョンの障害が発生した場合にはワークロードを復旧させる事が出来ません。この場合 マルチリージョン ロケーション や 別のリージョンの Cloud Storage へのスナップショットの作成による Backup & Restore にて、リージョンの障害が発生した際にワークロードを復旧させることが可能です。
  • Cloud SQL を利用する際には、以下に注意する必要があります。
    • Cloud SQL では利用できない機能があるため、利用したい機能が利用可能か事前確認するのが良いでしょう。
      参考ドキュメント : Cloud SQL と標準 MySQL の機能の違いCloud SQL と標準 PostgreSQL の機能の違いCloud SQL で使用できない SQL Server の機能
    • Cloud SQL インスタンスでは、バグの修正、セキュリティ侵害の防止、アップグレードの実施のため更新が必要です。更新を適用すると、Cloud SQL でインスタンスが再起動され、サービスが中断される可能性があります。メンテナンス中、HA プライマリ インスタンスがスタンバイ インスタンスにフェイルオーバーすることはありません。ビジネスに対する影響を回避するため Cloud SQL メンテナンスウィンドウを設定し、メンテナンスの時間を事前に設定しましょう。また、バックアップなども業務要件に応じて設定すると良いでしょう。
      参考ドキュメント : Cloud SQL インスタンスでのメンテナンスの概要

関係するデザインパターン