背景
在日常开发时,有时候需要保证自己代码的健壮性,需要模拟各种故障测试,比如:磁盘、网络、端口等,今天来汇总一下平时使用最多的几种故障模拟方法
磁盘
插入拔出
服务器的存储控制器如果是直通模式,那么在 OS 中能够直接获取到磁盘插入与拔出事件,有时候我们需要检测到相应的事件来自动化的做某些动作,具体的实现方式见之前的文章 Linux 下磁盘设备自动发现方式 。
那么我们写完了代码想要测试,不想去机房物理操作,怎么模拟呢?
Hypervisor
如果你的代码部署的机器是一台虚拟机,那么在 Hypervisor 层面一般都会有对应的接口来完成相应的操作。
比如 Vsphere ESXi 中可以直接编辑虚拟机,在磁盘选项中有一个“移除”按钮,可以直接移除磁盘:

再比如 KVM 下,可以通过 Libvirt 接口来 detach 磁盘。
当然对于插入动作,Hypervisor 也会提供对应的功能。
物理服务器
如果 OS 不是在 Hypervisor 上,而是直接安装在了物理服务器上,我们怎么做呢?
通常我们服务器上的磁盘都是 SCSI 设备,会实现完整的 SCSI(接口),可以通过修改相应设备的标置文件来达到目的。
示例:
节点存在设备 /dev/sda
,修改标置文件,在系统中会发现磁盘已经被移除了。
1 | [root@yiran ~]# lsblk |
如果我们新开一个终端,可以通过 udevadm
命令查看到设备移除过程:
1 | [root@yiran ~]# udevadm monitor |
说完了拔出磁盘,那么该如何插入呢?最简单的办法肯定是重启 OS,毕竟我们不是真正的拔出了设备,重启 OS 之后设备肯定会重新发现。我们也可以通过修改对应的标置文件来手动重新执行设备扫描动作:
1 | [root@yiran ~]# lsblk |
同样,在另一个终端可以看到 udev 事件:
1 | [root@yiran ~]# udevadm monitor |
延迟
为了测试软件在高 IO 延迟下的表现,现在我们需要给一块磁盘设置指定的 IO 延迟(latency),可以使用 dm-delay 来实现:
dm-delay具体参数为:<device> <offset> <delay> [<write_device> <write_offset> <write_delay>]
1 | [root@node90 10:45:54 ~]$modprobe brd rd_nr=1 rd_size=10485760 # 创建10G ram disk |
1 | [root@node90 10:45:54 ~]cat fio.conf |
通过 dm-delay,我们可以来进行各种排列组合来模拟磁盘状态,如:读延迟高,写延迟正常;读延迟高,写延迟高;读延迟低,写延迟高等。
IO 中断
dm-delay 支持设置 IO 中断,具体命令为:
1 | $ sudo dmsetup suspend /dev/dm-0 |
若在执行 fio 过程中设置 IO 中断, 会看到 iops 为0 的现象:
1 | [root@node90 10:45:54 ~]fio fio.conf |
IO Error
有时我们也要求验证磁盘突然出现 IO Error 情况,可以通过 dm-flakey 实现:
1 | [root@node90 11:06:44 ~]$modprobe brd rd_nr=1 rd_size=10485760 #10G |
若在执行 fio 过程中设置 IO Error,则会看到以下错误:
1 | [root@node90 10:45:54 ~]fio fio.conf |
网络
在故障场景中,最常见的应该就是网络故障,尤其是现在分布式应用的场景很多,另一点原因是网络相关的工具很多,可以很方便的使用。
故障
最简单的网络故障应该就是连接中断,也就是各个节点之间无法联通了,我们可以直接通过 ifdown <nic name>
来将网卡置为 down,或者如果不在意其他网卡状态的话,可以直接 systemctl stop network
停止网络服务,来模拟无法联通情况。
有时候我们不想模拟网卡故障,想要模拟固定节点之间的网络故障,那么可以通过 iptables 来实现:
1 | iptables -I INPUT -s 172.11.30.14 -j DROP |
将指定 IP 的所有连接都丢弃。
延迟
网络延迟模拟可以通过 tc
来实现,命令很简单,找到指定的网卡名称,并执行:
1 | [root@yiran ~]# tc qdisc add dev eth0 root netem delay 8ms |
新开终端,在其他节点 ping 设置了网络延迟的节点:
1 | root@yiran-30-250:~ |
可以看到延迟稳定在 8ms 左右。
除了 tc
工具外,前几天看到了一个 Golang 实现的 L4 网络代理项目,可以模拟延迟和丢包,具体关于故障模拟的代码如下:
1 | func (t *TCPServer) doWrite(bytes []byte, conn goetty.IOSession, ctl *conf.CtlUnit) { |
1 | for { |
端口
占用
在使用 Zookeeper 这类有状态应用时,除了通过 service 是否处于 running 来判断服务是否运行外,还需要判断节点的 zk 角色是否符合预期,如 leader 或 follower。
前几天又一个场景需要造出 Zookeeper 处于运行中,但是角色状态处于异常状态,我通过 nc
来实现的:
1 | [root@yiran ~]# nc -kl 2181 |
现将端口占用,然后启动 Zookeeper,就可以了。
总结
故障模拟的主要使用场景是产品的自动化测试中,平时主要是辅助开发来自测,不需要记住具体参数,但是最好了解到什么场景下有什么工具可以使用,省时省力。
参考链接
- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-managing_guest_virtual_machines_with_virsh-attaching_and_updating_a_device_with_virsh
- https://www.enodev.fr/posts/emulate-a-slow-block-device-with-dm-delay.html
- https://www.kernel.org/doc/Documentation/device-mapper/dm-flakey.txt
- https://github.com/fagongzi/netproxy