Red Hat Enterprise LinuxのHigh Availability Add-OnでActive/Standby構成のクラスターを組んでみた

2021.06.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

クラスターはロマンの塊

こんにちは、のんピ です。

皆さんはクラスターに興奮したことはありますか? 私はあります。

クラスターを構成することで、障害発生の絶体絶命のピンチもギリギリ耐え凌ぐことができる。そんなロマンを感じずにはいられません。

ただクラスターを構成するためのソフトの設定は一癖も二癖もあったりします。
AWSではそんなクラスターソフトが既にインストール済みのAMIが、以下の通り提供されています。

お客様は、Amazon EC2 のスケール、パフォーマンス、および伸縮自在性を、Red Hat Enterprise Linux (RHEL) with High availability (HA) と組み合わせて、ミッションクリティカルなワークロード向けに、信頼性と可用性の高いコンピューティングクラスターを簡単に構築できます。RHEL with HA AMI は、ソフトウェアパッケージで事前設定されており、お客様がインスタンスの構築と保守に使用できる Red Hat の高可用性アドオンリポジトリにアクセスできます。RHEL with HA は、Amazon EC2 コンソールおよびマーケットプレイスから直接起動できます。

Red Hat Enterprise Linux with High availability for Amazon EC2 が利用可能に

今回は、このRed Hat Enterprise Linux(以下RHEL)のHigh Availability Add-Onが含まれているAMIを使って、Active/Standby構成のクラスターを組んでみようと思います。

いきなりまとめ

  • High Availability Add-On(以下HA Add-On)は、Red Hatが提供している、クラスターを構成するのに必要なサービスがまとまったアドオン製品
  • HA Add-On単体では、ノード間のボリューム同期はできない。ノード間でボリュームの同期が必要な場合は、Resilient Storage Add-Onなど、別の製品が必要
  • HA Add-Oのインスタンス料金は、通常のRHELのインスタンス料金より高い

High Availability Add-On ってなに??

High Availability Add-On(HA Add-On)とは、Red Hatが提供している、クラスターを構成するのに必要なサービスがまとまったアドオン製品です。

具体的には以下のようなサービスが含まれています。

サービス 役割
pacemaker クラスター内のリソース(IPアドレスや、アプリケーションなど)の起動 / 停止や、
障害検出、フェールオーバーなどを行うクラスターの核となるサービスです。
corosync    クラスター内のノード間通信を管理し、ノード間で死活監視を行うサービスです。
pcs (pcsd)    クラスターの設定管理ツールです。クラスターの作成 / 起動 / 停止を行うサービスです。
pcsがCLIで、pcsdはWeb UIの管理ツールになります。
fence agent   リソースやノード障害時に、クラスターから問題のあるノードを切り離す処理を実行するサービスです。

また、以下公式ドキュメントの通り、HA Add-Onだけでなく、他のAdd-Onを使うことで、様々な要件のクラスターを構成することができます。

以下のコンポーネントで、High Availability Add-On を補完できます。

  • Red Hat GFS2 (Global File System 2) - Resilient Storage Add-On に同梱され、High Availability Add-On で使用するクラスターファイルシステムを提供します。GFS2 により、ストレージがローカルで各クラスターノードに接続されているかのように、ブロックレベルにおいて、複数ノードでストレージを共有できるようになります。GFS2 クラスターファイルシステムを使用する場合は、クラスターインフラストラクチャーが必要になります。

  • LVM ロッキングデーモン (lvmlockd): Resilient Storage Add-On に同梱され、クラスターストレージのボリューム管理を提供します。lvmlockd に対応するには、クラスターインフラストラクチャーも必要になります。

  • Load Balancer Add-On - レイヤー 4 (TCP) およびレイヤー 7 (HTTP および HTTPS) サービスで高可用性負荷分散とフェイルオーバーを提供するルーティングソフトウェアです。Load Balancer Add-On は、負荷アルゴリズムを使用して、クライアント要求を実サーバーに分散する冗長な仮想ルーターのクラスターで実行し、まとまって仮想サーバーとして機能します。Load Balancer Add-On は、Pacemaker と併用する必要はありません。

1. High Availability Add-On の概要 - 1.1. High Availability Add-On コンポーネント

追加の課金は発生する??

オンデマンド料金に含まれる形で、追加の課金が発生します。

EC2の料金表で、オペレーティングシステムのプルダウンメニューを開くと、RHELと、Red Hat Enterprise Linux HAがあることが確認できます。

試しに2つのOSのm5、c5のインスタンスファミリーのオンデマンド料金(東京リージョン)を見比べてみました。

  • m5 の比較
インスタンスタイプ RHELのオンデマンドインスタンスの
時間単価
Red Hat Enterprise Linux HAの
オンデマンドインスタンスの時間単価
料金比較  
m5.large 0.184 USD 0.219 USD 119.02%
m5.xlarge 0.308 USD 0.343 USD 111.36%
m5.2xlarge 0.626 USD 0.661 USD 105.59%
m5.4xlarge 1.122 USD 1.157 USD 103.12%
m5.8xlarge 2.114 USD 2.149 USD 101.66%
m5.12xlarge 3.106 USD 3.141 USD 101.13%
m5.16xlarge 4.098 USD 4.133 USD 100.85%
m5.24xlarge 6.082 USD 6.117 USD 100.58%
m5.metal 6.082 USD 6.117 USD 100.58%
  • c5 の比較
インスタンスタイプ RHELのオンデマンドインスタンスの
時間単価
Red Hat Enterprise Linux HAの
オンデマンドインスタンスの時間単価
料金比較  
c5.large 0.167 USD 0.202 USD 120.96%
c5.xlarge 0.274 USD 0.309 USD 112.77%
c5.2xlarge 0.558 USD 0.593 USD 106.27%
c5.4xlarge 0.986 USD 1.021 USD 103.55%
c5.9xlarge 2.056 USD 2.091 USD 101.70%
c5.12xlarge 2.698 USD 2.733 USD 101.30%
c5.18xlarge 3.982 USD 4.017 USD 100.88%
c5.24xlarge 5.266 USD 5.301 USD 100.66%
c5.metal 5.266 USD 5.301 USD 100.66%

参考: Amazon EC2 オンデマンド料金

どちらのインスタンスファミリーも、インスタンスサイズが大きくなればなるほど、Red Hat Enterprise Linux HAのオンデマンド料金はRHELのオンデマンド料金に近似していくことが分かります。

クラスター構成を組む必要がある場合、ユースケースにもよりますが、処理性能などの関係で大きめのインスタンスサイズを選択することが多いと思います。大きめのインスタンスサイズを選択したとしても、素のRHELからそこまで大きな追加課金が派生しないのは嬉しいポイントですね。

やってみた

検証環境

今回検証を行う環境は以下の通りです。

3つEC2インスタンスを用意したのはスプリッドブレイン対策です。 HA Add-Onでは、クォーラムデバイスによるスプリッドブレイン対策も可能ですが、今回は3ノード構成でクラスターを組んでみます。

クォーラムデバイスについてはこちらのRed Hatの公式ドキュメントをご確認ください。

AWS CDKでデプロイ

毎度のごとく、AWS CDKでデプロイします。ディレクトリの構成は以下の通りです。

> tree
.
├── .gitignore
├── .npmignore
├── README.md
├── bin
│   └── app.ts
├── cdk.context.json
├── cdk.json
├── jest.config.js
├── lib
│   └── app-stack.ts
├── package-lock.json
├── package.json
├── src
│   └── ec2
│       └── userDataRHEL8.sh
├── test
│   └── app.test.ts
└── tsconfig.json

5 directories, 13 files

メインで動かすのは./lib/app-stack.tsです。ここで全てのリソースを作成しています。

RHEL with HAのAMIの名前はRHEL_HA-8.4.0_HVM-20210504-x86_64-2-Hourly2-GP2のようなフォーマットになっています。そのためバージョンが変わっても良いように、正規表現でRHEL_HA-8.*_HVM-*と表記しました。
また、Red Hatが提供しているAMIの所有者IDは309956199498なので、併せて指定しています。

実際のコードは以下の通りです。

./lib/app-stack.ts

import * as cdk from "@aws-cdk/core";
import * as ec2 from "@aws-cdk/aws-ec2";
import * as iam from "@aws-cdk/aws-iam";
import * as elbv2 from "@aws-cdk/aws-elasticloadbalancingv2";
import * as fs from "fs";

export class AppStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    // Create SSM IAM role
    const ssmIamRole = new iam.Role(this, "SsmIamRole", {
      assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName(
          "AmazonSSMManagedInstanceCore"
        ),
      ],
    });

    // Create IAM policy for Lambda to operate EC2 instances.
    const ssmIamPolicy = new iam.Policy(this, "SsmIamPolicy", {
      statements: [
        new iam.PolicyStatement({
          effect: iam.Effect.ALLOW,
          actions: ["iam:PassRole"],
          resources: [ssmIamRole.roleArn],
        }),
        new iam.PolicyStatement({
          effect: iam.Effect.ALLOW,
          actions: ["ec2:StartInstances", "ec2:StopInstances"],
          resources: [
            `arn:aws:ec2:${new cdk.ScopedAws(this).region}:${
              new cdk.ScopedAws(this).accountId
            }:instance/*`,
          ],
        }),
        new iam.PolicyStatement({
          effect: iam.Effect.ALLOW,
          actions: ["ec2:DescribeInstances", "ec2:DescribeInstanceStatus"],
          resources: ["*"],
        }),
      ],
    });

    // Attach an IAM policy to the IAM role for Lambda to operate EC2 instances.
    ssmIamRole.attachInlinePolicy(ssmIamPolicy);

    // Create VPC
    const vpc = new ec2.Vpc(this, "Vpc", {
      cidr: "10.0.0.0/16",
      enableDnsHostnames: true,
      enableDnsSupport: true,
      natGateways: 3,
      maxAzs: 3,
      subnetConfiguration: [
        { name: "Public", subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24 },
        { name: "Private", subnetType: ec2.SubnetType.PRIVATE, cidrMask: 24 },
      ],
    });

    // Security Group for ALB
    const albSg = new ec2.SecurityGroup(this, "AlbSg", {
      allowAllOutbound: true,
      vpc: vpc,
    });
    albSg.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(80),
      "Allow inbound HTTP"
    );

    // Security Group for HA node
    const haSg = new ec2.SecurityGroup(this, "HaSg", {
      allowAllOutbound: true,
      vpc: vpc,
    });
    haSg.addIngressRule(
      haSg,
      ec2.Port.allTraffic(),
      "Allow HA nodes to communicate with each other"
    );
    haSg.addIngressRule(albSg, ec2.Port.tcp(80), "Allow web access from alb");

    // Create ALB
    const alb = new elbv2.ApplicationLoadBalancer(this, "Alb", {
      vpc: vpc,
      vpcSubnets: vpc.selectSubnets({ subnetGroupName: "Public" }),
      internetFacing: true,
      securityGroup: albSg,
    });

    // Create ALB Target group
    const targetGroup = new elbv2.ApplicationTargetGroup(this, "TargetGroup", {
      vpc: vpc,
      port: 80,
      protocol: elbv2.ApplicationProtocol.HTTP,
      targetType: elbv2.TargetType.INSTANCE,
      healthCheck: {
        path: "/phpinfo.php",
        healthyHttpCodes: "200",
        healthyThresholdCount: 2,
        unhealthyThresholdCount: 2,
      },
    });

    // Create ALB listener
    const listener = alb.addListener("Listener", {
      port: 80,
      defaultTargetGroups: [targetGroup],
    });

    // User data for RHEL 8
    const userDataParameter = fs.readFileSync(
      "./src/ec2/userDataRHEL8.sh",
      "utf8"
    );
    const userDataRhel8 = ec2.UserData.forLinux({
      shebang: "#!/bin/bash -xe",
    });
    userDataRhel8.addCommands(userDataParameter);

    // Create EC2 instance
    // Red Hat Enterprise Linux 8 with High Availability
    vpc
      .selectSubnets({ subnetGroupName: "Private" })
      .subnets.forEach((subnet, index) => {
        const ec2Instance = new ec2.Instance(this, `RHEL8HA${index}`, {
          machineImage: ec2.MachineImage.lookup({
            name: "RHEL_HA-8.*_HVM-*",
            owners: ["309956199498"],
          }),
          instanceType: new ec2.InstanceType("t3.micro"),
          vpc: vpc,
          securityGroup: haSg,
          keyName: this.node.tryGetContext("key-pair"),
          role: ssmIamRole,
          vpcSubnets: vpc.selectSubnets({
            subnetGroupName: "Private",
            availabilityZones: [vpc.availabilityZones[index]],
          }),
          userData: userDataRhel8,
        });
        targetGroup.addTarget(
          new elbv2.InstanceTarget(ec2Instance.instanceId, 80)
        );
      });
  }
}

User Dataは別ファイルに書いたものを読み込ませて、EC2インスタンス起動時に実行させています。
実行させている内容は以下の通りです。

  • EC2 インスタンスのコンソールログにUser Dataの内容を出力を送信するように設定
  • SSM Agentのインストール
    • RHELではデフォルトでインストールされていないので、SSMを使用するのであれば必須です。
  • 必要なパッケージのインストール
    • AWS上でフェンシング(クラスターから問題のあるノードを切り離す処理)を行うため、fence-agents-awsが必須です。
  • Apacheを動作させるために各種権限設定を変更
  • phpinfo.phpの作成
  • Apacheの拡張ステータス表示の有効化
  • ApacheがIPv4でのみ LISTEN するように設定
  • pcsd.serviceの起動、自動起動の有効化
  • AWS CLI v2のインストールと、シンボリックリンクの作成
    • fence-agents-awsの裏で AWS CLIが動作するようなのでインストールします。
    • AWS CLIのインストール先はデフォルトだと、/usr/local/bin/awsですが、fence-agents-aws/usr/bin/awsしか見ないようなので、シンボリックリンクを作成します。

実際のコードは以下の通りです。

./src/ec2/userDataRHEL8.sh

# Redirect /var/log/user-data.log and /dev/console
exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1

# Install and start SSM Agent
dnf install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent

# Install the necessary packages.
dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
dnf install -y http://rpms.remirepo.net/enterprise/remi-release-8.rpm
dnf module enable -y php:remi-8.0
dnf install -y httpd php wget unzip
dnf install -y fence-agents-aws

# Update packages.
dnf update -y

# Add ec2-user to the apache group.
usermod -a -G apache ec2-user

# Change the group ownership of /var/www and its contents to the apache group.
chown -R ec2-user:apache /var/www

# Change the directory permissions for /var/www and its subdirectories to set write permission for the group, and set the group ID for future subdirectories.
chmod 2775 /var/www
find /var/www -type d -exec chmod 2775 {} \;

# Repeatedly change the file permissions for /var/www and its subdirectories to add group write permissions.
find /var/www -type f -exec chmod 0664 {} \;

# Add phpinfo.php
echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php

# Enable extended status display 
tee /etc/httpd/conf.d/status.conf <<"EOF" >/dev/null
<Location /server-status>
SetHandler server-status
Order deny,allow
Deny from all
Allow from 127.0.0.1
Allow from ::1
</Location>
EOF

# Fixed to LISTEN only for IPv4.
sed -i -e "s/^Listen 80/Listen 0.0.0.0:80/" /etc/httpd/conf/httpd.conf

# Start up pcsd.service, the configuration tool for Pacemaker and Corosync.
systemctl start pcsd.service
systemctl enable pcsd.service

# Create a user for the cluster
echo Y-PfN8zQb@7xc46@3yX4 | passwd --stdin hacluster

# Install AWS CLI v2 and create a symbolic link to /usr/bin/aws
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
./aws/install
ln -s /usr/local/bin/aws /usr/bin/aws

cdk deploy後に確認をしてみると、EC2インスタンスが3台起動していることが確認できます。

High Availability Add-On の設定

クラスターの作成

それでは、OSにログインしてクラスターの設定をしていきます。

設定をするにあたって、こちらのRed Hatの公式ドキュメントを参考しながら行いました。

まず、クラスターの操作をするためのサービスであるpcsd.serviceが起動しているか確認します。
確認をしてみると以下の通り、サービスが起動していますね。

sh-4.4$ systemctl status pcsd.service
● pcsd.service - PCS GUI and remote configuration interface
   Loaded: loaded (/usr/lib/systemd/system/pcsd.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2021-06-08 07:02:04 UTC; 1min 13s ago
     Docs: man:pcsd(8)
           man:pcs(8)
 Main PID: 38195 (pcsd)
    Tasks: 1 (limit: 4625)
   Memory: 24.0M
   CGroup: /system.slice/pcsd.service
           └─38195 /usr/libexec/platform-python -Es /usr/sbin/pcsd[

次に、クラスターを構成するノードに対して、User Dataで作成したクラスター用ユーザー(hacluster)で認証をします。

認証をする際は、以下のようなフォーマットのコマンドを実行します。

pcs host auth  hostname1 hostname2 hostname3

このhostnameは、各ノードのプライベートIPv4 DNSを指定しました。

実際の操作ログは以下の通りです。全てのノードでAuthorizedと表示されているので、正しく認証されていますね。

sh-4.4$ sudo pcs host auth ip-10-0-3-58.ec2.internal ip-10-0-4-38.ec2.internal ip-10-0-5-8.ec2.internal
Username: hacluster
Password:
ip-10-0-3-58.ec2.internal: Authorized
ip-10-0-5-8.ec2.internal: Authorized
ip-10-0-4-38.ec2.internal: Authorized
sh-4.4$

次に、クラスターを作成します。

クラスターを作成する際は、以下のようなフォーマットのコマンドを実行します。

pcs cluster setup cluster-name hostname1 hostname2 hostname3

実際の操作ログは以下の通りです。corosyncpacemakerの認証情報が各ノードに渡され、corosyncの設定ファイルも各ノードに配布されていることが確認できます。

sh-4.4$ sudo pcs cluster setup web-cluster --start ip-10-0-3-58.ec2.internal ip-10-0-4-38.ec2.internal ip-10-0-5-8.ec2.internal
No addresses specified for host 'ip-10-0-3-58.ec2.internal', using 'ip-10-0-3-58.ec2.internal'
No addresses specified for host 'ip-10-0-4-38.ec2.internal', using 'ip-10-0-4-38.ec2.internal'
No addresses specified for host 'ip-10-0-5-8.ec2.internal', using 'ip-10-0-5-8.ec2.internal'
Destroying cluster on hosts: 'ip-10-0-3-58.ec2.internal', 'ip-10-0-4-38.ec2.internal', 'ip-10-0-5-8.ec2.internal'...
ip-10-0-3-58.ec2.internal: Successfully destroyed cluster
ip-10-0-5-8.ec2.internal: Successfully destroyed cluster
ip-10-0-4-38.ec2.internal: Successfully destroyed cluster
Requesting remove 'pcsd settings' from 'ip-10-0-3-58.ec2.internal', 'ip-10-0-4-38.ec2.internal', 'ip-10-0-5-8.ec2.internal'
ip-10-0-3-58.ec2.internal: successful removal of the file 'pcsd settings'
ip-10-0-4-38.ec2.internal: successful removal of the file 'pcsd settings'
ip-10-0-5-8.ec2.internal: successful removal of the file 'pcsd settings'
Sending 'corosync authkey', 'pacemaker authkey' to 'ip-10-0-3-58.ec2.internal', 'ip-10-0-4-38.ec2.internal', 'ip-10-0-5-8.ec2.internal'
ip-10-0-3-58.ec2.internal: successful distribution of the file 'corosync authkey'
ip-10-0-3-58.ec2.internal: successful distribution of the file 'pacemaker authkey'
ip-10-0-4-38.ec2.internal: successful distribution of the file 'corosync authkey'
ip-10-0-4-38.ec2.internal: successful distribution of the file 'pacemaker authkey'
ip-10-0-5-8.ec2.internal: successful distribution of the file 'corosync authkey'
ip-10-0-5-8.ec2.internal: successful distribution of the file 'pacemaker authkey'
Sending 'corosync.conf' to 'ip-10-0-3-58.ec2.internal', 'ip-10-0-4-38.ec2.internal', 'ip-10-0-5-8.ec2.internal'
ip-10-0-3-58.ec2.internal: successful distribution of the file 'corosync.conf'
ip-10-0-4-38.ec2.internal: successful distribution of the file 'corosync.conf'
ip-10-0-5-8.ec2.internal: successful distribution of the file 'corosync.conf'
Cluster has been successfully set up.
Starting cluster on hosts: 'ip-10-0-3-58.ec2.internal', 'ip-10-0-4-38.ec2.internal', 'ip-10-0-5-8.ec2.internal'...
sh-4.4$

クラスターの有効化と起動

続いて、クラスターの有効化と起動を行います。

クラスターの有効化と起動を行う際は、以下のようなフォーマットのコマンドを実行します。

# クラスターの有効化
# クラスターを構成している全てのノードに対して、OS起動時にクラスターサービスが実行されるよう設定
pcs cluster enable --all

# クラスターの起動
# クラスターを構成している全てのノードに対して、クラスターサービスを起動する
pcs cluster start --all

実際の操作ログは以下の通りです。全てのノードに対して正常に操作が完了していることが確認できます。

sh-4.4$ sudo pcs cluster enable --all
ip-10-0-3-58.ec2.internal: Cluster Enabled
ip-10-0-4-38.ec2.internal: Cluster Enabled
ip-10-0-5-8.ec2.internal: Cluster Enabled
sh-4.4$
sh-4.4$
sh-4.4$ sudo pcs cluster start --all
ip-10-0-3-58.ec2.internal: Starting Cluster...
ip-10-0-5-8.ec2.internal: Starting Cluster...
ip-10-0-4-38.ec2.internal: Starting Cluster...

フェンシングの設定

次にフェンシングの設定をします。

改めてフェンシングとは何かを確認します。 公式ドキュメントには以下のような記載があります。

クラスター内のノードの 1 つと通信が失敗した場合に、障害が発生したクラスターノードがアクセスする可能性があるリソースへのアクセスを、その他のノードが制限したり、解放したりできるようにする必要があります。クラスターノードが応答しない可能性があるため、そのクラスターノードと通信しても成功しません。代わりに、フェンスエージェントを使用した、フェンシングと呼ばれる外部メソッドを指定する必要があります。フェンスデバイスは、クラスターが使用する外部デバイスのことで、このデバイスを使用して、不安定なノードによる共有リソースへのアクセスを制限したり、クラスタノードでハードリブートを実行します。

フェンスデバイスが設定されていないと、以前使用していたリソースが解放されていることを切断されているクラスターノードが把握できず、他のクラスターノードでサービスを実行できなくなる可能性があります。また、クラスターノードがそのリソースを解放したとシステムが誤って想定し、データが破損または損失する可能性もあります。フェンスデバイスが設定されていないと、データの整合性は保証できず、クラスター設定はサポートされません。

1.2. High Availability Add-On の概念 - 1.2.1. フェンシング

要するに、フェンシングとは障害発生時に複数のノードが同時にリソースにアクセスしないように排他処理をする仕組みです。
fence-agents-awsの場合は、障害が発生したと判断したノードを再起動させます。

フェンシングの設定は、以下のようなフォーマットのコマンドを実行します。フェンシングの細かいプロパティについてはこちらのRed Hatの公式ドキュメントをご覧ください。

pcs stonith create name fence_aws access_key=access-key secret_key=secret-access-key region=region pcmk_host_map="rhel-hostname-1:Instance-ID-1;rhel-hostname-2:Instance-ID-2;rhel-hostname-3:Instance-ID-3" power_timeout=240 pcmk_reboot_timeout=480 pcmk_reboot_retries=4

実際の操作ログは以下の通りです。なお、今回はIAMロールで認証情報を渡しているので、access_keyと、secret_keyは指定していません。

sh-4.4$ sudo pcs stonith create clusterfence fence_aws region=us-east-1 pcmk_host_map="ip-10-0-3-58.ec2.internal:i-075fbb3489e5e7368;ip-10-0-4-38.ec2.internal:i-003c77cde1b375e02;ip-10-0-5-8.ec2.internal:i-0f279cee7b3c60a79" power_timeout=240 pcmk_reboot_timeout=480 pcmk_reboot_retries=4

フェンシング設定後、クラスターのステータスを確認します。クラスターのステータスを確認すると、clusterfenceというフェンシング用のリソースが作成できていることが確認できます。

sh-4.4$ sudo pcs status
Cluster name: web-cluster
Cluster Summary:
  * Stack: corosync
  * Current DC: ip-10-0-5-8.ec2.internal (version 2.0.5-9.el8_4.1-ba59be7122) - partition with quorum
  * Last updated: Tue Jun  8 07:08:48 2021
  * Last change:  Tue Jun  8 07:07:54 2021 by root via cibadmin on ip-10-0-3-58.ec2.internal
  * 3 nodes configured
  * 1 resource instance configured

Node List:
  * Online: [ ip-10-0-3-58.ec2.internal ip-10-0-4-38.ec2.internal ip-10-0-5-8.ec2.internal ]

Full List of Resources:
  * clusterfence	(stonith:fence_aws):	 Started ip-10-0-3-58.ec2.internal

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled
sh-4.4$

それではフェンシングのテストをしてみます。手動でフェンシングを実行した後、クラスターのステータスを確認してみます。
実際の操作ログは以下の通りです。フェンシングをしたip-10-0-5-8.ec2.internalのステータスが、OFFLINEになっていることが確認できます。

sh-4.4$ sudo pcs stonith fence ip-10-0-5-8.ec2.internal
Node: ip-10-0-5-8.ec2.internal fenced
sh-4.4$
sh-4.4$ sudo pcs status
Cluster name: web-cluster
Cluster Summary:
  * Stack: corosync
  * Current DC: ip-10-0-4-38.ec2.internal (version 2.0.5-9.el8_4.1-ba59be7122) - partition with quorum
  * Last updated: Tue Jun  8 07:13:38 2021
  * Last change:  Tue Jun  8 07:07:54 2021 by root via cibadmin on ip-10-0-3-58.ec2.internal
  * 3 nodes configured
  * 1 resource instance configured

Node List:
  * Online: [ ip-10-0-3-58.ec2.internal ip-10-0-4-38.ec2.internal ]
  * OFFLINE: [ ip-10-0-5-8.ec2.internal ]

Full List of Resources:
  * clusterfence	(stonith:fence_aws):	 Started ip-10-0-3-58.ec2.internal

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled
sh-4.4$

マネージメントコンソールを確認すると、ip-10-0-5-8.ec2.internalが停止->起動していることが確認できます。

ip-10-0-5-8.ec2.internalが起動完了するまで待った後、クラスターのステータスを確認すると、ip-10-0-5-8.ec2.internalのステータスが、Onlineになっていることが確認できます。

sh-4.4$ sudo pcs status
Cluster name: web-cluster
Cluster Summary:
  * Stack: corosync
  * Current DC: ip-10-0-4-38.ec2.internal (version 2.0.5-9.el8_4.1-ba59be7122) - partition with quorum
  * Last updated: Tue Jun  8 07:14:50 2021
  * Last change:  Tue Jun  8 07:07:54 2021 by root via cibadmin on ip-10-0-3-58.ec2.internal
  * 3 nodes configured
  * 1 resource instance configured

Node List:
  * Online: [ ip-10-0-3-58.ec2.internal ip-10-0-4-38.ec2.internal ip-10-0-5-8.ec2.internal ]

Full List of Resources:
  * clusterfence	(stonith:fence_aws):	 Started ip-10-0-3-58.ec2.internal

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled
sh-4.4$

サービスの登録

それでは、最後にクラスターにサービスを登録します。

まず、クラスターに登録するサービスにどのようなパラメーターがあるのか確認をします。サービスのパラメーターの確認は、以下のようなフォーマットのコマンドを実行します。

pcs resource describe resourcetype

今回クラスターに登録するサービスはApacheとPHP-FPMです。なお、以下の通り、RHEL8系よりPHPはモジュール版ではなく、FastCGI版がデフォルトになっています。

Red Hat Enterprise Linux 8 には PHP 7.2 が同梱されています。このバージョンには、RHEL 7 で利用できた PHP 5.4 に対する重要な変更が追加されています。

PHP はデフォルトで FastCGI Process Manager (FPM) を使用します (スレッド化された httpd で安全に使用できます)。

15.1. 動的プログラミング言語 - 15.1.2. PHP への主な変更点

実際の操作ログは以下の通りです。Apacheはconfigfileや、clientなど複数の指定可能なパラメーターがありますが、PHP-FPMは設定できるパラメーターはないようですね。

sh-4.4$ pcs resource describe apache
Assumed agent name 'ocf:heartbeat:apache' (deduced from 'apache')
ocf:heartbeat:apache - Manages an Apache Web server instance

This is the resource agent for the Apache Web server.
This resource agent operates both version 1.x and version 2.x Apache
servers.

The start operation ends with a loop in which monitor is
repeatedly called to make sure that the server started and that
it is operational. Hence, if the monitor operation does not
succeed within the start operation timeout, the apache resource
will end with an error status.

The monitor operation by default loads the server status page
which depends on the mod_status module and the corresponding
configuration file (usually /etc/apache2/mod_status.conf).
Make sure that the server status page works and that the access
is allowed *only* from localhost (address 127.0.0.1).
See the statusurl and testregex attributes for more details.

See also http://httpd.apache.org/

Resource options:
  configfile (unique): The full pathname of the Apache configuration file. This file is parsed to provide defaults for various other
                       resource agent parameters.
  httpd: The full pathname of the httpd binary (optional).
  port: A port number that we can probe for status information using the statusurl. This will default to the port number found in the
        configuration file, or 80, if none can be found in the configuration file.
  statusurl: The URL to monitor (the apache server status page by default). If left unspecified, it will be inferred from the apache
             configuration file. If you set this, make sure that it succeeds *only* from the localhost (127.0.0.1). Otherwise, it may
             happen that the cluster complains about the resource being active on multiple nodes.
  testregex: Regular expression to match in the output of statusurl. Case insensitive.
  client: Client to use to query to Apache. If not specified, the RA will try to find one on the system. Currently, wget and curl are
          supported. For example, you can set this parameter to "curl" if you prefer that to wget.
  testurl: URL to test. If it does not start with "http", then it's considered to be relative to the Listen address.
  testregex10: Regular expression to match in the output of testurl. Case insensitive.
  testconffile: A file which contains test configuration. Could be useful if you have to check more than one web application or in case
                sensitive info should be passed as arguments (passwords). Furthermore, using a config file is the only way to specify
                certain parameters. Please see README.webapps for examples and file description.
  testname: Name of the test within the test configuration file.
  options: Extra options to apply when starting apache. See man httpd(8).
  envfiles: Files (one or more) which contain extra environment variables. If you want to prevent script from reading the default file, set
            this parameter to empty string.
  use_ipv6: We will try to detect if the URL (for monitor) is IPv6, but if that doesn't work set this to true to enforce IPv6.

Default operations:
  start: interval=0s timeout=40s
  stop: interval=0s timeout=60s
  monitor: interval=10s timeout=20s
sh-4.4$
sh-4.4$
sh-4.4$ pcs resource describe service:php-fpm
service:php-fpm - systemd unit file for php-fpm

The PHP FastCGI Process Manager

Default operations:
  start: interval=0s timeout=100
  stop: interval=0s timeout=100
  monitor: interval=60 timeout=100
sh-4.4$

それでは、クラスターにApacheとPHP-FPMを登録します。

クラスターへのサービスの登録は、以下のようなフォーマットのコマンドを実行します。

pcs resource create resource_id [standard:[provider:]]type [resource_options] [op operation_action operation_options [operation_action operation options]...] [meta meta_options...] [clone [clone_options] | master [master_options] | --group group_name [--before resource_id | --after resource_id] | [bundle bundle_id] [--disabled] [--wait[=n]]

実際の操作ログは以下の通りです。ApacheとPHP-FPMを同じノードで動作させたいので、webgroupというグループに含まれるように設定します。
サービス登録後にクラスターのステータスを確認すると、webgroupApachePHP-FPMが登録され起動していることが確認できます。

sh-4.4$ sudo pcs resource create Apache ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf statusurl="http://localhost/server-status" client=wget --group webgroup
sh-4.4$
sh-4.4$ sudo pcs resource create PHP-FPM service:php-fpm --group webgroup
sh-4.4$
sh-4.4$ sudo pcs status
Cluster name: web-cluster
Cluster Summary:
  * Stack: corosync
  * Current DC: ip-10-0-4-38.ec2.internal (version 2.0.5-9.el8_4.1-ba59be7122) - partition with quorum
  * Last updated: Tue Jun  8 07:23:04 2021
  * Last change:  Tue Jun  8 07:22:56 2021 by root via cibadmin on ip-10-0-3-58.ec2.internal
  * 3 nodes configured
  * 3 resource instances configured

Node List:
  * Online: [ ip-10-0-3-58.ec2.internal ip-10-0-4-38.ec2.internal ip-10-0-5-8.ec2.internal ]

Full List of Resources:
  * clusterfence	(stonith:fence_aws):	 Started ip-10-0-3-58.ec2.internal
  * Resource Group: webgroup:
    * Apache	(ocf::heartbeat:apache):	 Started ip-10-0-4-38.ec2.internal
    * PHP-FPM	(service:php-fpm):	 Started ip-10-0-4-38.ec2.internal

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled
sh-4.4$

マネージメントコンソールでターゲットグループを確認すると、webgroupのリソースが起動しているip-10-0-4-38.ec2.internal (RHEL8HA1)のステータスがhealthyになっていました。

ターゲットグループのステータスがhealthyになっていることから、サービスが正しく起動していそうですね。

それでは、実際にブラウザでアクセスしてphpinfoが表示できるか確認します。

以下の通り、phpinfoが表示されています。Systemに記載されているホスト名も、クラスターのステータスで確認した通り、ip-10-0-4-38.ec2.internalとなっていることが確認できます。

High Availability Add-On のフェールオーバー

手動でフェールオーバー

クラスターといえば、フェールオーバーです。実際にサービスがフェールオーバーするかを確認してみます。

まずは、手動でサービスをフェールオーバーしてみます。

手動でフェールオーバーするために、現在、webgroupが稼働しているノードをスタンバイモードにします。
ノードがスタンバイモードになると、対象のノードはノード上のリソースを別のノードにフェールオーバーします。

ノードをスタンバイモードにする場合は、以下のようなフォーマットのコマンドを実行します。

pcs node standby node | --all

実際の操作ログは以下の通りです。webgroupが稼働しているノード(ip-10-0-4-38.ec2.internal)をスタンバイモードにして、クラスターのステータスを確認してみました。
Node Listip-10-0-4-38.ec2.internalstandbyになり、リソースが稼働しているノードがip-10-0-4-38.ec2.internalからip-10-0-5-8.ec2.internalに変わっていることが確認できますね。

sh-4.4$ sudo pcs node standby ip-10-0-4-38.ec2.internal
sh-4.4$
sh-4.4$ sudo pcs status
Cluster name: web-cluster
Cluster Summary:
  * Stack: corosync
  * Current DC: ip-10-0-4-38.ec2.internal (version 2.0.5-9.el8_4.1-ba59be7122) - partition with quorum
  * Last updated: Tue Jun  8 07:26:05 2021
  * Last change:  Tue Jun  8 07:25:57 2021 by root via cibadmin on ip-10-0-3-58.ec2.internal
  * 3 nodes configured
  * 3 resource instances configured

Node List:
  * Node ip-10-0-4-38.ec2.internal: standby
  * Online: [ ip-10-0-3-58.ec2.internal ip-10-0-5-8.ec2.internal ]

Full List of Resources:
  * clusterfence	(stonith:fence_aws):	 Started ip-10-0-3-58.ec2.internal
  * Resource Group: webgroup:
    * Apache	(ocf::heartbeat:apache):	 Started ip-10-0-5-8.ec2.internal
    * PHP-FPM	(service:php-fpm):	 Starting ip-10-0-5-8.ec2.internal

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled
sh-4.4$

マネージメントコンソールからターゲットグループを確認すると、ステータスがhealthyのインスタンスがRHEL8HA1からRHEL8HA2に変わっています。

また、phpinfo.phpにアクセスすると、Systemに記載されているホスト名が、クラスターのステータスで確認した通り、フェールオーバー先のip-10-0-5-8.ec2.internalとなっていることが確認できます。

それでは、スタンバイモードにしていたip-10-0-4-38.ec2.internalをスタンバイモードから解除します。

ノードをスタンバイモードから解除する場合は、以下のようなフォーマットのコマンドを実行します。

pcs node unstandby node | --all

実際の操作ログは以下の通りです。
Node Liststandbyだった、ip-10-0-4-38.ec2.internalOnlineになっていることが確認できます。

sh-4.4$ sudo pcs node unstandby ip-10-0-4-38.ec2.internal
sh-4.4$
sh-4.4$
sh-4.4$ sudo pcs status
Cluster name: web-cluster
Cluster Summary:
  * Stack: corosync
  * Current DC: ip-10-0-4-38.ec2.internal (version 2.0.5-9.el8_4.1-ba59be7122) - partition with quorum
  * Last updated: Tue Jun  8 07:26:50 2021
  * Last change:  Tue Jun  8 07:26:46 2021 by root via cibadmin on ip-10-0-3-58.ec2.internal
  * 3 nodes configured
  * 3 resource instances configured

Node List:
  * Online: [ ip-10-0-3-58.ec2.internal ip-10-0-4-38.ec2.internal ip-10-0-5-8.ec2.internal ]

Full List of Resources:
  * clusterfence	(stonith:fence_aws):	 Started ip-10-0-3-58.ec2.internal
  * Resource Group: webgroup:
    * Apache	(ocf::heartbeat:apache):	 Started ip-10-0-5-8.ec2.internal
    * PHP-FPM	(service:php-fpm):	 Started ip-10-0-5-8.ec2.internal

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled
sh-4.4$

カーネルパニックによるフェールオーバー

実際のフェールオーバーは障害発生によるものです。

そこで、以下の記事で紹介したように、診断割り込みAPIを実行してカーネルパニックを発生させて、フェールオーバーするか確認してみます。

まずは、以下コマンドで現在サービスが稼働しているip-10-0-5-8.ec2.internalをカーネルパニックさせます。

> aws ec2 send-diagnostic-interrupt --instance-id i-0f279cee7b3c60a79
>

カーネルパニック発生後、クラスターのステータスを確認します。

実際の操作ログは以下の通りです。一時的にwebgroupStoppedになり、ip-10-0-4-38.ec2.internalでサービスを再開していることが確認できます。

sh-4.4$ sudo pcs status
Cluster name: web-cluster
Cluster Summary:
  * Stack: corosync
  * Current DC: ip-10-0-4-38.ec2.internal (version 2.0.5-9.el8_4.1-ba59be7122) - partition with quorum
  * Last updated: Tue Jun  8 08:16:38 2021
  * Last change:  Tue Jun  8 07:45:05 2021 by hacluster via crmd on ip-10-0-5-8.ec2.internal
  * 3 nodes configured
  * 3 resource instances configured

Node List:
  * Online: [ ip-10-0-3-58.ec2.internal ip-10-0-4-38.ec2.internal ip-10-0-5-8.ec2.internal ]

Full List of Resources:
  * clusterfence	(stonith:fence_aws):	 Started ip-10-0-3-58.ec2.internal
  * Resource Group: webgroup:
    * Apache	(ocf::heartbeat:apache):	 Stopped
    * PHP-FPM	(service:php-fpm):	 Stopped

Pending Fencing Actions:
  * reboot of ip-10-0-5-8.ec2.internal pending: client=pacemaker-controld.38962, origin=ip-10-0-4-38.ec2.internal

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled
sh-4.4$
sh-4.4$
sh-4.4$ sudo pcs status
Cluster name: web-cluster
Cluster Summary:
  * Stack: corosync
  * Current DC: ip-10-0-4-38.ec2.internal (version 2.0.5-9.el8_4.1-ba59be7122) - partition with quorum
  * Last updated: Tue Jun  8 08:18:22 2021
  * Last change:  Tue Jun  8 07:45:05 2021 by hacluster via crmd on ip-10-0-5-8.ec2.internal
  * 3 nodes configured
  * 3 resource instances configured

Node List:
  * Online: [ ip-10-0-3-58.ec2.internal ip-10-0-4-38.ec2.internal ip-10-0-5-8.ec2.internal ]

Full List of Resources:
  * clusterfence	(stonith:fence_aws):	 Started ip-10-0-3-58.ec2.internal
  * Resource Group: webgroup:
    * Apache	(ocf::heartbeat:apache):	 Started ip-10-0-4-38.ec2.internal
    * PHP-FPM	(service:php-fpm):	 Started ip-10-0-4-38.ec2.internal

Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled
sh-4.4$

phpinfo.phpにアクセスすると、Systemに記載されているホスト名が、クラスターのステータスで確認した通り、フェールオーバー先のip-10-0-4-38.ec2.internalとなっていることが確認できます。

High Availability Add-Onを使えば、Red Hat Enterprise Linuxで簡単にActive/Standby構成のクラスターが組めますよ。

HA Add-Onで簡単にActive/Standby構成のクラスターが組めることが確認できました。

AWSでは可用性を向上させるために、ロードバランサーやAuto Scalingを使用して、Multi-AZでActive/Active構成にすることが多いと思います。

しかし、基幹系のシステムはActive/Active構成のクラスターを組むことが、ライセンスやソフトウェアの処理の都合などで難しい場合がよくあります。そのような場合に、今回紹介したHA Add-Onを使用することで、簡単に可用性を向上させることができると考えます。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!