# 20.14.高可用性存储（HAST）

高可用性是严肃商业应用的主要要求之一，而高可用存储是此类环境中的关键组成部分。在 FreeBSD 中，**高可用存储**（HAST）框架允许通过 TCP/IP 网络将相同数据透明地存储在几台物理分离的机器上。HAST 可以理解为基于网络的 RAID1（镜像），类似于 GNU/Linux® 平台上的 DRBD® 存储系统。结合 FreeBSD 的其他高可用性功能，如 CARP，HAST 使得构建一个能够抵御硬件故障的高可用存储集群成为可能。

以下是 HAST 的主要特性：

* 可以用于屏蔽本地硬盘的 I/O 错误。
* 文件系统无关，能够与 FreeBSD 支持的任何文件系统一起工作。
* 高效且快速的重新同步，只同步节点停机期间被修改的块。
* 可以在已经部署的环境中添加额外的冗余。
* 与 CARP、Heartbeat 或其他工具结合使用，可以构建一个强大且耐用的存储系统。

阅读完本节后，你将了解：

* 什么是 HAST，它是如何工作的，提供了哪些功能。
* 如何在 FreeBSD 上设置和使用 HAST。
* 如何集成 CARP 和 **devd(8)** 来构建一个强大的存储系统。

在阅读本节之前，你应该：

* 理解 UNIX® 和 FreeBSD 基础知识（[FreeBSD 基础](https://docs.freebsd.org/en/books/handbook/basics/#basics)）。
* 知道如何配置网络接口和其他核心 FreeBSD 子系统（[配置与调优](https://docs.freebsd.org/en/books/handbook/config/#config-tuning)）。
* 对 FreeBSD 网络有较好的理解（[网络通信](https://docs.freebsd.org/en/books/handbook/partiv/#network-communication)）。

HAST 项目由 FreeBSD 基金会赞助，并得到了 <http://www.omc.net/> 和 <http://www.transip.nl/> 的支持。

## 20.14.1. HAST 操作

HAST 提供了两台物理机器之间的同步块级复制：主节点和从节点。这两台机器一起被称为集群。

由于 HAST 采用主从配置，它允许在任何给定时间只有一个集群节点处于活动状态。主节点，也称为活动节点，将处理所有针对 HAST 管理设备的 I/O 请求。从节点会自动从主节点同步数据。

HAST 系统的物理组件包括主节点上的本地磁盘和远程从节点上的磁盘。

HAST 在块级上同步操作，使其对文件系统和应用程序透明。HAST 在 **/dev/hast/** 中提供常规 GEOM 提供程序，供其他工具或应用程序使用。使用 HAST 提供的设备与使用原始磁盘或分区没有区别。

每次写入、删除或刷新操作都会同时发送到本地磁盘和远程磁盘，数据通过 TCP/IP 传输。每次读操作默认从本地磁盘提供，除非本地磁盘未更新或发生了 I/O 错误。在这种情况下，读操作将发送到从节点。

HAST 旨在提供快速的故障恢复。因此，在节点故障后减少同步时间非常重要。为了提供快速同步，HAST 管理磁盘上的脏区位图，并且仅同步这些脏区，在常规同步时进行，初始同步除外。

有许多方法可以处理同步。HAST 实现了几种复制模式以处理不同的同步方法：

* **memsync**：此模式在本地写操作完成且远程节点确认数据到达时报告写操作完成，但在实际存储数据之前就报告完成。远程节点上的数据将在收到确认后直接存储。此模式旨在减少延迟，但仍提供良好的可靠性。此模式是默认模式。
* **fullsync**：此模式在本地写操作和远程写操作都完成时报告写操作完成。这是最安全但也是最慢的复制模式。
* **async**：此模式在本地写操作完成时报告写操作完成。这是最快但最危险的复制模式。它仅应在复制到延迟过高的远程节点时使用。

## 20.14.2. HAST 配置

HAST 框架由多个组件组成：

* 提供数据同步的 [hastd(8)](https://man.freebsd.org/cgi/man.cgi?query=hastd\&sektion=8\&format=html) 守护进程。启动此守护进程时，它会自动加载 `geom_gate.ko`。
* 用户空间管理工具 [hastctl(8)](https://man.freebsd.org/cgi/man.cgi?query=hastctl\&sektion=8\&format=html)。
* [hast.conf(5)](https://man.freebsd.org/cgi/man.cgi?query=hast.conf\&sektion=5\&format=html) 配置文件。该文件必须在启动 hastd 之前存在。

希望将 `GEOM_GATE` 支持静态编译到内核中的用户，应在自定义内核配置文件中添加以下行，然后按照 [配置 FreeBSD 内核](https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig) 中的说明重新编译内核：

```ini
options	GEOM_GATE
```

以下示例描述了如何使用 HAST 配置两台节点的主从操作，以便在两台机器之间复制数据。这些节点将被称为 `hasta`，其 IP 地址为 `172.16.0.1`，以及 `hastb`，其 IP 地址为 `172.16.0.2`。两台节点将有一个大小相同的专用硬盘 **/dev/ad6** 用于 HAST 操作。HAST 池，有时称为资源或 GEOM 提供程序，在 **/dev/hast/** 中将被称为 `test`。

HAST 的配置是通过 **/etc/hast.conf** 完成的。该文件在两台节点上应相同。最简单的配置是：

```ini
resource test {
	on hasta {
		local /dev/ad6
		remote 172.16.0.2
	}
	on hastb {
		local /dev/ad6
		remote 172.16.0.1
	}
}
```

有关更高级的配置，请参阅 [hast.conf(5)](https://man.freebsd.org/cgi/man.cgi?query=hast.conf\&sektion=5\&format=html)。

> **技巧**
>
> 如果主机可解析，并且在 **/etc/hosts** 或本地 DNS 中定义，亦可在 `remote` 语句中使用主机名。

配置文件在两台节点上存在后，可以创建 HAST 池。在两台节点上运行以下命令，将初始元数据放置到本地磁盘并启动 [hastd(8)](https://man.freebsd.org/cgi/man.cgi?query=hastd\&sektion=8\&format=html)：

```sh
# hastctl create test
# service hastd onestart
```

> **注意**
>
> *不* 能使用已存在的文件系统与 GEOM 提供程序一起使用，也不能将现有存储转换为 HAST 管理的池。此过程需要在提供程序上存储一些元数据，而现有提供程序上没有足够的空间。

HAST 节点的 `primary` 或 `secondary` 角色由管理员或像 Heartbeat 这样的软件通过 [hastctl(8)](https://man.freebsd.org/cgi/man.cgi?query=hastctl\&sektion=8\&format=html) 选择。在主节点 `hasta` 上，执行以下命令：

```sh
# hastctl role primary test
```

在从节点 `hastb` 上运行此命令：

```sh
# hastctl role secondary test
```

通过在每个节点上运行 `hastctl` 来验证结果：

```sh
# hastctl status test
```

检查输出中的 `status` 行。如果显示为 `degraded`，则说明配置文件有问题。它应该在每个节点上显示为 `complete`，这意味着节点之间的同步已经开始。当 `hastctl status` 报告 `dirty` 区域的字节数为 0 时，表示同步已完成。

下一步是在 GEOM 提供程序上创建文件系统并挂载它。这必须在 `primary` 节点上完成。创建文件系统可能需要几分钟，具体取决于硬盘的大小。此示例在 **/dev/hast/test** 上创建一个 UFS 文件系统：

```sh
# newfs -U /dev/hast/test
# mkdir /hast/test
# mount /dev/hast/test /hast/test
```

待 HAST 框架正确配置，最后一步是确保 HAST 在系统启动时自动启动。将以下行添加到 **/etc/rc.conf** 中：

```ini
hastd_enable="YES"
```

### 20.14.2.1. 故障切换配置

本示例的目标是构建一个强大的存储系统，能够抵御任意节点的故障。如果主节点发生故障，备份节点将无缝接管，检查并挂载文件系统，继续工作，不会丢失任何数据。

为实现这一任务，使用了通用地址冗余协议（CARP）来提供 IP 层的自动故障切换。CARP 允许同一网络段上的多个主机共享一个 IP 地址。根据 [“通用地址冗余协议（CARP）”](https://docs.freebsd.org/en/books/handbook/advanced-networking/#carp) 中的文档，在两个节点上设置 CARP。在此示例中，每个节点将有自己的管理 IP 地址，并且共享一个 IP 地址 *172.16.0.254*。集群的主 HAST 节点必须是主 CARP 节点。

上一节中创建的 HAST 池现在已经准备好可以导出到网络上的其他主机。这可以通过使用共享 IP 地址 *172.16.0.254* 通过 NFS 或 Samba 完成。唯一未解决的问题是，在主节点故障时自动进行故障切换。

在 CARP 接口状态变化时，FreeBSD 操作系统会生成一个 [devd(8)](https://man.freebsd.org/cgi/man.cgi?query=devd\&sektion=8\&format=html) 事件，这使得可以监控 CARP 接口的状态变化。CARP 接口的状态变化表明某个节点故障或重新上线。这些状态变化事件使得能够运行一个脚本来自动处理 HAST 故障切换。

为了捕获 CARP 接口的状态变化，请在每个节点的 **/etc/devd.conf** 中添加以下配置，同时将 `<vhid>` 替换为虚拟主机 ID，将 `<ifname>` 替换为相关的接口名称：

```ini
notify 30 {
	match "system" "CARP";
	match "subsystem" "<vhid>@<ifname>";
	match "type" "MASTER";
	action "/usr/local/sbin/carp-hast-switch primary";
};

notify 30 {
	match "system" "CARP";
	match "subsystem" "<vhid>@<ifname>";
	match "type" "BACKUP";
	action "/usr/local/sbin/carp-hast-switch secondary";
};
```

在两台节点上重启 [devd(8)](https://man.freebsd.org/cgi/man.cgi?query=devd\&sektion=8\&format=html)，以使新配置生效：

```sh
# service devd restart
```

当指定接口的状态变化时，系统会生成通知，允许 [devd(8)](https://man.freebsd.org/cgi/man.cgi?query=devd\&sektion=8\&format=html) 子系统运行指定的自动故障切换脚本 **/usr/local/sbin/carp-hast-switch**。有关此配置的更多说明，请参阅 [devd.conf(5)](https://man.freebsd.org/cgi/man.cgi?query=devd.conf\&sektion=5\&format=html)。

以下是一个自动故障切换脚本的示例：

```sh
#!/bin/sh

# 原始脚本由 Freddie Cash <fjwcash@gmail.com> 编写
# 由 Michael W. Lucas <mwlucas@BlackHelicopters.org> 和 Viktor Petersson <vpetersson@wireload.net> 修改

# HAST 资源的名称，如 /etc/hast.conf 中所列
resources="test"

# 成为主节点后延迟挂载 HAST 资源
# 请根据实际情况进行调整
delay=3

# 日志记录
log="local0.debug"
name="carp-hast"

# 用户可配置项结束

case "$1" in
	primary)
		logger -p $log -t $name "Switching to primary provider for ${resources}."
		sleep ${delay}

		# 等待所有 "hastd secondary" 进程停止
		for disk in ${resources}; do
			while $( pgrep -lf "hastd: ${disk} \(secondary\)" > /dev/null 2>&1 ); do
				sleep 1
			done

			# 为每个磁盘切换角色
			hastctl role primary ${disk}
			if [ $? -ne 0 ]; then
				logger -p $log -t $name "Unable to change role to primary for resource ${disk}."
				exit 1
			fi
		done

		# 等待 /dev/hast/* 设备出现
		for disk in ${resources}; do
			for I in $( jot 60 ); do
				[ -c "/dev/hast/${disk}" ] && break
				sleep 0.5
			done

			if [ ! -c "/dev/hast/${disk}" ]; then
				logger -p $log -t $name "GEOM provider /dev/hast/${disk} did not appear."
				exit 1
			fi
		done

		logger -p $log -t $name "Role for HAST resources ${resources} switched to primary."

		logger -p $log -t $name "Mounting disks."
		for disk in ${resources}; do
			mkdir -p /hast/${disk}
			fsck -p -y -t ufs /dev/hast/${disk}
			mount /dev/hast/${disk} /hast/${disk}
		done

	;;

	secondary)
		logger -p $log -t $name "Switching to secondary provider for ${resources}."

		# 切换 HAST 资源的角色
		for disk in ${resources}; do
			if ! mount | grep -q "^/dev/hast/${disk} on "
			then
			else
				umount -f /hast/${disk}
			fi
			sleep $delay
			hastctl role secondary ${disk} 2>&1
			if [ $? -ne 0 ]; then
				logger -p $log -t $name "Unable to switch role to secondary for resource ${disk}."
				exit 1
			fi
			logger -p $log -t $name "Role switched to secondary for resource ${disk}."
		done
	;;
esac
```

简而言之，当节点成为主节点时，脚本执行以下操作：

* 将 HAST 池提升为主节点。
* 检查 HAST 池下的文件系统。
* 挂载该池。

当节点成为从节点时：

* 卸载 HAST 池。
* 将 HAST 池降级为从节点。

> **当心**
>
> 这是一个示例脚本，仅作为概念验证。它没有处理所有可能的场景，可以根据需要进行扩展或修改，例如启动或停止所需的服务。

> **技巧**
>
> 本示例中使用了标准的 UFS 文件系统。为了减少恢复所需的时间，可以改用启用日志功能的 UFS 或 ZFS 文件系统。

除了将高可用存储本地使用外，还可以通过 [NFS](https://docs.freebsd.org/en/books/handbook/network-servers/#network-nfs)、[iSCSI](https://docs.freebsd.org/en/books/handbook/network-servers/#network-iscsi)、[sshfs(1)](https://man.freebsd.org/cgi/man.cgi?query=sshfs\&sektion=1\&format=html) 或 ports 中的程序（例如 [net/samba419](https://cgit.freebsd.org/ports/tree/net/samba419/)）将其共享给网络上的其他计算机。

更多详细信息和示例可以在 [http://wiki.FreeBSD.org/HAST](http://wiki.freebsd.org/HAST) 找到。

## 20.14.3. 故障排除

HAST 通常应无问题地工作。然而，像其他任何软件产品一样，有时它可能无法按预期工作。问题的来源可能各不相同，但基本规则是确保集群节点之间的时间同步。

在故障排除 HAST 时，应通过启动 `hastd` 并使用 `-d` 增加调试级别。可以多次指定此参数，以进一步增加调试级别。还可以考虑使用 `-F`，该选项会使 `hastd` 在前台运行。

### 20.14.3.1. 从脑裂状态恢复

*脑裂* 状态发生在集群的节点无法相互通信时，且两者都被配置为主节点。这是一个危险的状态，因为它允许两个节点对数据进行不兼容的更改。该问题必须由系统管理员手动修复。

管理员必须决定哪个节点的更改更为重要，或者手动进行合并。然后，让 HAST 对有问题的数据节点执行完全同步。为此，在需要重新同步的节点上执行以下命令：

```sh
# hastctl role init test
# hastctl create test
# hastctl role secondary test
```
