はじめに
OpenShiftの基本機能である「OpenShift Virtualization」では、Kubernetesの上で仮想マシンを起動することができます。勿論、RHEL(Red Hat Enterprise Linux)の起動も対応しています。
さて、RHELは複数台をクラスタとして高可用性(High Availability)を実現するアドオン「High Availability Add-On(略して、HA Add-on)を追加オプションとして提供しています。
複数台のRHEL仮想マシンでHAクラスタを組むことで、主系の仮想マシンにおいて何らかの原因でアプリが動かなくなった場合、即時に副系に切り替え(フェイルオーバ)することで、アプリのダウンタイムを極力下げることが可能です。
OpenShift Virtualizationは、その上で起動するRHEL仮想マシンに関して、RHEL HA Add-onをサポートしています。
Does OpenShift Virtualization support application clustering?
Yes, the Red Hat Enterprise Linux (RHEL) High-Availability Add-on (Pacemaker) and Windows Server Failover Clustering (WSFC) are supported.
ミッションクリティカルなシステムを提供する場合、仮想基盤のみならず「OSレイヤでのHA確保」も重要な観点です。
RHEL HA Add-onについての詳細は、Red Hatのブログを御覧ください。
OSレイヤのクラスタリングのメリット / デメリット
OSレイヤのクラスタリングのメリットとしては、大きく以下が考えられます。
- プロセスレベルで死活監視可能
- もし仮想マシン(主系)が落ちてしまっても、副系に切り替えるだけで、すぐにアプリケーションの稼働が継続できる
一方でデメリットとしては以下が考えられます。
- 仮想マシンを複数起動するため仮想基盤のリソースを多く要する
- 仮想マシンの数が増え、RHELのライセンスコストが増大する
- 共有ストレージの管理運用が必要になる
OpenShift VirtualizationでRHEL HA Add-onを利用するメリット
RHEL HA Add-onは複数の仮想基盤やAWSを始めとしたパブリッククラウドでも利用できますが、いずれのインフラであってもHAクラスタを組むからには、クラスタに参加する仮想マシンの数だけRHELのサブスクリプションが必要です。
しかし、対応するOpenShiftのEditionにおいては、OpenShift Virtualizationで起動するRHEL仮想マシンのサブスクリプションがエンタイトルされています。
If you are an OpenShift bare-metal customer, your OpenShift entitlement includes RHEL entitlements for any hosted RHEL virtual machines.
つまり、RHEL仮想マシンをいくら起動しても追加料金は掛かりません。
さらに、ストレージ共有の仕組みはKubernetesのネイティブな機能を利用し、比較的簡単に実装可能です。これがOpenShift VirtualizationでRHEL HA Add-onを組むメリットです。
RHELサブスクリプションのエンタイトルメントについては、こちらの記事も参照ください。
HA Add-onのサブスクリプションは別途購入が必要です。
いまからやることのイメージ
をこんな感じで絵に示します。まずは物理構成イメージです。
クラスタの物理構成イメージ
今回はサーバ調達の容易性に鑑み、AWSを用いてOpenShiftクラスタを構築しました。また、既にOpenShift Virtualization及びODFを利用可能としています。
クラスタの情報
- 3ノードクラスタで構築。
- OpenShift Virtualization Operatorを介してOpenShift Virtualizationが利用可能な状態。
- ストレージシステムとしてOpenShift Data Foundation(ODF)を利用。
- 追加でベアメタルインスタンス(c5.metal)を2台アタッチ。
OpenShift Virtualizationにおいて仮想マシンをデプロイするためにはベアメタルインスタンスが必要です。今回はc5.metalを2台アタッチしました。仮想マシンはこの2台のいずれかにスケジュールされ、仮想マシンノードにはスケジュールされません。なお、AWSのベアメタルインスタンスは相応のコストを要しますので、ご注意ください。
次は論理構成イメージを示します。
仮想マシンの論理構成イメージ
なんだかみょうちくりんな絵になってしまいました...
ちょっと分かりづらいので、以下補足説明です。
HAクラスタ構成
- HAクラスタは2台構成(rhel9-ha1 / rhel9-ha2)とします。
- 各VMが必ず異なるノードにスケジュールされるようにします。
ネットワーク構成
- 各VMに追加した2つめのvNIC(eth1)にはそれぞれ静的IPアドレスを付与します。
- またeth1は追加のオーバレイネットワークに参加し、このネットワークをPacemakerのHeartbeatネットワーク(互いの死活を確認する経路)とします。
- 元々Podについているeth0はデフォルトのオーバレイネットワーク(Pod Network)に接続します。
サンプルアプリ
- アクセスするたびにカウンター値がインクリメントされていくだけの簡単なステートフルアプリです。
- Node.jsで作成し、カウンター値(ステート)はsqliteで保存します。
- アプリは
/var/lib/nodeapp
で動作させます。
ストレージ
- 各VMの
/var/lib/nodeapp
に「nodeapp-storage」という共有ディスクをマウントします。 - ディスクの永続化を行うためにPersistentVolumeClaim(PVC)を利用します。
- PVCは、異なるノードにスケジュールされた2つのVMから読み書き可能となるReadWriteMany(RWX)で永続ボリュームを要求するようにします。
この構成にて、以下を試します。
- rhel9-ha1のNode.jsプロセスをKill(強制終了)した場合、rhel9-ha1上で速やかにプロセスが再起動するか?
- rhel9-ha1を停止(Stop)した場合、速やかにrhel9-ha2にてアプリの動作が引き継がれるか?
- rhel9-ha1がスケジュールされているノードをシャットダウンした際に、速やかにrhel9-ha2にてアプリの動作が引き継がれるか?
「1.」についてはプロセスレベルの障害を再現、「2.」についてはOSレベルの障害を再現します。また「3.」については仮想基盤を構成するホストマシン(Kubernetesのノード)レベルでの障害を再現します。
それでは早速環境を作りましょう〜!
今回はOpenShift Virtualization上でRHEL HAを簡単かつ分かりやすく試すことを目的にしており、商用利用ではサポートされない構成です。具体的なサポート対象外の設定や構成に関しては、本記事の該当箇所で適宜補足しています。公式ドキュメントと合わせて適宜ご参照ください。
事前準備
OpenShift Virtualizationを利用可能なOpenShiftクラスタにおいて、事前に以下を実行します。
プロジェクト(=NameSpace)作成
OpenShiftクラスタ内に「rhel-ha」という名前のプロジェクトを作成しておきます。
NAD作成
2nd Overlay Netowork(追加のオーバレイネットワーク)を作成します。「NetworkAttachmentDefinition(NAD)」というカスタムリソースを作成すると、追加のオーバレイネットワークを作成することができます。NADはNameSpaceスコープのリソースのため、NADによって作成される追加のオーバレイネットワークは、同じNameSpace内のPodだけが参加できます。
OpenShiftコンソールの管理者パースペクティブにて、「ネットワーク」メニューから「NetworkAttachmentDefinitions」を選んで作成することが可能です。
なお、以下のマニフェストを適用(oc apply -f
)するだけでも同NADを作成可能です。
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: nad-rhel-ha
namespace: rhel-ha
spec:
config: |-
{
"cniVersion": "0.3.1",
"name": "nad-rhel-ha",
"type": "ovn-k8s-cni-overlay",
"netAttachDefName": "rhel-ha/nad-rhel-ha",
"topology": "layer2"
}
OVN Kubernetes L2 overlay networkは、OpenShiftに標準で採用されているSoftware Defined Network(SDN)であるOVN Kubernetesを活用したネットワークです。このネットワークに参加するPodは、ノードを跨いでレイヤ2で通信できます。
詳細はOpenShiftの公式ドキュメントを確認いただくことをおすすめします。
Secret作成
同プロジェクト内に、RHEL仮想マシンに登録するSSH公開鍵をSecretとして登録しておきます。YAMLは以下の通りです。
kind: Secret
apiVersion: v1
metadata:
name: public-key-rhel-ha
namespace: rhel-ha
data:
key: <SSH公開鍵をBase64エンコードしたもの>
type: Opaque
PVC作成
共有ディスク(nodeapp-storage)のためのPVCを作成しておきます。今回、要求するPVのスペックは以下の通りです。
- 容量:5GiB
- ボリュームモード:ブロック
- アクセスモード(読み書きモード):ReadWriteMany(RWX)
- StorageClass:ocs-storagecluster-ceph-rbd-virtualization
YAMLは以下の通りです。
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nodeapp-pvc
namespace: rhel-ha
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: ocs-storagecluster-ceph-rbd-virtualization
volumeMode: Block
「ocs-storagecluster-ceph-rbd-virtualization」は、ODFによって提供されるCeph RBD(RADOS Block Device)を利用した仮想マシン向けのストレージクラスです。仮想マシンのディスクイメージを永続化し、高可用性やスケーラビリティを備えたブロックストレージを提供します。OpenShift Virtualizationが利用可能なクラスタにODFをインストールすると、当該StorageClassが自動的に作成されます。OpenShift Virtualizationにおける仮想マシン用のディスクの永続化に最適化されているので、特段理由がない限り、こちらを利用することがおすすめです。
ゲストシステムログへのアクセスの有効化
RHEL仮想マシンを起動する前に、仮想マシンのログへのアクセスを有効化しておきます。これにより、OpenShiftのコンソール画面上で仮想マシンのvar/log/
配下のログ内容が出力順に確認できるようになります。
Virtualizationパースペクティブの「Overviewメニュー」にて、「Settings」から「Guest managemnet」と進み、「Enable guest system log access」のトグルをONにしてください。
1台目のRHEL仮想マシンの起動
それでは早速RHEL仮想マシンを立てましょう〜!まずは1台目のrhel9-ha1から起動します。
Templateを選択
OpenShiftのコンソール画面にて、管理者パースペクティブの「Virtualization」メニューから「VirtualMachines」を選択します。
「Create VirtualMachine」から「From Template」を選択すると、テンプレートを使って仮想マシンを簡単に作成できます。
「Red Hat Enterprise Linux 9 VM」を選択します。
「Customize Virtualmachine」を選択します。
マニフェストファイルを適用
「YAML」タブに切り替えたら、内容を以下のものに置き換え、「保存」をクリックしてください。
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
labels:
app: rhel9-ha1
name: rhel9-ha1
namespace: rhel-ha
spec:
dataVolumeTemplates:
- apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
creationTimestamp: null
name: rhel9-ha1
spec:
sourceRef:
kind: DataSource
name: rhel9
namespace: openshift-virtualization-os-images
storage:
resources:
requests:
storage: 30Gi
storageClassName: ocs-storagecluster-ceph-rbd-virtualization
running: true
template:
metadata:
labels:
cluster: my_cluster
role: primary
spec:
accessCredentials:
- sshPublicKey:
propagationMethod:
noCloud: {}
source:
secret:
secretName: public-key-rhel-ha
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: role
operator: In
values:
- secondary
topologyKey: kubernetes.io/hostname
architecture: amd64
domain:
cpu:
cores: 1
sockets: 1
threads: 1
devices:
disks:
- disk:
bus: virtio
name: rootdisk
- disk:
bus: virtio
name: cloudinitdisk
- disk:
bus: scsi
name: nodeapp-storage
shareable: true
interfaces:
- macAddress: ''
masquerade: {}
model: virtio
name: default
- bridge: {}
macAddress: ''
model: virtio
name: eth1
rng: {}
features:
acpi: {}
smm:
enabled: true
firmware:
bootloader:
efi: {}
machine:
type: pc-q35-rhel9.4.0
memory:
guest: 2Gi
resources: {}
networks:
- name: default
pod: {}
- multus:
networkName: nad-rhel-ha
name: eth1
terminationGracePeriodSeconds: 180
volumes:
- dataVolume:
name: rhel9-ha1
name: rootdisk
- name: nodeapp-storage
persistentVolumeClaim:
claimName: nodeapp-pvc
- cloudInitNoCloud:
networkData: |
ethernets:
eth1:
addresses:
- 192.168.122.11/24
version: 2
userData: |
#cloud-config
user: cloud-user
password: cloud-password
chpasswd:
expire: false
runcmd:
- subscription-manager register --org=<ORG_ID> --activationkey=<ACTIVATION_KEY>
- subscription-manager repos --enable=rhel-9-for-x86_64-highavailability-rpms
- dnf install pcs pacemaker fence-agents-all nodejs sqlite -y
- systemctl start pcsd.service
- systemctl enable pcsd.service
- echo "hacluster:rhel9ha" | chpasswd
- mkdir -p /var/lib/nodeapp
- echo "192.168.122.11 rhel9-ha1" >> /etc/hosts
- echo "192.168.122.12 rhel9-ha2" >> /etc/hosts
name: cloudinitdisk
YAMLファイルの中にはユーザの環境毎に異なるパラメータがあります。
パラメータ | 内容 |
---|---|
<ORG_ID> |
Red Hat Accountが所属する組織のID |
<ACTIVATION_KEY> |
RHELのサブスクリプションを有効化するために作成するキー |
どちらの内容についても、ハイブリッドクラウドコンソールのアクティベーションキー作成画面 から確認できます。
アクティベーションキーを作成したら、その名称を<ACTIVATION_KEY>
に入れてください。
マニフェストの内容を確認
今貼り付けたマニフェストファイルを使って仮想マシンを即時起動しても良いのですが、中身についてしっかり見ておくことにします。
Labels
この仮想マシンには以下の通りラベルを定義しました。
...
spec:
template:
metadata:
labels:
cluster: my_cluster
role: primary
...
cluster: my_cluster
は、HAクラスタに参加するVM全てに共通して定義するラベルとして用います。後ほどNode.jsアプリをService / Routeを介してインターネットに共有する際に、当該ラベルをセレクタに使います。
role: primary
はrhel9-ha1にのみ定義するラベルです。このラベルはrhel9-ha1 / rhel9-ha2それぞれのPod Anti-Affinityルールを適用する際に利用します。
YAMLの内容はすべてコンソール画面(GUI)の表示にも反映されますので、主要な内容をGUIでも確認しておきましょう。
Overviewタブ
この画面では仮想マシンの名称の変更結果を確認しておきます。「Name」が「rhel9-ha1」となっているか確認してください。
なお、metadata.name
の値は、そのまま仮想マシンのホスト名にもなります。
metadata:
name: rhel9-ha1
...
Schedulingタブ
Kubernetesでは、Podのスケジュールに関する戦略を選択することができます。rhel9-ha1.yaml
を見ると、今回は1つのアフィニティルールが適用されています。
...
spec:
...
template:
...
spec:
...
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
topologyKey: kubernetes.io/hostname
- labelSelector:
matchExpressions:
- key: role
operator: In
values:
- secondary
...
Pod Anti-Affinityによって特定のラベルを有するPodとは異なるノードにスケジュールされるようにしています。これがOpenShift VirtualizationのGUIの「Scheduling」タブにも反映されます。
この内容は、以下のラベルが設定されているPodとは同じノードにスケジュールされないようにしています。
...
spec:
template:
metadata:
labels:
role: secondary
...
なお、同ラベルはこれからrhel-ha2に設定します。これにより、2つのRHEL VMは異なるノードにスケジュールされ、単一の物理マシン(ホスト)に障害が起きても、両仮想マシンが同時に被害に合わないように(停止しないよう)にします。
Affinityとは日本語で「親和性」という意味です。今回は「特定のPod同士を異なるノードに配置する(Pod Anti-Affinity)」という戦略を採用しましたが、逆に「同じノードに配置する(Pod Affinity)」などもを選択可能です。また、KubernetesではAffinityルールに加えて、Podを明示的に特定のノードにスケジュールするためのNodeSelectorというリソースも存在します。逆に「許可されたPodしか特定のノードにスケジュールされないようにする」ルールとして、Taint/Tolerationという仕組みも存在します。OpenShift Virtualizationでは、これらのリソースを組み合わせて、仮想マシンの柔軟なスケジュール戦略を実現可能です。
詳細はKubernetesの公式ドキュメントを確認することをおすすめします。
Network interfacesタブ
rhel9-ha1にはvNIC(仮想NIC)を2つ付与しています。
Kubernetes上の全てのPodに最初からアタッチされているvNIC「default」に加え、追加のvNIC「eth1」をアタッチしました。defaultはKubernetesのデフォルトのオーバレイネットワーク「Pod Network」に参加していますが、追加したeth1はNADによって作成した追加のオーバレイネットワークである2nd Oberlay NW(multus Network)に参加させています。
マニフェストにおける以下の内容が、上述した内容を定義した箇所です。
spec:
template:
spec:
domain:
...
devices:
...
interfaces: ##ネットワークインターフェース(vNIC)の定義
- macAddress: ''
masquerade: {}
model: virtio
name: default
- bridge: {}
macAddress: ''
model: virtio
name: eth1
...
networks: ##どのvNICをどのオーバレイネットワークに追加するかの定義
- name: default
pod: {}
- multus:
networkName: nad-rhel-ha
name: eth1
...
それぞれのvNICに付与されるMACアドレスは、マニフェストファイルでは指定していません。MACアドレスアドレスを''
として空欄にすると、KubeMacPoolコンポーネントによって、共有MACアドレスプールから仮想マシンネットワークインターフェイスのMACアドレスを割り当てます。これにより、各ネットワークインターフェイスに一意のMACアドレスが確実に割り当てられます。
Disksタブ
次に、今回の一番の肝になるディスクについて見ておきます。まず、rhel9-ha1には3つのディスクが存在します。
ディスク | ドライバ | 補足説明 |
---|---|---|
rootdisk | VirtIO | OpenShift Virtualizationで仮想マシンを作成すると必ず作られるディスク。その名の通り、仮想マシンのルートディスクとして、OSを起動(Boot)し、OSイメージや仮想マシン内のデータ一式を保持している。 |
cloudinitdisk | VirtIO | OpenShift Virtualizationでは、仮想マシンの初期設定にcloud-initを利用可能。この設定・実行を行うためのディスク。データは永続化されず、スナップショット対象にもならない。仮想マシンを削除した場合は当該ディスクも削除される。 |
nodeapp-storage | VirtIO | 今回使うサンプルアプリを動作させるために個別に用意した追加のデータディスク。サンプルアプリの資材一式やステートは当該ディスクに保存する。 |
これらのディスク一覧は「Disiks」タブでも確認できます。このなかで「nodeapp-storage」の情報を確認しておきます。右端の「 ⋮ 」をクリックしてして「Edit」を選択すると、設定情報を確認・編集できます。
データディスクであるnodeapp-storageのデータを永続化するためにPVCを利用します。このPVCは事前に作成したnodeapp-pvcです。nodeapp-pvcのスペックは以下の通りでした。
- 容量:5GiB
- ボリュームモード:ブロック
- アクセスモード(読み書きモード):ReadWriteMany(RWX)
- StorageClass:ocs-storagecluster-ceph-rbd-virtualization
当該PVCからプロビジョニングされるPVはRWXのボリュームであり、複数ノードからの参照・更新に対応しています。さらに、「Advanced settings」欄の「share this disk between multiple VirtualMachine」をにチェックを入れました。これにより、文字通り「複数の仮想マシン間の共有ディスクとする」を実現できます。
このように、Kubernetesの永続ボリュームの仕組みと、OpenShift Virtualizationのデフォルトの機能だけで、仮想マシン間の共有ディスクを簡単に用意することができます。
VirtIO は、仮想マシン内のデバイス(ネットワーク、ストレージなど)を効率的に処理するために用いられる、オープンソースの準仮想化デバイスドライバです。KVMやQEMUなどの仮想環境で広く採用されていますが、特定のハイパーバイザやゲストOSに依存しないフレームワークです。OpenShift Virtualizationにおいて仮想マシンにディスクやvNICをアタッチする際には、特段の理由がなければVirtIOをドライバとして利用すると良いでしょう。
「share this disk between multiple VirtualMachine」を有効化したい場合は、共有ディスクのボリュームは、必ずブロックモード(ブロックデバイス)」でなければなりません。そのため、永続ボリューム(PV)を払い出す際には、ブロックデバイスでプロビジョニングできるStorageClassを指定する必要があります。
共有ディスクを設定して、複数の仮想マシン (VM) が同じ基礎となるストレージを共有できるようにすることができます。共有ディスクのボリュームは、ブロックモードである必要があります。
Scriptsタブ
Scriptsでは、仮想マシンの初期設定に関するいくつかの設定を実施できます。今回はcloud-initによる初期設定と、仮想マシンにSSH公開鍵の登録を行います。特に肝となるのはcloud-initです。
cloud-initは、仮想マシンの初期設定やパッケージインストールを自動化するためのオープンソースのツールです。これにより、仮想マシンの起動時に自動で必要な初期設定が実行できます。OpenShift Virtualizationにおいて仮想マシンのセットアップの自動化を推進する上では、cloud-initを積極的に使いましょう。
「Scriptes」タブの「cloud-init」欄の「Edit」をクリックすると、Form ViewとScript直書きの2種類で設定内容を編集できます。
この黄色の枠内にcloud-initの書式に基づいた設定内容を記載することができ、その内容はVirtualMachineのマニフェストファイルのspec.template.spec.volumesのcloudInitNoCloudにも反映されます。
spec:
...
template:
...
spec:
...
volumes:
...
- cloudInitNoCloud:
networkData: |
ethernets:
eth1:
addresses:
- 192.168.122.11/24
version: 2
userData: |
#cloud-config
user: cloud-user
password: cloud-password
chpasswd:
expire: false
runcmd:
- subscription-manager register --org=<ORG_ID> --activationkey=<ACTIVATION_KEY>
- subscription-manager repos --enable=rhel-9-for-x86_64-highavailability-rpms
- dnf install pcs pacemaker fence-agents-all nodejs sqlite -y
- systemctl start pcsd.service
- systemctl enable pcsd.service
- echo "hacluster:rhel9ha" | chpasswd
- mkdir -p /var/lib/nodeapp
- echo "192.168.122.11 rhel9-ha1" >> /etc/hosts
- echo "192.168.122.12 rhel9-ha2" >> /etc/hosts
この内容について少し補足します。
■ networkData
追加したvNICに付与する静的IPアドレスを指定できます。今回はeth1に192.168.122.11/24を設定しました。
■ userData
ユーザ情報を設定する欄です。今回はcloud-user
というユーザを作成し、ログインパスワードをcloud-password
としました。
また、runcmd
以下には仮想マシンを作成する際にroot権限で実行可能なコマンドを記載できます。今回は以下のようなコマンドを記載しています。
# RHELをRed Hat Sbscription Manger(RHSM)に登録し、dnf/yumを利用可能とします
# 自身のRed Hatアカウントの<ORG_ID>と、作成した<ACTIVATION_KEY>を設定してください。
subscription-manager register --org=<ORG_ID> --activationkey=<ACTIVATION_KEY>
# HA Add-onを利用する際に必要なパッケージを取得するためのリポジトリを登録します
subscription-manager repos --enable=rhel-9-for-x86_64-highavailability-rpms
# HA Add-on関連のパッケージ及び、この後node.jsアプリを作成・実行するのに必要パッケージをインストールします
dnf install pcs pacemaker fence-agents-all nodejs sqlite -y
# HA Add-on関連の設定を行うためのpcsデーモンを起動し、仮想マシン起動時に自動開始する設定を行います
systemctl start pcsd.service
systemctl enable pcsd.service
# HAクラスタに参加する仮想マシンに自動作成されるユーザ「hacluster」のパスワードを「rhel9ha」に設定します
echo "hacluster:rhel9ha" | chpasswd
# node.jsアプリを実行するためのディレクトリを作成しておきます
mkdir -p /var/lib/nodeapp
# rhel9-ha1/rhel9-ha2の追加vNICに付与する静的IPアドレスがホスト名で名前解決できるように/etc/hosts内に同内容を追記します
echo "192.168.122.11 rhel9-ha1" >> /etc/hosts
echo "192.168.122.12 rhel9-ha2" >> /etc/hosts
これでrhel9-ha1の設定内容を確認できました。
「Create VirtualMachine」をクリックして、仮想マシン「rhel9-ha1」を起動します。
2台目のRHEL 仮想マシンの起動
続いて2台目です。1台目と全く同様の手順で、以下のマニフェストファイルを用いて仮想マシン「rhel9-ha2」を起動します。
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
labels:
app: rhel9-ha2
name: rhel9-ha2
namespace: rhel-ha
spec:
dataVolumeTemplates:
- apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
creationTimestamp: null
name: rhel9-ha2
spec:
sourceRef:
kind: DataSource
name: rhel9
namespace: openshift-virtualization-os-images
storage:
resources:
requests:
storage: 30Gi
storageClassName: ocs-storagecluster-ceph-rbd-virtualization
running: true
template:
metadata:
labels:
cluster: my_cluster
role: secondary
spec:
accessCredentials:
- sshPublicKey:
propagationMethod:
noCloud: {}
source:
secret:
secretName: public-key-rhel-ha
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: role
operator: In
values:
- primary
topologyKey: kubernetes.io/hostname
architecture: amd64
domain:
cpu:
cores: 1
sockets: 1
threads: 1
devices:
disks:
- disk:
bus: virtio
name: rootdisk
- disk:
bus: virtio
name: cloudinitdisk
- disk:
bus: virtio
name: nodeapp-storage
shareable: true
interfaces:
- macAddress: ''
masquerade: {}
model: virtio
name: default
- bridge: {}
macAddress: ''
model: virtio
name: eth1
rng: {}
features:
acpi: {}
smm:
enabled: true
firmware:
bootloader:
efi: {}
machine:
type: pc-q35-rhel9.4.0
memory:
guest: 2Gi
resources: {}
networks:
- name: default
pod: {}
- multus:
networkName: nad-rhel-ha
name: eth1
terminationGracePeriodSeconds: 180
volumes:
- dataVolume:
name: rhel9-ha2
name: rootdisk
- name: nodeapp-storage
persistentVolumeClaim:
claimName: nodeapp-pvc
- cloudInitNoCloud:
networkData: |
ethernets:
eth1:
addresses:
- 192.168.122.12/24
version: 2
userData: |
#cloud-config
user: cloud-user
password: cloud-password
chpasswd:
expire: false
runcmd:
- subscription-manager register --org=<ORG_ID> --activationkey=<ACTIVATION_KEY>
- subscription-manager repos --enable=rhel-9-for-x86_64-highavailability-rpms
- dnf install pcs pacemaker fence-agents-all nodejs sqlite -y
- systemctl start pcsd.service
- systemctl enable pcsd.service
- echo "hacluster:rhel9ha" | chpasswd
- mkdir -p /var/lib/nodeapp
- echo "192.168.122.11 rhel9-ha1" >> /etc/hosts
- echo "192.168.122.12 rhel9-ha2" >> /etc/hosts
name: cloudinitdisk
1台目との差分
rhel9-ha1との差分は以下の通りです。
マニフェストの箇所 | rhel9-ha1 | rhel9-ha2 | 備考 |
---|---|---|---|
metadata.name | rhel-ha1 | rhel-ha2 | 各VMのホスト名。これに伴い、DataVolumeの名称も合わせて変更しています。 |
spec.template.metadata.labels.role | primary | secondary | HAクラスタ内における役割(主系/副系)を示しています。 |
spec.template.spec.volumes.cloudInitNoCloud.ethernets.eth1.addresses | 192.168.122.11/24 | 192.168.122.12/24 | 各VMのeth1に異なる静的IPアドレスを付与 |
内容を確認できたら、rhel9-ha2も起動してください。
両仮想マシンに必要なパッケージのインストールや設定などが完了するまで、しばらく待ちましょう。
HAクラスタを作成
2台のRHEL仮想マシンが起動し、必要なパッケージのインストールや設定などが完了したら、いよいよHAクラスタを作成します。なお、必要なコマンドの詳細はRHEL HA Add-onの公式ドキュメントでも確認できますので、気になる方はそちらを読んでください。
なお、HAクラスタの設定はどちらの仮想マシンで実行してもOKです。今回はrhel9-ha1で実施します。
SSHでリモート接続
以後、仮想マシンに対するコマンド操作は、作業端末のターミナルで実行します。そのため、仮想マシンにSSHでリモート接続します。SSHにはvirtctl CLIを利用します。
virtctl CLIのダウンロードは、OpenShiftのコンソール画面から可能です。右上にある「?」アイコンをクリックし、表示されるメニューから「コマンドラインツール」を選択します。「
virtctl - KubeVirt command line interface」の欄から、利用している OS およびアーキテクチャに対応するバージョンをダウンロードしてください。
ダウンロードした圧縮ファイルを展開し、実行可能な状態へ変更します。以下は Apple Silicon Mac
(ARM64)での操作例です。
## ダウンロードした ZIP ファイルを解凍
$ tar xvf virtctl.zip
x virtctl
## virtctl CLI を PATH に含まれるディレクトリへ移動
$ sudo mv virtctl /usr/local/bin/virtctl
## virtctl CLI コマンドが利用できるか確認
$ virtctl
Available Commands:
addvolume add a volume to a running VM
completion Generate the autocompletion script for the specified shell
console Connect to a console of a virtual machine instance.
...
これでvirtctlコマンドが利用可能になりました。
rhel9-ha1及びrhel9-ha2に対して、それぞれ以下のコマンドでSSHしてください。
# rhel9-ha1にSSH
Username@Loaclmachine ~ % virtctl -n rhel-ha ssh cloud-user@rhel9-ha1 --identity-file=~/.ssh/id_rsa
# rhel9-ha2にSSH
Username@Loaclmachine ~ % virtctl -n rhel-ha ssh cloud-user@rhel9-ha2 --identity-file=~/.ssh/id_rsa
rhel9-ha1で以下のコマンドを実行
まずは、rhel9-ha1 / rhel9-ha2をHAクラスタに参加させるための認証を行います。
[cloud-user@rhel9-ha1 ~]$ sudo pcs host auth rhel9-ha1 rhel9-ha2 -u hacluster -p rhel9ha
rhel9-ha1: Authorized
rhel9-ha2: Authorize
認証が成功すると上記のようなログが出力されます。なお、hacluster
はHA Add-on関連のパッケージをインストールしたRHELに自動的に作成されるユーザです。パスワードはcloud-init内のコマンドで作成したrhel9ha
としました。
もし各仮想マシンのetc/hosts
に名前解決情報が無い場合は、IPアドレスを直指定することで認証可能です。
sudo pcs host auth 192.168.122.11 192.168.122.12 -u hacluster -p rhel9ha
次にクラスタを作成し稼働させます。
[cloud-user@rhel9-ha1 ~]$ sudo pcs cluster setup my_cluster rhel9-ha1 rhel9-ha2 --start
...
Cluster has been successfully set up.
Starting cluster on hosts: 'rhel9-ha1', 'rhel9-ha2'...
また、各仮想マシンが再起動した際に自動的にHAクラスタも動作するための設定を行います。
[cloud-user@rhel9-ha1 ~]$ sudo pcs cluster enable --all
rhel9-ha1: Cluster Enabled
rhel9-ha2: Cluster Enabled
以下のコマンドで一度HAクラスタの状態を確認しておきます。HA Add-onを構成する各コンポーネント(デーモン)がきちんと動作しているか確認しておきましょう。
[cloud-user@rhel9-ha1 ~]$ sudo pcs status
Cluster name: my_cluster
WARNINGS:
No stonith devices and stonith-enabled is not false
Cluster Summary:
* Stack: corosync (Pacemaker is running)
* Current DC: rhel9-ha1 (version 2.1.8-3.el9-3980678f0) - partition with quorum
* Last updated: Fri Feb 28 02:32:16 2025 on rhel9-ha1
* Last change: Fri Feb 28 02:30:45 2025 by hacluster via hacluster on rhel9-ha1
* 2 nodes configured
* 0 resource instances configured
Node List:
* Online: [ rhel9-ha1 rhel9-ha2 ]
Full List of Resources:
* No resources
Daemon Status:
corosync: active/enabled
pacemaker: active/enabled
pcsd: active/enabled
いくつか追加でコマンドを実行します。まずは、「Shoot the Other Node in the Head (STONITH)」の無効化です。
[cloud-user@rhel9-ha1 ~]$ sudo pcs property set stonith-enabled=false
HAクラスタに参加する仮想マシンを監視するコンポーネントがPacemakerです。このPacemakerはクラスタ内のノードが応答しなくなった場合に STONITH(フェンシング) を使用して、強制的に電源を切ることでデータの整合性を保ちますが、フェンシングデバイスがない環境では STONITH を無効化する必要があります。今回はAWS環境でサーバを調達していますので、ホストマシンの電源をユーザが何等か管理したり操作することができません。よって、今回はSTONITHを無効化します。この設定は商用利用では推奨されません。
フェンシングに関する詳細に記載の通り、STONITHの無効化はRed Hatのサポート構成では無いのでご注意ください。
Fencing in a Red Hat High Availability Cluster
Important: Red Hat does not provide support for clusters without a STONITH device configured for any member, and the expected behavior of such a cluster is undefined. Clusters without STONITH for every node may behave in problematic ways, and should not be relied on for High Availability of critical services or applications.
次は過半数(クォーラム)ポリシーの無効化です。
[cloud-user@rhel9-ha1 ~]$ sudo pcs property set no-quorum-policy=ignore
Pacemaker は、クラスタのノードが過半数(クォーラム)を満たさない場合、リソースの起動やフェイルオーバーを停止します。しかし、2ノードクラスタ(偶数ノード)では、1台が落ちると過半数を満たせないので、クラスタが停止する問題があります。この設定をすることで、クォーラムが失われてもクラスタを継続動作させることができます。なお、2ノードクラスタでは通常この設定が必要ですが、3ノード以上のクラスタでは、スプリットブレインを防ぐ観点から推奨されません。
クォーラムに関する詳細に記載の通り、2ノードクラスタ(2つのRHELインスタンスで構成するクラスタ)では1つのノードが故障するとクラスタとしての機能が意味を成しません。
Exploring Concepts of RHEL High Availability Clusters - Quorum
Two-node cluster: The majority-wins policy doesn't make much sense in a cluster with two nodes. A single member would not have a majority, meaning a failure of a single node would leave the cluster unable to function - not a very useful design for a system that is meant to function through member-failures.
このことから、商用環境における高い可用性の実現には3ノード以上が強く推奨されます。
これで、HAクラスタの作成と設定が完了しました。
共有ディスクをvar/lib/nodeapp
にマウントする
共有ディスク「nodeapp-storage」をrhel9-ha1 / rhel9-ha2のvar/lib/nodeapp
にマウントします。
ファイルシステムを作成する
nodeapp-storageの永続ボリュームはブロックデバイスとなっており、まだまっさらな状態です。まずはファイルシステムを作成します。
その前に、nodeapp-storageがどのデバイスとして認識されているかを確認します。引き続きrhel9-ha1で以下のコマンドを実行します。
[cloud-user@rhel9-ha1 ~]$ lsblk -f
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
vda
├─vda1
├─vda2 vfat FAT16 7B77-95E7 192.8M 4% /boot/efi
├─vda3 xfs boot a53019c4-0c14-499f-b4aa-10f62413b407 786.7M 18% /boot
└─vda4 xfs root 4b34029a-2c52-4051-bb74-af43d491bc74 26.7G 7% /
vdb iso9660 Joliet Extension cidata 2025-02-28-06-54-22-00
vdc
ちょっとわかりにくいのですが、vdcの項目が空っぽのデバイスとして認識されています。以下のコマンドを使い、ext4形式のファイルシステムを作成します。
[cloud-user@rhel9-ha1 ~]$ sudo mkfs.ext4 -F /dev/vdc
mke2fs 1.46.5 (30-Dec-2021)
Discarding device blocks: done
Creating filesystem with 1310720 4k blocks and 327680 inodes
Filesystem UUID: b63cc29b-6792-4452-afea-6f19c455716f
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736
Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
再度lsblk -f
で今作成したファイルシステムを確認しましょう。
[cloud-user@rhel9-ha1 ~]$ lsblk -f
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
vda
├─vda1
├─vda2 vfat FAT16 7B77-95E7 192.8M 4% /boot/efi
├─vda3 xfs boot a53019c4-0c14-499f-b4aa-10f62413b407 786.7M 18% /boot
└─vda4 xfs root 4b34029a-2c52-4051-bb74-af43d491bc74 26.7G 7% /
vdb iso9660 Joliet Extension cidata 2025-02-28-06-54-22-00
vdc ext4 1.0 <FILESYSTEM_UUID>
<FILESYSTEM_UUID>
は環境毎に異なりますので、ご自身のものを確認しておいてください。
共有ディスクをマウントする
以下のコマンドにて、/var/lib/nodeapp
にファイルシステム(を作成した共有ディスク)をマウントしましょう。
[cloud-user@rhel9-ha1 ~]$ sudo mount /dev/vdc /var/lib/nodeapp
以下のコマンドにて正しくマウントされたか一応確認しておきます。
[cloud-user@rhel9-ha1 ~]$ mount | grep /var/lib/nodeapp
/dev/vdc on /var/lib/nodeapp type ext4 (rw,relatime,seclabel)
サンプルアプリケーションをデプロイする
共有ディスクをマウントしたvar/lib/nodeapp/
内にNode.jsアプリの資材を配置します。
/var/lib/nodeapp
の権限設定を変更する
/var/lib/nodeapp
はrootユーザによって作成されたディレクトリです。そのため、/var/lib/nodeapp
ディレクトリの 所有者 (owner) とグループ (group) を cloud-user に変更し、cloud-userがこのディレクトリ内でファイルを作成・変更できるようにしておきます。
[cloud-user@rhel9-ha1 ~]$ sudo chown cloud-user:cloud-user /var/lib/nodeapp
また、/var/lib/nodeapp
の アクセス権限を「755」 に設定し、所有者(cloud-user)が 読み・書き・実行できるが、グループとその他のユーザーは 読み・実行のみを許可します。
[cloud-user@rhel9-ha1 ~]$ sudo chmod 755 /var/lib/nodeapp
サンプルアプリを作成する
以下のコマンドで、/var/lib/nodeapp
内にindex.js
を作成します。
cat <<EOF | sudo tee /var/lib/nodeapp/index.js
const express = require("express");
const sqlite3 = require("sqlite3").verbose();
const os = require("os");
const app = express();
const PORT = 3000;
// SQLite データベースの設定
const DB_PATH = "/var/lib/nodeapp/data.db";
const db = new sqlite3.Database(DB_PATH, (err) => {
if (err) {
console.error("❌ データベース接続エラー:", err);
} else {
console.log("✅ SQLite データベースに接続しました");
db.run("CREATE TABLE IF NOT EXISTS counter (id INTEGER PRIMARY KEY, value INTEGER)", () => {
db.run("INSERT OR IGNORE INTO counter (id, value) VALUES (1, 0)");
});
}
});
// アクセス時にカウンターを増やして表示
app.get("/", (req, res) => {
db.run("UPDATE counter SET value = value + 1 WHERE id = 1", function (err) {
if (err) {
return res.status(500).send("❌ データ更新エラー");
}
db.get("SELECT value FROM counter WHERE id = 1", (err, row) => {
if (err) {
return res.status(500).send("❌ データ取得エラー");
}
res.send(\`
<h1>🌐 RHEL HA Node.js Demo</h1>
<h2>🖥️ ホスト名: \${os.hostname()}</h2>
<h3>🔢 カウンター値: \${row.value}</h3>
\`);
});
});
});
// サーバー起動
app.listen(PORT, () => {
console.log(\`🚀 サーバー起動: http://localhost:\${PORT}\`);
});
EOF
次に、/var/lib/nodeapp
内でNode.jsアプリの実行に必要なライブラリをインストールします。
## ディレクトリを移動
[cloud-user@rhel9-ha1 ~]$ cd /var/lib/nodeapp
## Node.jsサーバを初期化
[cloud-user@rhel9-ha1 nodeapp]$ npm init -y
Wrote to /var/lib/nodeapp/package.json:
{
"name": "nodeapp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
## 必要なライブラリをインストール
[cloud-user@rhel9-ha1 nodeapp]$ npm install express sqlite3
...
npm notice Run npm install -g npm@11.1.0 to update!
npm notice
## ホームディレクトリに戻っておきます
[cloud-user@rhel9-ha1 nodeapp]$ cd ~
expressは、Node.js向けのHTTPサーバフレームワークです。また、sqlite3はSQLiteデータベースをNode.jsで操作するためのライブラリです。
Systemdサービス登録
今作成したNode.jsアプリケーションをSystemdのサービスとして登録し、仮想マシンが起動した際にアプリケーションも自動的に起動するようにします。
まずは以下のコマンドで、Systemd サービスファイルを作成しておきます。
[cloud-user@rhel9-ha1 ~]$ cat <<EOF | sudo tee /etc/systemd/system/nodeapp.service
[Unit]
Description=Node.js High Availability App
After=network.target nodeapp-storage.mount
[Service]
ExecStart=/usr/bin/node /var/lib/nodeapp/index.js
Restart=always
User=cloud-user
Group=cloud-user
Environment=PATH=/usr/bin:/usr/local/bin
Environment=NODE_ENV=production
WorkingDirectory=/var/lib/nodeapp
[Install]
WantedBy=multi-user.target
EOF
作成が完了したら、以下のコマンドで自動起動の有効化と、Node.jsアプリを起動します。
## Systemdの設定を再読み込みする
[cloud-user@rhel9-ha1 ~]$ sudo systemctl daemon-reload
## 自動起動の有効化
[cloud-user@rhel9-ha1 ~]$ sudo systemctl enable nodeapp
Created symlink /etc/systemd/system/multi-user.target.wants/nodeapp.service → /etc/systemd/system/nodeapp.service.
## アプリを起動する
[cloud-user@rhel9-ha1 ~]$ sudo systemctl start nodeapp
サンプルアプリの動作確認
curl
コマンドでサンプルアプリケーションにアクセスし、動作確認を行います。
[cloud-user@rhel9-ha1 ~]$ curl http://localhost:3000
<h1>🌐 RHEL HA Node.js Demo</h1>
<h2>🖥️ ホスト名: rhel9-ha1</h2>
<h3>🔢 カウンター値: 1</h3>
アプリが起動していることを確認できました。curl
コマンドを実行するたびに、カウンター値が増えます。
rhel9-ha2に共有ディスクをマウントする
rhel9-ha1の/var/lib/nodeapp
に共有ディスクをマウントし、サンプルアプリケーションを起動できるようにしました。この共有ディスクをrhel9-ha2側の/var/lib/nodeapp
にもマウントします。
以下の操作は、rhel9-ha2側で実施します。
rhel9-ha1で実行した通り、lsblk -f
で共有ディスク「nodeapp-storage」の認識状況を確認してみましょう。
[cloud-user@rhel9-ha2 ~]$ lsblk -f
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
vda
├─vda1
├─vda2 vfat FAT16 7B77-95E7 192.8M 4% /boot/efi
├─vda3 xfs boot a53019c4-0c14-499f-b4aa-10f62413b407 786.7M 18% /boot
└─vda4 xfs root 4b34029a-2c52-4051-bb74-af43d491bc74 26.7G 7% /
vdb iso9660 Joliet Extension cidata 2025-02-28-06-56-45-00
vdc
おや?vdcの欄が空です。root権限でpartprobe
コマンドを実行すると、デバイスの再読み込みが可能です。再読み込み後、もう一度lsblk -f
コマンドでデバイスを確認しましょう。
[cloud-user@rhel9-ha2 ~]$ sudo partprobe
[cloud-user@rhel9-ha2 ~]$ lsblk -f
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
vda
├─vda1
├─vda2 vfat FAT16 7B77-95E7 192.8M 4% /boot/efi
├─vda3 xfs boot a53019c4-0c14-499f-b4aa-10f62413b407 786.7M 18% /boot
└─vda4 xfs root 4b34029a-2c52-4051-bb74-af43d491bc74 26.7G 7% /
vdb iso9660 Joliet Extension cidata 2025-02-28-06-56-45-00
vdc ext4 1.0 <FILESYSTEM_UUID>
先ほどrhel9-ha1で作成したファイルシステムがrhel9-ha2でも認識されたので、rhel9-ha2側でもvar/lib/nodeapp
にマウントします。
[cloud-user@rhel9-ha2 ~]$ sudo mount /dev/vdc /var/lib/nodeapp
[cloud-user@rhel9-ha2 ~]$ mount | grep /var/lib/nodeapp
/dev/vdc on /var/lib/nodeapp type ext4 (rw,relatime,seclabel)
rhel9-ha1側で作成したNode.jsアプリケーションのソースを確認します。
[cloud-user@rhel9-ha2 ~]$ ls -l /var/lib/nodeapp
total 176
-rw-r--r--. 1 cloud-user cloud-user 8192 Feb 28 03:52 data.db
-rw-r--r--. 1 root root 1482 Feb 28 03:50 index.js
drwx------. 2 root root 16384 Feb 28 02:56 lost+found
drwxr-xr-x. 183 cloud-user cloud-user 4096 Feb 28 03:23 node_modules
-rw-r--r--. 1 cloud-user cloud-user 142160 Feb 28 03:23 package-lock.json
-rw-r--r--. 1 cloud-user cloud-user 296 Feb 28 03:23 package.json
無事、rhel9-ha1 / rhel9-ha2間でディスクが共有され、アプリケーションのソースを両仮想マシンから確認できました。Kubernetesの永続ボリューム要求(PersistentVolumeClaim, PVC)を活用すれば、こんな簡単に仮想マシン間のディスク共有が設定できてしまいます。
rhel9-ha2側でもSystemdサービス登録
rhel9-ha1と同様に以下コマンドでSystemdサービスとして登録し、仮想マシン起動時にNode.jsアプリが自動起動するようにしましょう。
[cloud-user@rhel9-ha2 ~]$ cat <<EOF | sudo tee /etc/systemd/system/nodeapp.service
[Unit]
Description=Node.js High Availability App
After=network.target nodeapp-storage.mount
[Service]
ExecStart=/usr/bin/node /var/lib/nodeapp/index.js
Restart=always
User=cloud-user
Group=cloud-user
Environment=PATH=/usr/bin:/usr/local/bin
Environment=NODE_ENV=production
WorkingDirectory=/var/lib/nodeapp
[Install]
WantedBy=multi-user.target
EOF
[cloud-user@rhel9-ha2 ~]$ sudo systemctl daemon-reload
[cloud-user@rhel9-ha2 ~]$ sudo systemctl enable nodeapp
Created symlink /etc/systemd/system/multi-user.target.wants/nodeapp.service → /etc/systemd/system/nodeapp.service.
さて、共有ディスクのマウントが確認できたら、一度両仮想マシンからアンマウントしておきます。
[cloud-user@rhel9-ha1 ~]$ sudo systemctl stop nodeapp
[cloud-user@rhel9-ha1 ~]$ sudo umount /var/lib/nodeapp
[cloud-user@rhel9-ha2 ~]$ sudo umount /var/lib/nodeapp
Peacemakerリソースを作成する
つづいて、Node.jsアプリケーション及び、共有ディスクのマウントに関して、それぞれをPacemakerのリソースとして登録します。以下の作業もどちらの仮想マシンで実行指定も構いませんが、今回はrhel9-ha1上で実行します。
デバイスのマウント管理
まずは、特定のデバイス(dev/vdc
)を指定したディレクトリ(/var/lib/nodeapp
)にマウントする設定をPacemakerリソースとして登録し、Pacemakerの管理配下とします。
[cloud-user@rhel9-ha1 ~]$ sudo pcs resource create nodeapp-storage ocf:heartbeat:Filesystem \
device="/dev/vdc" directory="/var/lib/nodeapp" fstype="ext4" \
--group nodeapp-group
将来的に--group
というオプションが廃止される旨の警告が表示されますが、今回は気にせず進めて大丈夫です。
Deprecation Warning: Using '--group' is deprecated and will be replaced with 'group' in a future release. Specify --future to switch to the future behavior.
上記コマンドでは、「nodeapp-storage」という名称でPacemakerリソースを作成します。今回は、クラスタ管理用のリソースエージェントの標準仕様であるOCF(Open Cluster Framework)のFilesystemリソースエージェントを使用し、マウント設定をPacemakerに管理させます。これにより、フェイルオーバ時に切り替え先の仮想マシンにおいて、自動的に共有ディスクがdev/vdc
にマウントされます。
以下のコマンドで自動起動の有効化しておきます。
[cloud-user@rhel9-ha1 ~]$ sudo pcs resource enable nodeapp-storage
Systemdサービスの管理
次に、Systemdサービスとして登録したnodeappを、Pacemakerリソースとして登録します。
[cloud-user@rhel9-ha1 ~]$ sudo pcs resource create nodeapp systemd:nodeapp --group nodeapp-group
このコマンドでは「nodeapp」という名称でPacemakerリソースを作成し、systemctl を使って nodeapp.service を制御させる設定を行いました。また、nodeapp-group というグループに当該リソースを追加しました。これにより、同じグループに属するPacemakerリソースは、必ず同じ仮想マシン上で動作します。
nodeapp-storageと同様、自動起動の有効化しておきます。
[cloud-user@rhel9-ha1 ~]$ sudo pcs resource enable nodeapp
Pacemakerリソース「nodeapp」を起動するには、dev/vdc
にアタッチされている共有ディスク「nodeapp-storage」が/var/lib/nodeapp
にマウントされている必要があります。つまり、Pacemakerリソース「nodeapp-storage」が先に起動していないと、nodeappが起動できません。そのため、起動順を明示的に設定する必要があります。同じグループ内のPacemakerリソースは上から順に起動されますので、必ず
Full List of Resources:
* Resource Group: nodeapp-group:
* nodeapp-storage (ocf:heartbeat:Filesystem): Started rhel9-ha1
* nodeapp (systemd:nodeapp): Started rhel9-ha1
の順番(nodeapp-storage → nodeapp)で登録されているか確認しましょう。なお、同じグループ内のPacemakerリソースの並び順は作成順で決定されます。
Pacemakerリソースの状態確認
さて、これでNode.jsアプリケーションの自動起動及び、共有ディスクのマウントがPacemakerリソースとして管理されるようになりました。再度以下のコマンドでHAクラスタの設定状況を確認しておきましょう。
[cloud-user@rhel9-ha1 ~]$ sudo pcs status
Cluster name: my_cluster
Cluster Summary:
* Stack: corosync (Pacemaker is running)
* Current DC: rhel9-ha1 (version 2.1.8-3.el9-3980678f0) - partition with quorum
* Last updated: Sun Mar 2 20:10:46 2025 on rhel9-ha1
* Last change: Sun Mar 2 20:10:36 2025 by root via root on rhel9-ha1
* 2 nodes configured
* 2 resource instances configured
Node List:
* Online: [ rhel9-ha1 rhel9-ha2 ]
Full List of Resources:
* Resource Group: nodeapp-group:
* nodeapp-storage (ocf:heartbeat:Filesystem): Started rhel9-ha1
* nodeapp (systemd:nodeapp): Started rhel9-ha1
Daemon Status:
corosync: active/enabled
pacemaker: active/enabled
pcsd: active/enabled
「nodeapp-group」に属するPacemakerリソース(nodeapp-storage, nodeapp)がrhel9-ha1上で起動していることを確認できました。
サンプルアプリケーションをインターネットに公開
それでは、仮想マシン上で起動しているNode.jsアプリケーションをインターネットに公開します。そのためにService及びRouteを利用します。
以下のマニフェストをそれぞれOpenShiftクラスタに適用し、リソースを作成しましょう。
kind: Service
apiVersion: v1
metadata:
name: service-ha
namespace: rhel-ha
spec:
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: ClusterIP
selector:
cluster: my_cluster
これにより、Podのラベルにcluster: my_cluster
がついた仮想マシンに対するサービスディスカバリ(通信窓口)が作成され、OpenShiftクラスタ内からアクセスできるようになります。
kind: Route
apiVersion: route.openshift.io/v1
metadata:
name: route-ha
namespace: rhel-ha
spec:
to:
kind: Service
name: service-ha
port:
targetPort: 3000
Routeによって、クラスタ内の通信窓口をインターネットに公開します。それでは、PCのブラウザにRouteのURLを入力して、アプリケーションにアクセスしてみましょう。
サンプルアプリにアクセスできました。このアプリがrhel9-ha1上で動作していることも分かります。
フェイルオーバしてみる
今から、冒頭に示した以下3つのフェイルオーバを実験してみます。
- rhel9-ha1のNode.jsプロセスをKill(強制終了)した場合、rhel9-ha1上で速やかにプロセスが再起動するか?
- rhel9-ha1を停止(Stop)した場合、速やかにrhel9-ha2にてアプリの動作が引き継がれるか?
- rhel9-ha1がスケジュールされているノードをシャットダウンした際に、速やかにrhel9-ha2にてアプリの動作が引き継がれるか?
プロセスをKill
Node.jsプロセスをKill(強制終了)させます。以下のコマンドでまずはindex.jsを起動しているプロセスIDを確認します。
[cloud-user@rhel9-ha1 ~]$ ps aux | grep node
cloud-u+ 20635 0.0 3.6 848148 62424 ? Ssl 20:10 0:00 /usr/bin/node /var/lib/nodeapp/index.js
cloud-u+ 23849 0.0 0.1 3876 2176 pts/0 S+ 20:21 0:00 grep --color=auto node
この結果から、プロセスID=20635でアプリが起動していることが確認できます。なお、プロセスIDは環境毎に異なりますので、ご自身の環境で確認したプロセスIDを使い、以下のコマンドでプロセスを強制終了させましょう。
sudo kill 20635
サンプルアプリケーションを更新してみてください。
何事もなかったように、アプリが起動し続けていますね。ジャーナルログを確認して、再起動した証拠を確認しておきます。
[cloud-user@rhel9-ha1 ~]$ sudo journalctl -u nodeapp --no-pager --lines=4
Mar 02 20:21:34 rhel9-ha1 systemd[1]: nodeapp.service: Deactivated successfully.
Mar 02 20:21:41 rhel9-ha1 systemd[1]: Started Cluster Controlled nodeapp.
Mar 02 20:21:41 rhel9-ha1 node[24009]: 🚀 サーバー起動: http://localhost:3000
Mar 02 20:21:41 rhel9-ha1 node[24009]: ✅ SQLite データベースに接続しまし
Node.jsアプリが停止(Stopped)した後、すぐに再起動(Started)していることが確認できます。再度ps
コマンドを使うことで、再起動はプロセスIDが変わっていることも確認できます。
[cloud-user@rhel9-ha1 ~]$ ps aux | grep node
cloud-u+ 24009 1.0 3.6 848148 62396 ? Ssl 20:21 0:00 /usr/bin/node /var/lib/nodeapp/index.js
cloud-u+ 24222 0.0 0.1 3876 2176 pts/0 S+ 20:22 0:00 grep --color=auto node
ご自身の環境でもプロセスIDが変化したことを確認してください。また、sudo pcs status
コマンドにて、HAクラスタの状況を確認します。
[cloud-user@rhel9-ha1 ~]$ sudo pcs status
Cluster name: my_cluster
Cluster Summary:
* Stack: corosync (Pacemaker is running)
* Current DC: rhel9-ha1 (version 2.1.8-3.el9-3980678f0) - partition with quorum
* Last updated: Sun Mar 2 20:22:17 2025 on rhel9-ha1
* Last change: Sun Mar 2 20:10:36 2025 by root via root on rhel9-ha1
* 2 nodes configured
* 2 resource instances configured
Node List:
* Online: [ rhel9-ha1 rhel9-ha2 ]
Full List of Resources:
* Resource Group: nodeapp-group:
* nodeapp-storage (ocf:heartbeat:Filesystem): Started rhel9-ha1
* nodeapp (systemd:nodeapp): Started rhel9-ha1
Failed Resource Actions:
* nodeapp 1m-interval monitor on rhel9-ha1 returned 'not running' (inactive) at Sun Mar 2 20:21:39 2025
Daemon Status:
corosync: active/enabled
pacemaker: active/enabled
pcsd: active/enabled
nodeapp-groupに属するPacemakerリソース(nodeapp-storage、nodeapp)が、変わらずrhel9-ha1上で動作していることが確認できます。なお、Failed Resource Actions:
に以下のようなログが出力されていることが確認できます。
...
Failed Resource Actions:
* nodeapp 1m-interval monitor on rhel9-ha1 returned 'not running' (inactive) at Sun Mar 2 20:21:39 2025
...
これは、Pacemakerがは1m-interval monitor(60秒間隔)でリソースの状態を監視しており、そのタイミングでnot running
を確認したという趣旨のログです。このように、Systemdサービス「nodeapp」がPacemakerリソースとして管理され、何らかの異常なプロセス終了に対して、即時にプロセス再起動を行っていることが確認できました。
OSを停止(シャットダウン)する
それでは、rhel9-ha1を停止し、Node.jsアプリがrhel9-ha2にフェイルオーバするかを確認してみます。OpenShiftのコンソール画面にて、rhel9-ha1を停止(Stop)してみましょう。
PCのターミナルでrhel9-ha1にSSH接続していたセッションが切れます。サンプルアプリケーションを更新すると、ホスト名が「rhel9-ha2」に変わっています。
しかし、直前のカウンター値+1がカウンター値に表示され、ステートはそのまま引き継がれていることが確認できます。まだSSH接続が生きているrhel9-ha2の方から、sudo pcs statsu
コマンドで状況を確認してみてください。
[cloud-user@rhel9-ha2 ~]$ sudo pcs status
Cluster name: my_cluster
Cluster Summary:
* Stack: corosync (Pacemaker is running)
* Current DC: rhel9-ha2 (version 2.1.8-3.el9-3980678f0) - partition with quorum
* Last updated: Sun Mar 2 20:40:33 2025 on rhel9-ha2
* Last change: Sun Mar 2 20:10:36 2025 by root via root on rhel9-ha1
* 2 nodes configured
* 2 resource instances configured
Node List:
* Online: [ rhel9-ha2 ]
* OFFLINE: [ rhel9-ha1 ]
Full List of Resources:
* Resource Group: nodeapp-group:
* nodeapp-storage (ocf:heartbeat:Filesystem): Started rhel9-ha2
* nodeapp (systemd:nodeapp): Started rhel9-ha2
Daemon Status:
corosync: active/enabled
pacemaker: active/enabled
pcsd: active/enabled
rhel9-ha1がオフラインになり、Pacemakerリソースがrhel9-ha2上で実行されていることが確認できました。rhel9-ha1が何らかの原因で停止(シャットダウン)してしまっても、アプリケーションはrhel-ha2上にフェイルオーバされて継続動作している旨を確認できました。
rhel9-ha1を再起動(Start)させます。
rhel9-ha1が完全に再起動したらSSHで再接続しておきます。rhel9-ha1側からもう一度sudo pcs status
を実行すると、rhel9-ha1がオンラインに戻り、Pacemakerリソースは引き続きrhel9-ha2側で実行されていることが確認できます。
[cloud-user@rhel9-ha1 ~]$ sudo pcs status
Cluster name: my_cluster
...
Node List:
* Online: [ rhel9-ha1 rhel9-ha2 ]
Full List of Resources:
* Resource Group: nodeapp-group:
* nodeapp-storage (ocf:heartbeat:Filesystem): Started rhel9-ha2
* nodeapp (systemd:nodeapp): Started rhel9-ha2
...
ノード障害を再現する
最後に、アプリケーションが動作している仮想マシンがスケジュールされているノード(ホストマシン)が停止した場合の挙動を確認しておきます。
まずは、現在アプリケーションが動作している仮想マシン(rhel9-ha2)がスケジュールされているノードを確認しておきます。以下のコマンドで確認できます。
User@localmachine ~ % oc get vmi -n rhel-ha -o wide
NAME AGE PHASE IP NODENAME READY LIVE-MIGRATABLE PAUSED
rhel9-ha1 9m40s Running 10.130.2.118 ip-10-0-19-3.ap-northeast-1.compute.internal True True
rhel9-ha2 129m Running 10.131.2.15 ip-10-0-3-184.ap-northeast-1.compute.internal True True
oomuramaki@Masakis-MacBook-Air15 ~ %
また、OpenShiftのコンソールからも簡単に確認できます。
では、AWSのマネジメントコンソールのEC2メニューから、rhel9-ha2がスケジュールされているインスタンスを停止してみましょう。
インスタンスが終了するまではアプリケーションが動作し続けてますが、
インスタンスの停止が完了すると、アプリにアクセスできなくなります。
この時、OpenShiftのコンソール上ではrhel9-ha2がスケジュールされていたノードがNotReady
となります。
アプリケーションの画面を更新すると、再びrhel9-ha1側にアプリケーションが戻ってきており、正しくフェイルオーバされたことが確認できます。
カウンター値も引き継がれています。最後にsudo pcs status
で状況を確認しておきます。
[cloud-user@rhel9-ha1 ~]$ sudo pcs status
Cluster name: my_cluster
...
Node List:
* Online: [ rhel9-ha1 ]
* OFFLINE: [ rhel9-ha2 ]
Full List of Resources:
* Resource Group: nodeapp-group:
* nodeapp-storage (ocf:heartbeat:Filesystem): Started rhel9-ha1
* nodeapp (systemd:nodeapp): Started rhel9-ha1
...
rhel9-ha2はスケジュールされていたノードが落ちたことに伴いオフラインになりました。Pacemakerリソースがrhel9-ha1側で起動していることが確認でき、物理ホスト障害を起因としたフェイルオーバが動作していることが見て取れました。
おわりに
OpenShift Virtualization上で起動しているRHEL仮想マシンのHAクラスタリングを試しました。OpenShift Virtualizationの特徴である「RHEL仮想マシン建て放題」を活用すれば、これまでは「本当はOSレベルのHAクラスタリングをしたいけど、RHELのサブスクリプションを追加で購入するのはちょっとなあ...」と感じていた方々のハードルを下げてくれると思います。
また、共有ディスクや共有ストレージの管理がセルフサービス化され、開発者側でHAクラスタを組む仮想マシンといっしょに共有ストレージもプロビジョニングできるようになります。これにより、よりHA Add-on利用のハードルも下がるのではないでしょうか!?