1、简介
Pacemaker是Linux环境中使用非常广泛的集群资源管理软件,通过利用corosync/heartbeat等消息和集群管理功能来实现对资源的创建和管理。pacemaker自带不少优秀的资源管理脚本可用,但是大多数场景需要根据自己的业务场景来实现符合业务特性的资源代理脚本。资源代理脚本编写基于一种叫做OCF(Open Cluster Framework)的规范,下面将介绍这种规范部分要求。
1.1、什么是资源代理
资源代理是管理群集资源的可执行文件。集群管理的任何东西都是资源,不存在群集资源的正式定义。群集资源可以多种多样,如 IP 地址、文件系统、数据库服务和整个虚拟机。
1.2、资源代理编写语言
符合OCF标准的资源代理可以用任何编程语言实现。它不是基于特定于语言的。但是大多数资源代理都是用shell 脚本实现的,因此本文也使用 shell 语言编写示例代码。
2、接口定义
2.1、环境变量
资源代理通过环境变量接收有关其管理的资源的所有配置信息,这些环境变量的名称就是资源参数的名称,它的前缀为OCF_RESKEY_。例如要把资源的参数ip设置为192.168.1.21,那么就用OCF_RESKEY_ip保存这个ip的值。
如果用户不需要设置任何资源参数,那么required参数必须填写为true,即required="true"。按照约定,默认的参数变量命名需按OCF_RESKEY_<parametername>_default这种格式。
2.2、脚本可执行操作
脚本必须包含可执行如下4中操作:
- start— 启动资源。
- stop— 关闭资源。
- monitor— 查询资源的状态。
- meta-data— 转储资源代理元数据。
此外,资源代理可以选择支持以下操作:
- promote— 将资源转换为主角色(仅限主/从资源)。Master
- demote— 将资源转换为从角色(仅限主/从资源)。Slave
- migrate_to和migrate_from— 实现资源的实时迁移。
- validate-all— 验证资源的配置。
- usage或help— 显示帮助信息。
- status— 已弃用,同monitor
2.3、超时
超时是指资源代理程序强制实施操作设定的超时时间。集群管理器负责监视资源代理操作已运行的时间,并在未达到其完成截止时间时终止该操作。因此,资源代理本身无需检查是否有任何超时过期。但是,资源代理可以向用户建议合理的超时值(如果设置正确,群集管理器将适当地强制执行这些操作)。
2.4、元数据
每个资源代理都必须在一组 XML 元数据中描述自己的用途和支持的参数。此元数据由集群管理应用程序用于联机帮助,并且资源代理手册页也从这个xml中生成。以下是来自虚构资源代理的一组虚构元数据:
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="foobar" version="0.1">
<version>0.1</version>
<longdesc lang="en">
This is a fictitious example resource agent written for the
OCF Resource Agent Developers Guide.
</longdesc>
<shortdesc lang="en">Example resource agent
for budding OCF RA developers</shortdesc>
<parameters>
<parameter name="eggs" unique="0" required="1">
<longdesc lang="en">
Number of eggs, an example numeric parameter
</longdesc>
<shortdesc lang="en">Number of eggs</shortdesc>
<content type="integer"/>
</parameter>
<parameter name="superfrobnicate" unique="0" required="0">
<longdesc lang="en">
Enable superfrobnication, an example boolean parameter
</longdesc>
<shortdesc lang="en">Enable superfrobnication</shortdesc>
<content type="boolean" default="false"/>
</parameter>
<parameter name="datadir" unique="0" required="1">
<longdesc lang="en">
Data directory, an example string parameter
</longdesc>
<shortdesc lang="en">Data directory</shortdesc>
<content type="string"/>
</parameter>
</parameters>
<actions>
<action name="start" timeout="20" />
<action name="stop" timeout="20" />
<action name="monitor" timeout="20"
interval="10" depth="0" />
<action name="reload" timeout="20" />
<action name="migrate_to" timeout="20" />
<action name="migrate_from" timeout="20" />
任何资源代理都必须定义name和version,关于资源的描述包含两部分,其中longdesc指的是详细描述,shortdesc是简短描述。
required指示设置参数是必需的 (required="0") 还是可选的 (required="0")。
unique(允许的值:0或1) 表明该值在整个集群中必须是唯一的。例如声明一个高度可用的vip地址,这个ip地址就是不能重复的。
content type描述参数类型,如string,integer,boolean默认的type是string。
每个actions都有自己的超时时间,需要设置最低的timeout时间。
2.5、返回码
ocf有严格的返回码规范,其返回码如下表格中所示。
返回码 |
标签 |
描述 |
0 |
OCF_SUCCESS |
该操作成功完成。这是任何成功启动、停止、提升和降级命令的预期返回代码。 如果意外: soft 则键入 |
1 |
OCF_ERR_GENERIC |
该操作返回一个通用错误。 类型:软 资源管理器将尝试恢复资源或将其移动到新位置。 |
2 |
OCF_ERR_ARGS |
资源的配置在此计算机上无效。例如,它引用节点上未找到的位置。 类型: hard 资源管理器将在其他位置移动资源,并阻止其在当前节点上重试 |
3 |
OCF_ERR_UNIMPLEMENTED |
请求的操作未实施。 类型: hard |
4 |
OCF_ERR_PERM |
资源代理没有足够的特权来完成该任务。这可能是因为代理无法打开特定文件、侦听特定套接字或写入目录。 类型: hard 除非另有特殊配置,否则资源管理器将通过在其他节点上重启资源来尝试恢复出错的资源(其中权限问题可能不存在)。 |
5 |
OCF_ERR_INSTALLED |
执行该操作的节点上缺少所需的组件。这可能是因为所需的二进制文件不可执行,或者重要配置文件不可读取。 类型: hard 除非另有特殊配置,否则资源管理器将尝试通过在其他节点上重启资源(可能存在所需的文件或二进制文件)来恢复发生此错误的资源。 |
6 |
OCF_ERR_CONFIGURED |
本地节点上的资源配置无效。 类型:fatal 当返回此代码时,Pacemaker 将阻止资源在集群中的任何节点上运行,即使服务配置在某些其他节点上有效。 |
7 |
OCF_NOT_RUNNING |
资源已被安全停止。这意味着资源已正常关闭,或者从未启动。 类型: soft 对于任何操作,集群不会尝试停止返回此值的资源。 |
8 |
OCF_RUNNING_MASTER |
资源在 master 模式下运行。 类型: soft |
9 |
OCF_FAILED_MASTER |
资源处于 master 模式,但失败。 类型:soft 资源将被降级、停止,然后再次启动(可能升级)。 |
关于错误类型解释如下表:
类型 |
描述 |
集群操作 |
soft |
发生瞬态错误. |
重新启动资源 或将其移到新位置。 |
hard |
发生非临时错误,可能特定于当前节点。 |
将资源移到其他位置,并阻止其在当前节点上重试。 |
fatal |
发生非临时错误,适用于所有集群节点(例如,指定了一个错误的配置)。 |
停止资源,并阻止其在任何群集节点上启动。 |
3、资源代理编写结构
本次使用典型的shell标准结构,其结构如下。
3.1、资源代理解释器
作为脚本实现的任何资源代理都必须使用标准的“shebang”(#!) 标头语法指定其解释器。
#!/bin/sh
3.2、作者和许可信息
#
# Resource Agent for managing foobar resources.
#
# License: GNU General Public License (GPL)
# (c) 2023-2030 Jinsong Hu, Lei Ren
#
3.3、初始化
任何 shell 资源代理都应获取函数库,按照如下语法添加头文件:
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
3.4、常用执行操作
这是调用资源代理时实际执行的资源代理的一部分。它通常遵循如下标准的结构:
# Make sure meta-data and usage always succeed
case $__OCF_ACTION in
meta-data) foobar_meta_data
exit $OCF_SUCCESS
;;
usage|help) foobar_usage
exit $OCF_SUCCESS
;;
esac
3.4.1、start
使用start操作调用资源时,如果资源尚未运行,则资源代理必须启动该资源。这意味着代理必须验证资源的配置,查询其状态,然后仅在资源未运行时启动它。
3.4.1、stop
使用stop操作调用时,资源代理必须停止资源(如果资源正在运行)。这意味着代理必须验证资源配置,查询其状态,然后仅在当前正在运行时才停止它。
3.4.1、monitor
使用monitor操作查询资源的当前状态。monitor按照如下三种状态返回对应的错误码
- 资源当前正在运行(返回$OCF_SUCCESS);
- 资源已正常停止(返回$OCF_NOT_RUNNING);
- 资源遇到问题且必须返回失败(返回相应的代码以指示问题的性质)。$OCF_ERR_code
3.4.1、validate-all
该操作将测试资源代理程序是否正常。 应使用以下返回代码之一退出:
- $OCF_SUCCESS— 一切顺利,配置有效且可用。
- $OCF_ERR_CONFIGURED— 用户错误地配置了资源。
- $OCF_ERR_INSTALLED— 资源可能已正确配置,但正在执行的节点上缺少重要组件。
- $OCF_ERR_PERM— 资源配置正确,没有缺少任何必需的组件,但遇到了权限问题(如无法创建必要的文件)。
validate-all通常包装在一个函数中,该函数不仅在显式调用相应操作时调用,而且作为健全性检查,可从任何其他函数调用。因此,资源代理可以在start,stop,monitor操作期间以及在探测期间调用该函数。
3.4.1、meta-data
该操作将资源代理元数据转储到标准输出。
4. 脚本变量
本节概述了通常可供资源代理使用的变量。
4.1.$OCF_ROOT
OCF 资源代理层次结构的根。资源代理永远不应更改此设置。这通常是/usr/lib/ocf。
4.2.$OCF_FUNCTIONS_DIR
资源代理依赖函数库所在的目录,不能更改。.ocf-shellfuncs通常定义在$OCF_ROOT后面。
4.3.$OCF_RESOURCE_INSTANCE
资源实例名称。对于原始(非克隆、无状态)资源,这只是资源名称。对于克隆和有状态资源,这就是后跟冒号和克隆实例编号的原始名称,(例如p_foobar:0)。
4.4.$__OCF_ACTION
当前调用的操作。这是集群管理器在调用资源代理程序时指定的第一个命令行参数。
4.5.$__SCRIPT_NAME
资源代理的名称。这是删除了前导目录名称的资源代理脚本基本名称,。
4.6.$HA_RSCTMP
供资源代理使用的临时目录。系统保证在系统启动时清空此目录,因此在节点重新启动后,此目录将不包含任何数据。
5、项目实践操作实例
以下是基于nfs的shell版本的缩减ocf脚本:
#!/bin/sh
#
# Resource Agent for managing foobar resources.
#
# License: GNU General Public License (GPL)
# (c) 2023-2030 Jinsong Hu, Lei Ren
#
#######################################################################
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
#. ${OCF_FUNCTIONS_DIR}/findif.sh
OCF_RESKEY_sfs_default="agent"
SFS_LOG_PATH="/var/log/cstor/"
RAAGENTLOG=$SFS_LOG_PATH/ra_agent.log
COMMAND=$1
statefile="/run/sfs.active"
ADMIN_IP=$(cat /opt/consts.py | grep AGENT_ADMIN_IP | awk '{print $3}')
meta_data() {
cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="cstor_agent" version="0.1">
<version>0.1</version>
<longdesc lang="en">
This is for cstor agent resource manage.
</longdesc>
<shortdesc lang="en">cstor agent resource</shortdesc>
<parameters>
<parameter name="agent_vip" unique="1" required="1">
<longdesc lang="en">
cstor ha business vip
</longdesc>
<shortdesc lang="en">business ha</shortdesc>
<content type="string"/>
</parameter>
<parameter name="mgr_vip" unique="1">
<longdesc lang="en">
cstor ha mgr vip
</longdesc>
<shortdesc lang="en">mgr vip</shortdesc>
<content type="string"/>
</parameter>
</parameters>
<actions>
<action name="start" timeout="180" />
<action name="stop" timeout="180" />
<action name="monitor" timeout="180"
interval="10" depth="0" />
<action name="meta-data" timeout="5" />
<action name="validate-all" timeout="30" />
</actions>
</resource-agent>
END
exit $OCF_SUCCESS
}
nfs_monitor ()
{
i_nfs=1
while [ $i_nfs -le 3 ]; do
nfs_status=$(systemctl status nfs | grep -c 'active (exited)')
nfsd_num=$(ps -ef | grep -w nfsd | grep -v grep | wc -l)
if [[ $nfs_status == 0 ]] || [[ $nfsd_num == 0 ]]; then
systemctl restart nfs
sleep 1
# return $OCF_NOT_RUNNING
else
return $OCF_SUCCESS
fi
let i_nfs=$i_nfs+1
done
return $OCF_NOT_RUNNING
}
nfs_start ()
{
systemctl start nfs
if [ $? -eq 0 ]; then
ocf_log debug "===success to start nfs==="
return $OCF_SUCCESS
else
ocf_log err "===fail to start nfs==="
return $OCF_ERR_GENERIC
fi
}
nfs_stop()
{
systemctl start stop
if [ $? -eq 0 ]; then
ocf_log debug "===success to start nfs==="
return $OCF_SUCCESS
else
ocf_log err "===fail to start nfs==="
return $OCF_ERR_GENERIC
fi
}
monitor_sfs() {
if ! [ -e "$statefile" ]; then
ocf_log info "===${OCF_RESKEY_agent_vip} agent not active on this node; skipping 13-agent check==="
return $OCF_NOT_RUNNING
fi
nfs_monitor ()
if [ $? -eq 0 ]; then
ocf_log debug "===service(nfs/cifs/ftp) normal==="
else
ocf_log err "===start service(nfs/cifs/ftp) error==="
rm -rf "$statefile"
return $OCF_ERR_GENERIC
fi
return $OCF_SUCCESS
}
start_sfs() {
nfs_start
if [ $? -eq 0 ]; then
ocf_log debug "===service(nfs/cifs/ftp) normal==="
else
ocf_log err "===start service(nfs/cifs/ftp) error==="
rm -rf "$statefile"
return $OCF_ERR_GENERIC
fi
return $OCF_SUCCESS
}
stop_sfs() {
nfs_stop
if [ $? -eq 0 ]; then
ocf_log debug "===service(nfs/cifs/ftp) normal==="
else
ocf_log err "===start service(nfs/cifs/ftp) error==="
rm -rf "$statefile"
return $OCF_ERR_GENERIC
fi
return $OCF_SUCCESS
}
case $COMMAND in
meta-data)
meta_data
;;
start)
start_sfs
;;
stop)
stop_sfs
;;
status)
;;
monitor)
monitor_sfs
;;
validate-all)
;;
*)
;;
esac