在生产环境中,由于各种事件(例如磁盘故障和管理员错误),可能会发生文件系统故障。作为Chaos Engineering平台,Chaos Mesh自其早期版本以来就一直支持在文件系统中模拟I / O故障。通过简单地添加IOChaos CustomResourceDefinition(CRD),我们可以观察文件系统如何失败并返回错误。
但是,在Chaos Mesh 1.0之前,该实验并不容易,并且可能消耗了大量资源。我们需要通过变异的接纳网络钩子将边车容器注入Pod,并重写ENTRYPOINT命令。即使未注入故障,注入的侧车集装箱也会造成大量开销。
Chaos Mesh 1.0改变了这一切。现在,我们可以使用IOChaos在运行时将错误注入文件系统。这简化了过程并大大减少了系统开销。这篇博客文章介绍了如何在不使用辅助工具的情况下实施IOChaos实验。
为了在运行时模拟I / O错误,我们需要在程序开始系统调用(例如读取和写入)之后但在调用请求到达目标文件系统之前,将错误注入文件系统中。我们可以通过以下两种方式之一来做到这一点:
在目标文件系统之前添加一个称为ChaosFS的文件系统层。 ChaosFS使用目标文件系统作为后端,并从操作系统接收请求。整个调用链接是目标程序syscall-> Linux内核-> ChaosFS->目标文件系统。因为ChaosFS是可定制的,所以我们可以根据需要注入延迟和错误。因此,ChaosFS是我们的选择。
如果ChaosFS在目标文件系统中读写文件,则需要将ChaosFS挂载到与Pod配置中指定的目标路径不同的路径。 ChaosFS无法安装到目标目录的路径。
我们需要在目标程序开始运行之前挂载ChaosFS。这是因为新安装的ChaosFS仅对目标文件系统中的程序新打开的文件有效。
我们需要将ChaosFS挂载到目标容器的mnt名称空间。有关详细信息,请参见mount_namespaces(7)-Linux手册页。
在Chaos Mesh 1.0之前,我们使用了变异接纳Webhook来实现IOChaos。该技术解决了上面列出的三个问题,并允许我们:
在目标容器中运行脚本。该操作更改了ChaosFS后端文件系统的目标目录(例如,从/ mnt / a更改为/ mnt / a_bak),以便我们可以将ChaosFS挂载到目标路径(/ mnt / a)。启动Pod。例如,我们可以将原始命令/ app修改为/waitfs.sh / app。
waitfs.sh脚本一直在检查文件系统是否已成功安装。如果已安装,则/ app已启动。
在Pod中添加一个新容器以运行ChaosFS。该容器需要与目标容器(例如,/ mnt)共享一个卷,然后我们将该卷安装到目标目录(例如,/ mnt / a)。我们还为该卷的装载正确启用了装载传播,以将共享渗透到主机,然后将奴隶渗透到目标。
这三种方法使我们可以在程序运行时注入I / O错误。但是,注入远非方便:
我们只能将故障注入到卷子目录中,而不能注入到整个卷中。解决方法是将mv(重命名)替换为mount move以移动目标卷的安装点。
我们必须在Pod中显式编写命令,而不是隐式使用image命令。否则,挂载文件系统后/waitfs.sh脚本将无法正确启动程序。
相应的容器需要具有正确的配置以进行安装传播。由于潜在的隐私和安全问题,我们无法通过变异许可Webhook修改配置。
注射配置很麻烦。更糟糕的是,在配置能够注入故障之后,我们不得不创建一个新的Pod。
程序运行时,我们无法撤出ChaosFS。即使没有注入故障或错误,性能也会受到很大影响。
在没有变异的入网钩的情况下如何破解这些顽强的坚果呢?让我们回过头来思考一下我们为什么使用变异接纳Webhook添加运行ChaosFS的容器的原因。我们这样做是为了将文件系统挂载到目标容器。
实际上,还有另一种解决方案。除了将容器添加到Pod中之外,我们可以首先使用setns Linux系统调用来修改当前进程的名称空间,然后使用mount调用将ChaosFS挂载到目标容器。假设要注入的文件系统是/ mnt。新的注入过程如下:
在当前进程中使用setns输入目标容器的mnt名称空间。
该过程完成后,目标容器将通过ChaosFS打开,读取和写入/ mnt中的文件。这样,可以更轻松地注入延迟或故障。但是,仍然有两个问题要回答:
假设在打开文件时我们无法卸载文件系统,您如何恢复该过程?
ptrace解决了以上两个问题。我们可以使用ptrace在运行时替换打开的文件描述符(FD),并替换当前的工作目录(CWD)和mmap。
ptrace是一个功能强大的工具,可使目标进程(跟踪)运行任何系统调用或二进制程序。为了使Tracee运行程序,ptrace修改了RIP指向目标进程的地址,并添加了int3指令来触发断点。当二进制程序停止时,我们需要恢复寄存器和内存。
在x86_64体系结构中,RIP寄存器(也称为指令指针)始终指向运行下一条指令的内存地址。要将程序加载到目标进程的内存空间中:
将二进制程序写入新分配的内存,并使RIP寄存器指向它。
最佳实践是,我们经常用process_vm_writev替换ptrace POKE_TEXT写入,因为如果要写入的数据量很大,process_vm_writev的执行效率会更高。
使用ptrace,我们可以创建一个进程来替换其自己的FD。现在,我们只需要一种方法即可进行替换。此方法是dup2系统调用。
dup2函数的签名是int dup2(int oldfd,int newfd);。它用于创建旧FD(oldfd)的副本。该副本的FD号为newfd。如果newfd已经对应于已打开文件的FD,则已打开文件上的FD将自动关闭。
例如,当前进程打开FD为1的/ var / run / __ chaosfs__test __ / a。要用/ var / run / test / a替换此打开的文件,此过程将执行以下操作:
使用fcntl系统调用获取/ var / run / __ chaosfs__test __ / a的OFlags(开放系统调用使用的参数,例如O_WRONLY)。
使用open系统调用使用相同的OFlags打开/ var / run / test / a。假设FD为2。
使用dup2(2,1)用新打开的FD 2替换/ var / run / __ chaosfs__test __ / a的FD 1。
该过程完成后,当前过程的FD 1指向/ var / run / test / a。为了注入故障,对目标文件的所有后续操作都将通过用户空间中的文件系统(FUSE)。 FUSE是Unix和类似Unix的计算机操作系统的软件接口,它使非特权用户无需编辑内核代码即可创建自己的文件系统。
ptrace和dup2的组合功能使跟踪程序可以使跟踪程序自己替换打开的FD。现在,我们需要编写一个二进制程序并使目标进程运行它:
当目标进程使用克隆函数创建线程时,将传递CLONE_FILES参数。
因此,Chaos Mesh仅替换线程组中第一个线程的FD。
根据以上两节和syscall指令的用法编写一段汇编代码。这是汇编代码的示例。
使用汇编程序将代码转换为二进制程序。我们使用dynasm-rs作为汇编器。
使用ptrace使目标进程运行该程序。在程序运行时,将在运行时替换FD。
在此图中,每条水平线对应于一条沿箭头方向延伸的螺纹。挂载/挂载文件系统和替换FD任务是按顺序精心安排的。考虑到上述过程,这种安排很有意义。
我已经讨论了如何实现故障注入以在运行时模拟I / O故障(请参阅chaos-mesh / toda)。 但是,当前的实现还远远不够完美: Chaos Mesh不会立即确定文件系统是否已成功安装。 仅在一秒钟后才这样做。 如果您对Chaos Mesh感兴趣并希望帮助我们改进它,欢迎您加入我们的Slack频道,或将拉取请求或问题提交到我们的GitHub存储库。 这是有关混沌网格实现系列的第一篇文章。 如果您想了解如何实现其他类型的故障注入,请继续关注。