前置き
この記事はIBM CloudのVirtual Server for VPCでRed Hat High Availability Clusterを構成するの補足記事として作成したものです。背景の理解のためにあらかじめ上記の記事をお読みいただくことをおすすめします。
はじめに
Pacemakerには、リソースエージェントと呼ばれるスクリプトが同梱されています。これは、Pacemakerが起動や停止といった対象のリソースへの操作を行うために実行されるスクリプトです。管理対象として様々なサービスに対応したリソースエージェントがあらかじめ用意されていますが、このリソースエージェントは必要に応じて自分で作成することも可能であり、クラスターの管理対象のサービスとして追加することが可能です。
そこで、この記事ではIBM CloudのRIP(Reserved IP)と呼ばれるプライベートIPアドレスを管理するためのリソースエージェントを作成し、Red Hat High Availability Clusterの管理下に置くことが目標です。また、上記を達成することで、フェイルオーバー時に自動的にRIPをパッシブノード側の仮想サーバーに付与する構成を実現したいと思います。
参考文献
作成にあたって参考にさせていただいたのが、公式ドキュメントであるOCFリソースエージェント開発者ガイドの日本語版です。基本的にはこちらのドキュメントに記載されている通りに記述すれば問題ありません。
スクリプトの作成
#!/bin/sh
#
#   Resource Agent for IBM Cloud resources.
#
#   Copyright (c) 2024 Yuki Tada
#
#Initialize
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/resource.d/heartbeat}
. ${OCF_FUNCTIONS_DIR}/.ocf-shellfuncs
#Environment variables
host_name=`hostname`
case ${host_name} in
	ytada-rhel-ha-test-1)
		vni_id="xxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
		;;
	ytada-rhel-ha-test-2)
		vni_id="xxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
		;;
esac 
vip_id="xxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
api_key="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
meta_data() {
	cat << EOF
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="secondary-rip-resource" version="1.0">
	<version>1.0</version>
	<longdesc lang="en">This is a Resource Agent to manage IBM Cloud secondary ip address</longdesc>
	<shortdesc lang="en">Resource Agent for IBM Cloud secondary IP</shortdesc>
	<parameters>
	</parameters>
	<actions>
		<action name="meta-data" timeout="60" />
		<action name="start" timeout="60" />
		<action name="stop" timeout="60" />
		<action name="monitor" timeout="60" />
		<action name="validate-all" timeout="60" />
	</actions>
</resource-agent>
EOF
	return $OCF_SUCCESS
}
validate_status(){
	return $OCF_SUCCESS
}
rip_start(){
	rip_monitor
	if [ $? =  $OCF_SUCCESS ]; then
		ocf_log info "Resource is already running"
		return $OCF_SUCCESS
        fi
	
	echo N | ibmcloud login -a cloud.ibm.com --apikey ${api_key} -r jp-tok
	echo y | ibmcloud plugin install vpc-infrastructure
	ibmcloud is virtual-network-interface-reserved-ip-bind ${vni_id} ${vip_id}
	sleep 2
	return $OCF_SUCCESS
}
rip_stop(){
	rip_monitor
	case $? in
		"$OCF_SUCCESS")
			ocf_log debug "Resource is currently running"
			;;
		"$OCF_NOT_RUNNING")
			ocf_log info "Resource is already stopped"
			return $OCF_SUCCESS
			;;
	esac
	
	echo N | ibmcloud login -a cloud.ibm.com --apikey ${api_key} -r jp-tok
	echo y | ibmcloud plugin install vpc-infrastructure
	echo y | ibmcloud is virtual-network-interface-reserved-ip-unbind ${vni_id} ${vip_id}
	sleep 2
	return $OCF_SUCCESS
}
rip_monitor(){
	echo N | ibmcloud login -a cloud.ibm.com --apikey ${api_key} -r jp-tok
	echo y | ibmcloud plugin install vpc-infrastructure
	number_rip=$(ibmcloud is virtual-network-interface-reserved-ips ${vni_id} --output JSON  | jq "length")
	
	if [ ${number_rip} -eq 2 ];
		then
			return $OCF_SUCCESS
		fi
		return $OCF_NOT_RUNNING
}
sample_usage(){
	echo "Resource Agent for IBM Cloud secondary IP"
	return $OCF_SUCCESS
}
# Translate each action into the appropriate function call
case $__OCF_ACTION in
meta-data)	meta_data
		exit $OCF_SUCCESS
		;;
start)		rip_start;;
stop)		rip_stop;;
monitor)	rip_monitor;;
validate-all)	validate_status;;
usage|help)	sample_usage
		exit $OCF_SUCCESS
		;;
esac
解説
それぞれの項目について解説します。
使用言語について
リソースエージェントはどのような言語でも実装可能ですが、基本的にはシェルスクリプトで記述されることが多いようです。今回はシェルスクリプトを利用しています。
#!/bin/sh
初期化
Initialize
必要なライブラリ関数を読み込んでいるとのことです。全てのリソースエージェントで利用するため、必ず必要になります。
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/resource.d/heartbeat}
. ${OCF_FUNCTIONS_DIR}/.ocf-shellfuncs
Environment variables
環境変数を設定しています。API Key等のデータはベタ打ちせず、他のファイルから読み込むなどのセキュリティ対策を講じた方が良いと思います。今回は動作検証のためここで指定しました。
このスクリプトは、クラスター内の全てのノード上に配置されることになります。そのため、ホスト名に合わせて変数の値を変え、後続の処理を実施するように設定しています。
host_name=`hostname`
case ${host_name} in
	ytada-rhel-ha-test-1)
		vni_id="xxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
		;;
	ytada-rhel-ha-test-2)
		vni_id="xxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
		;;
esac 
vip_id="xxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
api_key="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
実行ブロックの定義
リソースエージェントが起動すると、この内容が実行されます。リソースエージェントは実行されるときに引数としてこの実行ブロック中のアクション(start/stop/monitorなど)が指定されます。ここではそれぞれのアクションを作成した関数の呼び出しに変換しています。
# Translate each action into the appropriate function call
case $__OCF_ACTION in
meta-data)	meta_data
		exit $OCF_SUCCESS
		;;
start)		rip_start;;
stop)		rip_stop;;
monitor)	rip_monitor;;
validate-all)	validate_status;;
usage|help)	sample_usage
		exit $OCF_SUCCESS
		;;
esac
下記の4つのアクションは必ずサポートする必要があります。
- start
- リソースを起動します。
 
 - stop
- リソースを停止します。
 
 - monitor
- リソースの状態を問合せします。
 
 - meta-data
- リソースエージェントメタデータをダンプします。
 
 
また、meta-dataとusage|helpにおいては必ず$OCF_SUCCESSを戻り値として返す必要があります。戻り値についてはこちらに記載されています。
アクション
meta-dataアクション
リソースエージェントのメタデータを標準出力へ書き出します。その目的やサポートしているパラメータを一連のXMLメタデータとして記述することができます。なお、タグではそれぞれのアクションのタイムアウト値を指定することができますが、ここでは推奨値が記載できるのみで、実際のタイムアウトの管理はクラスターマネージャー側で実施します。
meta_data() {
	cat << EOF
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="secondary-rip-resource" version="1.0">
	<version>1.0</version>
	<longdesc lang="en">This is a Resource Agent to manage IBM Cloud secondary ip address</longdesc>
	<shortdesc lang="en">Resource Agent for IBM Cloud secondary IP</shortdesc>
	<parameters>
	</parameters>
	<actions>
		<action name="meta-data" timeout="60" />
		<action name="start" timeout="60" />
		<action name="stop" timeout="60" />
		<action name="monitor" timeout="60" />
		<action name="validate-all" timeout="60" />
	</actions>
</resource-agent>
EOF
	return $OCF_SUCCESS
}
validate-allアクション
動作環境がそもそも正しいことを確認します。この関数は明示的に実行されたときだけでなく、start/stop/monitorなどの他のアクションから呼び出されることもあるので、その点を念頭に置いて設計する必要があります。
正しい動作環境にある場合は$OCF_SUCCESSを戻り値として返します。今回は動作検証として一旦$OCF_SUCCESSをそのまま返すようにしましたが、実際はIBM Cloud CLIが導入されているか、IBM Cloudにログインできるか、必要なibmcloudコマンドが実行できるか、APIが正しく実行できるか、といった点をこの関数内で確認するようにするのが良いと思います。
validate_status(){
	return $OCF_SUCCESS
}
startアクション
monitorアクションを実行し、リソースが稼働しているか(2次IPがすでに割り当てられているかどうか)を確認します。割り当てられている場合はそのまま$OCF_SUCCESSを返して関数を終了し、割り当てられていない場合はibmcloudコマンドを利用してあらかじめ予約したVIP用のRIPを自身のサーバーのVNI(仮想ネットワークインターフェース)にバインドします。
rip_start(){
	rip_monitor
	if [ $? =  $OCF_SUCCESS ]; then
		ocf_log info "Resource is already running"
		return $OCF_SUCCESS
        fi
	
	echo N | ibmcloud login -a cloud.ibm.com --apikey ${api_key} -r jp-tok
	echo y | ibmcloud plugin install vpc-infrastructure
	ibmcloud is virtual-network-interface-reserved-ip-bind ${vni_id} ${vip_id}
	sleep 2
	return $OCF_SUCCESS
}
stopアクション
monitorアクションを実行し、リソースの稼働状況を確認します。startアクションと違いcase文にしているのは、戻り値によっては他のアクションを実行する必要があるからです(今回はstatelessリソースですが、statefulリソースの場合にはMaster/Slaveの稼働状態が存在するため、$OCF_RUNNING_MASTERが返ってくる場合があります)。すでにリソースが止まっている場合(2次IPが割り当てられていない場合)にはそのまま$OCF_SUCCESSを返して関数を終了し、リソースが稼働している場合(2次IPが割り当てられている場合)はibmcloudコマンドを利用して割り当てられているVIP用のRIPをアンバインドします。
rip_stop(){
	rip_monitor
	case $? in
		"$OCF_SUCCESS")
			ocf_log debug "Resource is currently running"
			;;
		"$OCF_NOT_RUNNING")
			ocf_log info "Resource is already stopped"
			return $OCF_SUCCESS
			;;
	esac
	
	echo N | ibmcloud login -a cloud.ibm.com --apikey ${api_key} -r jp-tok
	echo y | ibmcloud plugin install vpc-infrastructure
	echo y | ibmcloud is virtual-network-interface-reserved-ip-unbind ${vni_id} ${vip_id}
	sleep 2
	return $OCF_SUCCESS
}
monitorアクション
リソースの稼働状況を確認します。ここでは、自身の仮想サーバーのVNIに対してRIPが付与されている個数をibmcloudコマンドで確認しています。1つならPrimary IPのみ、2つならSecondary IPも付与されているだろう、という単純な確認方法です。
rip_monitor(){
	echo N | ibmcloud login -a cloud.ibm.com --apikey ${api_key} -r jp-tok
	echo y | ibmcloud plugin install vpc-infrastructure
	number_rip=$(ibmcloud is virtual-network-interface-reserved-ips ${vni_id} --output JSON  | jq "length")
	
	if [ ${number_rip} -eq 2 ];
		then
			return $OCF_SUCCESS
		fi
		return $OCF_NOT_RUNNING
}
usageアクション
リソースエージェントの使用方法を記載します。それぞれの実行コマンドの用途などを記載すると良いと思います。
sample_usage(){
	echo "Resource Agent for IBM Cloud secondary IP"
	return $OCF_SUCCESS
}
スクリプトのテスト
スクリプトの作成が完了した後はテストを実行します。作成したスクリプトは/usr/lib/ocf/resource.d/配下に作成者名でフォルダを作成し、格納する必要があります。テストにはocf-testerコマンドを利用します。
ocf-tester -n ibmcloud-secondary-ip /usr/lib/ocf/resource.d/ytada/ibmcloud-secondary-ip
テストに失敗する場合は-dや-v、-Xオプションを付与して実行し、エラーの原因を修正します。/var/log/messagesファイルにもpacemakerのログが出力されますので、確認すると参考になります。
$ ocf-tester -n ibmcloud-secondary-ip /usr/lib/ocf/resource.d/ytada/ibmcloud-secondary-ip
Beginning tests for /usr/lib/ocf/resource.d/ytada/ibmcloud-secondary-ip...
* rc=7: Monitoring an active resource should return 0
* rc=7: Probing an active resource should return 0
* rc=7: Monitoring an active resource should return 0
* rc=7: Monitoring an active resource should return 0
Tests failed: /usr/lib/ocf/resource.d/ytada/ibmcloud-secondary-ip failed 4 tests
全てのテストにパスすれば完了です。
$ ocf-tester -n ibmcloud-secondary-ip /usr/lib/ocf/resource.d/ytada/ibmcloud-secondary-ip
Beginning tests for /usr/lib/ocf/resource.d/ytada/ibmcloud-secondary-ip...
/usr/lib/ocf/resource.d/ytada/ibmcloud-secondary-ip passed all tests
リソースの作成
リソースエージェントを指定し、Pacemakerのリソースを作成します。スクリプトの中ではIBM Cloudにログインしたり、CLIで情報を取得したりといった時間がかかるアクションも存在するため、タイムアウト値を長めに設定しています。
pcs resource create controll_rip ocf:ytada:ibmcloud-secondary-ip op monitor interval=10s timeout=60s op start timeout=60s op stop timeout=60s --before website --group ha_test_group 
正しく作成できているか確認します。
$ pcs status
Cluster name: my_cluster
Cluster Summary:
  * Stack: corosync (Pacemaker is running)
  * Current DC: 10.50.4.15 (version 2.1.7-5.2.el9_4-0f7f88312) - partition with quorum
  * Last updated: Mon Dec  9 01:51:57 2024 on 10.50.4.15
  * Last change:  Mon Dec  9 01:49:31 2024 by root via root on 10.50.4.15
  * 2 nodes configured
  * 5 resource instances configured
Node List:
  * Online: [ 10.50.4.15 10.50.4.16 ]
Full List of Resources:
  * ibmvpcfence	(stonith:fence_ibm_vpc):	 Started 10.50.4.15
  * Resource Group: ha_test_group:
    * fs_nfs	(ocf:heartbeat:Filesystem):	 Started 10.50.4.16
    * vip	(ocf:heartbeat:IPaddr2):	 Started 10.50.4.16
    * controll_rip	(ocf:ytada:ibmcloud-secondary-ip):	 Started 10.50.4.16
    * website	(ocf:heartbeat:apache):	 Started 10.50.4.16
Daemon Status:
  corosync: active/enabled
  pacemaker: active/enabled
  pcsd: active/enabled
設定したVIPで正しく接続できていることを確認します。
$ curl 10.50.4.100
<html>
<body>Hello</body>
</html>