HAST (Highly Available Storage) is a new concept for FreeBSD and it is under constant development. HAST allows to transparently store data on two physically separated machines connected over the TCP/IP network. HAST operates on block level making it transparent for file systems, providing disk-like devices in /dev/hast directory.
In this article we will create two identical HAST nodes, hast1 and hast2. Both devices will use one NIC connected to a vlan for data synchronization and another NIC will be configured via CARP in order to share the same IP address across the network. The first node will be called storage1.hast.test, the second storage2.hast.test and they will both listen to a common IP address which we will bind to storage.hast.test
HAST binds its resource names according to the machine's hostname. Therefore, we will use hast1.freebsd.loc and hast2.freebsd.loc as the machines hostnames so that HAST can operate without complaining.
For starters, lets set up two identical nodes. For this example I have installed FreeBSD 9.0-RELEASE on two deferent instances using a Linux KVM. Both nodes have 512MB of RAM, one SATA drive containing the OS and three SATA drives which will be used to create our shared Raidz1 pool.
In order for carp to work we don't have to compile a new kernel. We can just load it as a module by adding to /boot/loader.conf
Our both nodes are set up, it is time to make some adjustments. First a descent /etc/rc.conf for the first node:
The second node we will also much the first except for the IP addressing:
At this point we have assigned re1 with two IPs for HAST synchronization. We have also assigned two IPs to re0 which in turn we share with a third common IP assigned to carp0.
As a result, re1 is being used for HAST synchronization in a vlan while carp0 which is cloned by re0 used under the same vlan with the rest of our clients.
In order for HAST to function correctly we have to resolve the correct IPs on every node. We don't want to rely on DNS for this because DNS can fail. Instead we will use /etc/hosts same on every node.
Next, we have to create the /etc/hast.conf file. Here we will declare the resources that we want to create. All resources will eventually create devices located under /dev/hast on the primary node. Every resource indicates a physical device specifying a local and remote IP device. The /etc/hast.conf must be exactly the same on every node.
In this example we are sharing three resources, disk1, disk2 and disk3. Each resource indicates a device the local and the remote IP address. With this configuration in place, we are ready to begin setting up out HAST devices.
Lets start hastd on both nodes first:
Now on the primary node we will initialize our resources, create them and finally assign a primary role:
Next, on the secondary node we will initialize our resources, create them and finally assign a secondary role:
There are other ways for creating and assigning roles to each resource. Having repeat this procedure a few times, I saw that this usually always works.
Now check the status on both nodes:
The first node looks good. Status is complete.
So does the second. Like I mentioned earlier there are different ways for doing this the first time. You have to look for the word
status: complete. If you get a degraded status you can always repeat the procedure.
Now it is time to create our ZFS pool. The primary node should have a /dev/hast directory containing our resources. This directory appears only at the active node.
We can now use hastctl status on each node to see if everything looks ok. The magic word we are looking for here is:
replication: fullsync
At this point both of our nodes should be available for failover. We have storage1 running as primary and sharing a pool called zhast. Our storage2 is currently in a standby mode. If we have set DNS properly we can ssh to storage.hast.test or by using its carp IP to 10.10.10.180.
In this article we will create two identical HAST nodes, hast1 and hast2. Both devices will use one NIC connected to a vlan for data synchronization and another NIC will be configured via CARP in order to share the same IP address across the network. The first node will be called storage1.hast.test, the second storage2.hast.test and they will both listen to a common IP address which we will bind to storage.hast.test
HAST binds its resource names according to the machine's hostname. Therefore, we will use hast1.freebsd.loc and hast2.freebsd.loc as the machines hostnames so that HAST can operate without complaining.
For starters, lets set up two identical nodes. For this example I have installed FreeBSD 9.0-RELEASE on two deferent instances using a Linux KVM. Both nodes have 512MB of RAM, one SATA drive containing the OS and three SATA drives which will be used to create our shared Raidz1 pool.
In order for carp to work we don't have to compile a new kernel. We can just load it as a module by adding to /boot/loader.conf
Code:
if_carp_load="YES"
Our both nodes are set up, it is time to make some adjustments. First a descent /etc/rc.conf for the first node:
Code:
zfs_enable="YES"
###Primary Interface##
ifconfig_re0="inet 10.10.10.181 netmask 255.255.255.0"
###Secondary Interface for HAST###
ifconfig_re1="inet 192.168.100.100 netmask 255.255.255.0"
defaultrouter="10.10.10.1"
sshd_enable="YES"
hostname="hast1.freebsd.loc"
##CARP INTERFACE SETUP##
cloned_interfaces="carp0"
ifconfig_carp0="inet 10.10.10.180 netmask 255.255.255.0 vhid 1 pass mypassword advskew 0"
hastd_enable=YES
The second node we will also much the first except for the IP addressing:
Code:
zfs_enable="YES"
###Primary Interface##
ifconfig_re0="inet 10.10.10.182 netmask 255.255.255.0"
###Secondary Interface for HAST###
ifconfig_re1="inet 192.168.100.101 netmask 255.255.255.0"
defaultrouter="10.10.10.1"
sshd_enable="YES"
hostname="hast2.freebsd.loc"
##CARP INTERFACE SETUP##
cloned_interfaces="carp0"
ifconfig_carp0="inet 10.10.10.180 netmask 255.255.255.0 vhid 1 pass mypassword advskew 0"
hastd_enable=YES
At this point we have assigned re1 with two IPs for HAST synchronization. We have also assigned two IPs to re0 which in turn we share with a third common IP assigned to carp0.
As a result, re1 is being used for HAST synchronization in a vlan while carp0 which is cloned by re0 used under the same vlan with the rest of our clients.
In order for HAST to function correctly we have to resolve the correct IPs on every node. We don't want to rely on DNS for this because DNS can fail. Instead we will use /etc/hosts same on every node.
Code:
::1 localhost localhost.freebsd.loc
127.0.0.1 localhost localhost.freebsd.loc
192.168.100.100 hast1.freebsd.loc hast1
192.168.100.101 hast2.freebsd.loc hast2
10.10.10.181 storage1.hast.test storage1
10.10.10.182 storage2.hast.test storage2
10.10.10.180 storage.hast.test storage
Next, we have to create the /etc/hast.conf file. Here we will declare the resources that we want to create. All resources will eventually create devices located under /dev/hast on the primary node. Every resource indicates a physical device specifying a local and remote IP device. The /etc/hast.conf must be exactly the same on every node.
Code:
resource disk1 {
on hast1 {
local /dev/ad1
remote hast2
}
on hast2 {
local /dev/ad1
remote hast1
}
}
resource disk2 {
on hast1 {
local /dev/ad2
remote hast2
}
on hast2 {
local /dev/ad2
remote hast1
}
}
resource disk3 {
on hast1 {
local /dev/ad3
remote hast2
}
on hast2 {
local /dev/ad3
remote hast1
}
}
In this example we are sharing three resources, disk1, disk2 and disk3. Each resource indicates a device the local and the remote IP address. With this configuration in place, we are ready to begin setting up out HAST devices.
Lets start hastd on both nodes first:
Code:
hast1#/etc/rc.d/hastd start
Code:
hast2#/etc/rc.d/hastd start
Now on the primary node we will initialize our resources, create them and finally assign a primary role:
Code:
hast1#hastctl role init disk1
hast1#hastctl role init disk2
hast1#hastctl role init disk3
hast1#hastctl create disk1
hast1#hastctl create disk2
hast1#hastctl create disk3
hast1#hastctl role primary disk1
hast1#hastctl role primary disk2
hast1#hastctl role primary disk3
Next, on the secondary node we will initialize our resources, create them and finally assign a secondary role:
Code:
hast2#hastctl role init disk1
hast2#hastctl role init disk2
hast2#hastctl role init disk3
hast2#hastctl create disk1
hast2#hastctl create disk2
hast2#hastctl create disk3
hast2#hastctl role secondary disk1
hast2#hastctl role secondary disk2
hast2#hastctl role secondary disk3
There are other ways for creating and assigning roles to each resource. Having repeat this procedure a few times, I saw that this usually always works.
Now check the status on both nodes:
Code:
hast1# hastctl status
disk1:
role: primary
provname: disk1
localpath: /dev/ada1
...
remoteaddr: hast2
replication: fullsync
status: complete
dirty: 0 (0B)
...
disk2:
role: primary
provname: disk2
localpath: /dev/ada2
...
remoteaddr: hast2
replication: fullsync
status: complete
dirty: 0 (0B)
...
disk3:
role: primary
provname: disk3
localpath: /dev/ada3
...
remoteaddr: hast2
replication: fullsync
status: complete
dirty: 0 (0B)
...
The first node looks good. Status is complete.
Code:
hast2# hastctl status
disk1:
role: secondary
provname: disk1
localpath: /dev/ada1
...
remoteaddr: hast1
replication: fullsync
status: complete
dirty: 0 (0B)
...
disk2:
role: secondary
provname: disk2
localpath: /dev/ada2
...
remoteaddr: hast1
replication: fullsync
status: complete
dirty: 0 (0B)
...
disk3:
role: secondary
provname: disk3
localpath: /dev/ada3
...
remoteaddr: hast1
replication: fullsync
status: complete
dirty: 0 (0B)
...
So does the second. Like I mentioned earlier there are different ways for doing this the first time. You have to look for the word
status: complete. If you get a degraded status you can always repeat the procedure.
Now it is time to create our ZFS pool. The primary node should have a /dev/hast directory containing our resources. This directory appears only at the active node.
Code:
hast1# zpool create zhast raidz1 /dev/hast/disk1 /dev/hast/disk2 /dev/hast/disk3
hast1# zpool status zhast
pool: zhast
state: ONLINE
scan: none requested
config:
NAME STATE READ WRITE CKSUM
zhast ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
hast/disk1 ONLINE 0 0 0
hast/disk2 ONLINE 0 0 0
hast/disk3 ONLINE 0 0 0
We can now use hastctl status on each node to see if everything looks ok. The magic word we are looking for here is:
replication: fullsync
At this point both of our nodes should be available for failover. We have storage1 running as primary and sharing a pool called zhast. Our storage2 is currently in a standby mode. If we have set DNS properly we can ssh to storage.hast.test or by using its carp IP to 10.10.10.180.