记一次OpenStack功能测试,出现xfs数据不一致的问题
前言
工作中所负责的OpenStack集群由于在前期规划当中,热迁移的网卡默认走的是管理网络,而管理网络在集群规划时,是基于千兆网络组网的,这就导致如果出现大量热迁移的时候,千兆网络会成为限制。
通过修改live_migrate_inbound_addr
选项,是可以修改通过哪个网卡进行迁移的。
冠状病毒给我放了一个大寒假,来的第一天我就准备把热迁移网卡换成存储的万兆网口网络,本来想着这个操作很简单,就不在测试集群上试了,直接在实际环境中操作吧,当时的想法如下:
- 寒假的第一天,蜜汁自信….
- nova-compute服务自是管理虚拟机生命周期,短暂的重启nova-compute服务,并不会影响虚拟机,只要保证libvirtd服务的正常运行就行。
开始部署
实际环境中的虚拟机是使用Kolla-ansible部署的,有一些问题需要说明:
- 所有的服务都是跑在容器里面的,包括libvirtd服务
- 如果一旦容器内的pid进程退出,那么整个容器就会关闭,也就是说在容器内的所有进程,都会被迫关闭,这种情况会导致qemu-kvm退出,虚拟机被迫强制关机。
等我跑完脚本,准备测试的时候,出现了问题,libvirtd在部署的时候,不是以0.0.0.0
的方式监听的,当然这种事合理的,为了安全考虑,不做过多的外部暴露。但是由于没有监听存储网络的地址,在使用了新的配置之后,热迁移会尝试用目的地址为对方存储网络的ip去进行连接,但是对方并没有监听这个地址,会导致连接失败…
那么能不能想办法修改监听地址呢?现在如果关闭libvirtd服务,并不会影响qemu-kvm进程,不会影响到虚拟机,但是由于运行在容器的原因,pid为1,所以还是会导致关闭…
到了这里卡住了,现在如果想不停机,只有两种方法:
- 一台一台的热迁移上面的虚拟机,热迁移完成后修改该节点配置文件
- 通过反向代理的方式实现端口监听。
上面说到的反向代理的方式实现,是一条折中的方法,既然这样准备写一个部署脚本来实现,但是还没来得及写,问题就出现了….
处理问题
陆陆续续有很多负责人说自己的虚拟机ssh连不上去了,登录上去爆了一大堆的xfs的错误,通过console连上去,按什么都没有反应,只能重启,重启之后直接进入救援模式,挂载也挂载不上去,直接报错
重启之后也有这种错误
这种情况有的使用xfs_repair修复成功了,但是大多数都没成功,好多文件都丢到lost+found文件夹里面去了,而且每次修复,往里面丢的越多。
没办法,只能重建…
连数据都拷不出来,拷着烤着就会报Input/Output error
我当时疑惑到底哪里出了问题…
还没反应过来是因为我刚刚操作导致的..
查找原因
通过这些出现问题的虚拟机的日志,我发现,大多数出问题的时间点都是在我执行脚本,那个时间段出的问题…
我没想到导致我重启nova-compute会导致虚拟机xfs的错误…
直到我突然在昨天想到我之前写过一个Python脚本,监听OpenStack的nova-compute服务状态,如果状态为down,就将该节点上的所有虚拟机迁移出去。这个脚本当时的初衷是为了防止某个节点宕机之后,虚拟机可以在其他节点上启动起来。
当我重启a节点nova-compute节点的时候,该节点状态为down状态,脚本检测到,然后执行迁移(冷)操作,假设迁移到b节点,这个时候a节点上的虚拟机并没有死(正常的冷迁移操作会先删除然后再迁移到新的机器上),还在正常运行,而b节点又以相同的配置启动起来一个,所有的存储都是基于rbd,这个两个操作虚拟机共同使用一个根文件系统,而且这个时候可能还有业务写入操作,就会导致数据不一致的问题…
打个比方:
假设有这样一个业务逻辑,一个进程监听文件a的变化,如果文件新加了一行数据,新加的数据为123,那么就执行往b文件里面追加写入456并且加上现在的时间戳,这是正常的一个进程的实现。
但是如果这个时候在启动一个进程,监听123,写入456加时间戳,那么这个时候b文件存的内容就被追加了两个456加时间戳,就导致了数据的错乱
可以把上面的文件理解为一个个block块,进程理解为系统kernel。那么上面说的两个kernel可能由于运行的时间段和逻辑不通,环境不通,比如要新加一个文件,运行b节点上的虚拟机查询block bitmap发现bloack 1000可以使用,那么创建一个inode,指向这个block,然后把数据放在这里了,这个时候就会标记这个block已经被使用。而a节点上的虚拟机上业务进程可能需要对某个文件增加新的内容,需要新的block存数据,检测可用的block,那么block 块为1000可用,就把数据放在这里了。这样就导致了数据不一致的问题,至于xfs的日志会不会正常纪律这样的操作,其实又回到上面的问题上了,记录日志也会对block写入,就算记录,数据也被重写了,不正确了。
注意:block bitmap被系统维护,会被缓存,如果读取的时间断不一样,那么两个内核就会出现上述情况。
当然了,上面只是我的猜想,并不正确,没有实际的代码证据支撑。
如何避免
运维规范化
没错,这次又是因为操作不规范引起的事故。以下几点需要注意:
- 不应该直接在生产环境进行实验
- 在一些操作方面并没有规范化,比如在写脚本之前就应该在运维操作流程里面备注清楚,节点状态为down会迁移,然后写在操作手册上面,而不是全靠记忆来记这些问题。
自动迁移脚本不够严谨
这次最大最大的失误也就是因为自己在写脚本的时候只是简单的从集群API里面拉去状态来判断,而不是从多个维度上去判断该节点是否宕机。