Ubuntu 上搭建高可用性的数据库服务器

pacemaker drbd ubuntu postgresql nfs corosync

更新信息

原先一直使用的是 heartbeat 2.x 来搭建 HA 系统, 后来 heartbeat 升级到了 3.x, 它将原本的模块拆分成好几个子项目. 原先的 heartbeat 只用来做心跳, 所以索性就抛弃 heartbeat 改成主流的 pacemaker + corosync HA 方案.

Ruby on Rails 程序本身设计就能很好的支持集群部署, 所以就算坏一台应用服务器也不只至于造成整体无法对外提供服务.

但数据层面一般只会有一台服务器, 所以数据库服务器的可用性是整个系统的短板. 如果可以提高数据库层面服务器的可用性的话, 也就大大提高了整体系统的可用性.

数据库的可用性说白了就是多部署一台服务器, 当一台出问题的时候另外一台可以接手继续工作. 这方面 linux 有很多现成的工具. 这里只是整理一下自己的安装步骤, 目标是提供一套简单的高可用方案. (大规模复杂的就是专业 SA 的事情了)

结构规划

规划图

图中 app N 就是应用服务器(比如 rails, tomcat 之类的). 他们通过一个 虚拟的IP地址(简称: vip) 连接到数据库服务器.

而数据库服务器则分为两台, 其中只有一台 A 会设置成 VIP 的地址.

他们之间通过心跳网络来判断是否正常, 当 A 故障后, B 服务器 就会自动的设置成 VIP的地址 对外进行服务. 当故障的 A 修复好以后, 对外服务的仍然还是 B 服务器. 直到 B 故障才会重新变为 A.

作为 vip 服务器需要自动的将修改的内容同步给另一台服务器, 以保证万一出故障另一台可以马上继续服务.

IP地址

每台服务器都配有两块网卡, 一块用于对外服务区, 一块用于内部保持心跳和通讯. 规划如下:

name eth0 eth1
db1 192.168.37.51 192.168.0.1
db2 192.168.37.52 192.168.0.2
vip 192.168.37.50  

其中 vip192.168.37.50 就是 app 服务器配置的比如: nfs, postgresql 的地址.

基础设置

这里是基于 ubuntu server 12.04 lts 设置的, 如果是其他的服务器配置需要进行相应的修改.

设置 db1 数据库

  1. 修改服务器名 /etc/hostnamedb1

  2. 设置 IP 地址 /etc/network/interfaces

    auto eth0
    iface eth0 inet static
    	address 192.168.37.51
    	netmask 255.255.255.0
    	gateway 192.168.37.1
    	dns-nameservers 192.168.37.1
       	
    auto eth1
    iface eth1 inet static
    	address 192.168.0.1
    	netmask 255.255.255.0
    

db2 以此类推, 另外这里不用设置 vip 的地址.

配置 drbd

drbd 是用于在多台服务器之间自动同步硬盘数据. 一般用户上传的附件之类的不会存数据库, 所以需要通过 drbd 来同步, 如果有的数据库并没有提供复制功能, 那也可以通过 drbd 来同步数据.

1. [db1, db2] 准备 drbd 硬盘分区

我这里使用的是通过 LVM 划分出的虚拟分区 /dev/mapper/ubuntu/drbd0 另外 drbd 也可以使用真实的物理分区, 详细的分区步骤这里就不详述了.

2. 安装 drbd

root@db1:~# apt-get install -y drbd8-utils
root@db2:~# apt-get install -y drbd8-utils

安装完成后编辑 /etc/drbd.d/r0.res 加入

resource r0 {
	startup {
		wfc-timeout 15;
		degr-wfc-timeout 60;
	}
	net {
		after-sb-0pri discard-zero-changes;
		after-sb-1pri discard-secondary;
		after-sb-2pri disconnect;
	}
	syncer {
		rate 20M;
	}
	on db1 {
		device /dev/drbd0;
		disk /dev/mapper/ubuntu-drbd0;
		address 192.168.0.1:7788;
		meta-disk internal;
	}
	on db2 {
		device /dev/drbd0;
		disk /dev/mapper/ubuntu-drbd0;
		address 192.168.0.2:7788;
		meta-disk internal;
	}
}

这个配置文件中 disk 就是真实分区的位置, 还在 net 加入了对自动冲突(split brain) 的处理.

完成后将配置同步到 db2 上.

root@db1:~# scp /etc/drbd.d/r0.res db2:/etc/drbd.d/

3. 初始化磁盘

终端中输入

root@db1:~# service drbd stop
root@db2:~# service drbd stop
root@db1:~# drbdadm create-md r0
root@db2:~# drbdadm create-md r0

接着启动 drbd 服务

root@db1:~#  service drbd start 
root@db2:~#  service drbd start 

修复 ubuntu 12.04 lts 下 drbd 无法启动问题

在 ubuntu 12.04 下运行 service drbd start 很可能会出现如下错误.

node already registered
 * Starting DRBD resources
DRBD module version: 8.4.2
   userland version: 8.3.11
you should upgrade your drbd tools!

这是由于 linux 内核版本升级到 3.8, 而于自带的 drbd 软件不兼容导致的. 可以通过 ppa 来安装第三方提供的 8.4 版本的 drbd 解决, 命令如下:

apt-get install -y python-software-properties
add-apt-repository ppa:icamargo/drbd
apt-get update
apt-get install -y drbd8-utils

之后应该就可以启动 drbd

service drbd start

启动后可以输入

root@db1:~# service drbd status

drbd driver loaded OK; device status:
version: 8.4.2 (api:1/proto:86-101)
srcversion: 18C7EBE1B3F8CCCB5CF512C 
m:res  cs         ro                   ds                         p  mounted  fstype
0:r0   Connected  Secondary/Secondary  Inconsistent/Inconsistent  C

看到当前两个服务器磁盘都属于 Secondary 模式. 接着将 db1 的磁盘设置成主磁盘

root@db1:~# drbdadm -- --overwrite-data-of-peer primary r0

这时可以通过

root@db1:~# cat /proc/drbd 
	
version: 8.4.2 (api:1/proto:86-101)
srcversion: 18C7EBE1B3F8CCCB5CF512C 
 0: cs:SyncSource ro:Primary/Secondary ds:UpToDate/Inconsistent C r-----
    ns:1915904 nr:0 dw:0 dr:1916568 al:0 bm:116 lo:0 pe:2 ua:0 ap:0 ep:1 wo:f oos:8570524
	[==>.................] sync'ed: 18.3% (8368/10236)Mfinish: 0:01:29 speed: 95,692 (95,692) K/sec

看到目前正在慢慢的将数据同步到 db2 上. 不同等待同步结束可以直接输入

root@db1:~# mkfs.ext4 /dev/drbd0

将 drbd0 的磁盘格式化成 ext4 格式. 接着需要在服务器上建被 mount 的目录

root@db1:~# mkdir /srv/drbd0
root@db2:~# mkdir /srv/drbd0

db1 上可以测试下能否被正确的 mount

mount /dev/drbd0 /srv/drbd0
ls -al /srv/drbd0

测试没问题后把它 umount 掉

umount /srv/drbd0

至此虽然 drbd 配置完成了, 但还需要配合 pacemaker 才能实现自动转移.

Corosync

corosync 是用于处理心跳和上报节点信息的软件.

1. 安装配置 corosync

root@db1:~# apt-get install -y corosync
root@db2:~# apt-get install -y corosync

安装完成以后修改配置文件 /etc/corosync/corosync.conf 找到

        interface {
                # The following values need to be set based on your environment 
                ringnumber: 0
                bindnetaddr: 127.0.0.1
                mcastaddr: 226.94.1.1
                mcastport: 5405
        }

其中的 bindnetaddr 需要根据当前网络修改, 通过命令 (其中 eth1 根据实际的网络修改)

ip addr | grep "inet " | grep eth1 | awk '{print $4}' | sed s/255/0/

可以看到这里我们需要修成的值是 192.168.0.0. 于是我们把这段配置修改成

        interface {
                # The following values need to be set based on your environment 
                ringnumber: 0
                bindnetaddr: 192.168.0.0
                mcastaddr: 226.94.1.1
                mcastport: 5405
        }

接着找到

service {
        # Load the Pacemaker Cluster Resource Manager
        ver:       0
        name:      pacemaker
}

把其中的 ver: 0 修改成 1 也就是修改成:

service {
        # Load the Pacemaker Cluster Resource Manager
        ver:       1
        name:      pacemaker
}

2. 启动 corosync

完成以上配置后还需要修改 /etc/default/corosync

START=no

修改成

START=yes

接着通过命令, 启动 corosync

root@db1:~# service corosync start
root@db2:~# service corosync start

Pacemaker

pacemaker 是一个群集资源管理器, 用于管理 corosync 的节点.

1. 安装和运行 pacemaker

root@db1:~# apt-get install -y pacemaker
root@db1:~# apt-get install -y pacemaker

root@db1:~# service pacemaker start
root@db2:~# service pacemaker start

完成后就可以通过命令 crm status 查看节点状态了.

root@db1:~# crm status

============
Last updated: Mon Dec  2 16:45:30 2013
Last change: Mon Dec  2 16:41:35 2013 via crmd on db2
Stack: openais
Current DC: NONE
2 Nodes configured, 2 expected votes
0 Resources configured.
============

Node db1: UNCLEAN (offline)
Node db2: UNCLEAN (offline)

可以看到目前的2个节点 db1 和 db2 都是离线的. 接着就需要配置 pacemaker 了

2. 配置 pacemaker

首先在 db1 输入

crm configure property stonith-enabled=false
crm configure property no-quorum-policy=ignore

crm configure rsc_defaults resource-stickiness=100

这几条命令保证, 当 db1 宕机切换到 db2 后, db1 修复好重新上线也不会自动的把主机转移回 db1, 造成不必要的切换.

3. 配置 drbd

继续再终端输入 crm configure 进入配置模式, 接着输入

primitive drbd0 ocf:linbit:drbd \
    params drbd_resource="r0" \
    op monitor interval="29s" role="Master" \
    op monitor interval="31s" role="Slave"

ms ms-drbd0 drbd0 \
    meta master-max="1" master-node-max="1" \
         clone-max="2" clone-node-max="1" \
         notify="true"

primitive db-fs ocf:heartbeat:Filesystem \
    params fstype=ext4 directory=/srv/drbd0 \
        device=/dev/drbd0

group db-ha-group db-fs

colocation db-ha-group-on-ms-drbd0 inf: db-ha-group ms-drbd0:Master
order db-ha-group-after-ms-drbd0 inf: ms-drbd0:promote db-ha-group:start

commit
exit

这时可以通过 crm status 查看到

Online: [ db1 db2 ]

 Master/Slave Set: ms-drbd0 [drbd0]
     Masters: [ db1 ]
     Slaves: [ db2 ]
 Resource Group: db-ha-group
     db-fs	(ocf::heartbeat:Filesystem):	Started db1

当前的 drbd 已经在 db1 上启动并挂载到 /srv/drbd0 下了.

4. 配置 vip

crm configure primitive db-ip ocf:heartbeat:IPaddr2 \
    params ip="192.168.37.50" nic="eth0" meta target-role=stopped
echo $(crm configure show db-ha-group) db-ip | crm configure load update -
crm resource start db-ip

以上命令就是新建了一个叫 db-ip 的资源, 并把他加入 db-ha-group 组并启动. 为了验证是否成功我们可以输入

root@db1:~# crm status

Online: [ db1 db2 ]

 Master/Slave Set: ms-drbd0 [drbd0]
     Masters: [ db1 ]
     Slaves: [ db2 ]
 Resource Group: db-ha-group
     db-fs	(ocf::heartbeat:Filesystem):	Started db1
     db-ip	(ocf::heartbeat:IPaddr2):	Started db1

看到 db-ip 已经在 db1 启动了. 接着验证这个 ip 的设置可以输入

root@db1:~# ip addr show eth0

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:0c:0d:69 brd ff:ff:ff:ff:ff:ff
    inet 192.168.37.51/24 brd 192.168.37.255 scope global eth0
    inet 192.168.37.50/32 brd 192.168.37.178 scope global eth0
    inet6 fe80::20c:29ff:fe0c:d69/64 scope link 
       valid_lft forever preferred_lft forever

可以看到这个 192.168.37.50 已经被配置给了 eth0

5. 配置 Postgresql

这里 postgresql 将使用 drbd 来做 HA, 没有使用 postgresql 自带的 replication, 主要是因为 replication 配置太复杂, 也没啥必要.

首先安装数据库

root@db1:~# apt-get install -y postgresql
root@db2:~# apt-get install -y postgresql

安装完成后, 取消 postgresql 的默认启动

root@db1:~# service postgresql stop
root@db2:~# service postgresql stop

root@db1:~# update-rc.d -f postgresql remove
root@db2:~# update-rc.d -f postgresql remove

接着在 db1 上将 postgresql 相关文件移动到 drbd 上

mkdir /srv/drbd0/postgresql
ln -s /srv/drbd0/postgresql /srv/postgresql

mv /etc/postgresql/9.1 /srv/postgresql/conf
ln -s /srv/postgresql/conf /etc/postgresql/9.1
chown postgres:postgres -h /etc/postgresql/9.1

mv /var/lib/postgresql/9.1 /srv/postgresql/data
ln -s /srv/postgresql/data /var/lib/postgresql/9.1
chown postgres:postgres -h /var/lib/postgresql/9.1

db1 这样处理好以后, 把 db2 的同样目录也指向 drbd, 输入

ln -s /srv/drbd0/postgresql /srv/postgresql

rm -rf /etc/postgresql/9.1
ln -s /srv/postgresql/conf /etc/postgresql/9.1
chown postgres:postgres -h /etc/postgresql/9.1

rm -rf /var/lib/postgresql/9.1
ln -s /srv/postgresql/data /var/lib/postgresql/9.1
chown postgres:postgres -h /var/lib/postgresql/9.1

之后就可以用 packmaker 配置启动了, 再 db1 上输入

crm configure primitive db-pg ocf:heartbeat:pgsql \
    params pgctl="/usr/lib/postgresql/9.1/bin/pg_ctl" \
        psql="/usr/bin/psql" pgdata="/var/lib/postgresql/9.1/main" \
        config="/etc/postgresql/9.1/main/postgresql.conf" \
        logfile="/var/log/postgresql/postgresql-9.1-main.log" \
    op start interval="0" timeout="120s" \
    op stop interval="0" timeout="120s" \
    op monitor interval="30s" meta target-role=stopped
echo $(crm configure show db-ha-group) db-pg | crm configure load update -
crm resource start db-pg

之后通过 crm status 或者 sudo -u postgres psql 应该可以看到已经成功启动了数据库了.

6. 配置 NFS

对于集群的应用服务器来说, 对于像用户上传文件之类的资源, 一般都都会通过 NFS 挂在到应用服务器上. 而这里还需要将 nfs 的目录映射到 drbd 的目录.

首先安装 nfs

root@db1:~# apt-get install -y nfs-kernel-server
root@db2:~# apt-get install -y nfs-kernel-server

接着和 postgresql 一样 停止默认启动

root@db1:~# service nfs-kernel-server stop
root@db2:~# service nfs-kernel-server stop

root@db1:~# update-rc.d -f nfs-kernel-server remove
root@db2:~# update-rc.d -f nfs-kernel-server remove

接着将原有配置复制到 drbd 中

root@db1:~# mkdir /srv/drbd0/nfs
root@db1:~# ln -sf /srv/drbd0/nfs /srv/nfs
root@db1:~# mv /etc/exports /srv/nfs/
root@db1:~# ln -s /srv/nfs/exports /etc/exports
root@db1:~# mkdir /srv/nfs/data

root@db2:~# ln -sf /srv/drbd0/nfs /srv/nfs
root@db2:~# ln -sf /srv/nfs/exports /etc/exports

然后编辑 db1/etc/exports 最后加入

/srv/nfs/data *(rw,no_subtree_check,no_all_squash,no_root_squash)

最后配置 pacemaker 的资源

crm configure primitive db-nfs lsb:nfs-kernel-server \
    op monitor interval="30s" meta target-role=stopped
echo $(crm configure show db-ha-group) db-nfs | crm configure load update -
crm resource start db-nfs

最终输入 crm status

Online: [ db1 db2 ]

 Master/Slave Set: ms-drbd0 [drbd0]
     Masters: [ db1 ]
     Slaves: [ db2 ]
 Resource Group: db-ha-group
     db-fs	(ocf::heartbeat:Filesystem):	Started db1
     db-ip	(ocf::heartbeat:IPaddr2):	Started db1
     db-pg	(ocf::heartbeat:pgsql):	Started db1
     db-nfs	(lsb:nfs-kernel-server):	Started db1

可以看到所有的服务都已启动.

7. 测试迁移

完成之前配置后, 可以通过 crm status 看到所有资源都在 db1 上启动了, 现在我们测试能否将资源转移到 db2 上并且根据设计他不应该自动迁移回 db1, 我们输入

crm resource migrate db-ha-group db2
crm resource unmigrate db-ha-group

先资源强制分配到 db2, 再取消强制分配. 然后输入 crm_mon, 应该就可以看到资源再慢慢的迁移到 db2, 等一会最终可以看到.

Online: [ db1 db2 ]

 Master/Slave Set: ms-drbd0 [drbd0]
     Masters: [ db2 ]
     Slaves: [ db1 ]
 Resource Group: db-ha-group
     db-fs      (ocf::heartbeat:Filesystem):    Started db2
     db-ip      (ocf::heartbeat:IPaddr2):       Started db2
     db-pg      (ocf::heartbeat:pgsql): Started db2
     db-nfs     (lsb:nfs-kernel-server):        Started db2

所有的资源都运行于 db2 了. 然后把资源再迁回 db1 可以输入之前的迁移命令也可以简单的重启 pacemaker

service pacemaker restart

不一会儿就可以通过 crm status 看到所有的资源又被迁移回了 db1

最后

至此基本的 HA 都已经配置完成了. 基本就是让 pacemaker 来负责程序的启动和调度. 另外还可以通过下列命令来设置 crm.

crm resource show             # 显示所有的 resource
crm resource cleanup ${id}    # 清除失败的 action 日志
crm resource stop ${id}       # 停止某个 resource
crm configure delete ${id}    # 删除某个配置
crm configure edit            # 编辑现有的配置文件

参考资料

更新历史

  • 2013-12-03 更新 使用 pacemaker + corosync 代替 heartbeat
  • 2012-08-13 初始 使用 heartbeat 2.x 搭建

Comments