登录 |  注册
首页 >  编程测试工具 >  测试工具实践学习笔记 >  阿里开源混沌工程(Chaos Engineering)是什么?

阿里开源混沌工程(Chaos Engineering)是什么?

混沌工程.jpg

工程师团队最不愿碰到的便是大半夜被电话叫醒,开始紧张地查验问题,处理故障以及恢复服务。也许就是因为睡前的一个很小的变更,因某种未预料到的场景,引起蝴蝶效应,导致大面积的系统混乱、故障和服务中断,对客户的业务造成影响。特别是近几年,尽管有充分的监控告警和故障处理流程,这样的新闻在 IT 行业仍时有耳闻。问题的症结便在于,对投入生产的复杂系统有多少信心。监控告警和故障处理都是事后的响应与被动的应对,那有没有可能提前发现这些复杂系统的缺陷呢?

混沌工程.jpg

混沌工程在分布式系统上进行由经验指导的受控实验,观察系统行为并发现系统缺陷,以建立对系统在规模增大时因意外条件引发混乱的能力和信心。

混沌工程和传统测试(故障注入FIT、故障测试)在关注点和工具集上都有很大的重叠。譬如,在Netflix的很多混沌工程实验研究的对象都是基于故障注入来引入的。混沌工程和这些传统测试方法的主要区别在于:混沌工程是发现新信息的实践过程,而故障注入则是对一个特定的条件、变量的验证方法。

当你希望探究复杂系统如何应对异常时,对系统中的服务注入通信故障(如超时、错误等)不失为一种很好的方法。但有时我们希望探究更多其他的非故障类的场景,如流量激增、资源竞争条件、拜占庭故障(例如性能差或有异常的节点发出有错误的响应、异常的行为、对调用者随机性的返回不同的响应,等等)、非计划中的或非正常组合的消息处理等等。因为如果一个面向公众用户的网站突然收到激增的流量,从而产生更多的收入时我们很难称之为故障,但我们仍然需要探究清楚系统在这种情况下的影响。

和故障注入类似,故障测试方法通过对预先设想到的可以破坏系统的点进行测试,但是并没能去探究上述这类更广阔领域里的、不可预知的、但很可能发生的事情。

混沌工程实验的可能性是无限的,根据不同的分布式系统架构和不同的核心业务价值,实验可以千变万化。下面是部分混沌实验的输入示例:

  1. 模拟整个云服务区域或整个数据中心故障;

  2. 跨多实例删除部分 Kafka 主题来重现生产环境中发生过的问题;

  3. 挑选一个时间段,和针对一部分流量,对其涉及的服务间调用注入一些特定的延时;

  4. 方法级别的混乱(运行时注入):让方法随机抛出各种异常;

  5. 在代码中插入一些指令可以允许在这些指令之前运行故障注入;

  6. 强制系统节点间的时间不同步;

  7. 在驱动程序中执行模拟 I/O 错误的程序;

  8. 让某个 Elasticsearch 集群 CPU 超负荷。

阿里巴巴混沌工程工具ChaosBlade的使用案例

下面介绍混沌工程工具ChaosBlade中的CPU满载的故障模拟的实现方式。之前看的是Netflix的Simain Army中的实现,看上去一目了然,通过java的形式调用一下模拟脚本,脚本的内容如下:

#!/bin/bash
cat << EOF > /tmp/infiniteburn.sh
#!/bin/bash
while true;
    do openssl speed;
done
EOF
for i in {1..32}
do
    nohup /bin/bash /tmp/infiniteburn.sh &
done

通过调用openssl speed来模拟CPU满负荷运作,这里的方式还比较粗暴,直接开了32个线程来执行openssl speed这种CPU密集型计算程序。如果CPU的内核数大于32,那么就需要修改这里的硬编码脚本了。

通过翻看阿里巴巴混沌工程工具ChaosBlade的代码(golang project),发现它对于CPU的蹂躏方式要细腻很多。比如CPU的内核个数使用runtime.NumCPU()来获取。当然了还会有一些其它的附加细节,这个下面会详述。

我们先来简单的了解一下ChaosBlade对于这一块的实现布局。在ChaosBlade中,我们实现故障(类似cpu 100%、I/O 100%、网络中断等)注入的入口是通过blade命令。例如:使得CPU满载负荷,那么可以使用./blade create cpu fullload来实现。

其实ChaosBlade中的blade命令只是采用Cobra封装的CLI入口,其内部实现是调用bin/目录下的chaos_burncpu程序。

ChaosBlades(releases版)的下载地址为:https://github.com/chaosblade-io/chaosblade/releases。解压之后的目录结构为:

hidden@hidden:~/chaos/chaosblade-0.2.0$ tree
.
├── bin
│   ├── chaos_burncpu
│   ├── chaos_burnio
│   ├── chaos_changedns
│   ├── chaos_delaynetwork
│   ├── chaos_dropnetwork
│   ├── chaos_filldisk
│   ├── chaos_killprocess
│   ├── chaos_lossnetwork
│   ├── chaos_stopprocess
│   ├── cplus-chaosblade.spec.yaml
│   ├── jvm.spec.yaml
│   └── tools.jar
├── blade
├── chaosblade.dat
└── lib --<snip>

可以看到bin/目录下处理chaos_burncpu之外还有很多chaos_***形式的程序,比如chaos_burnio是让I/O满载负荷。这些chaos_***形式的程序的使用方式都是大同小异的。

使用指南

我们这里不使用ChaosBlade提供的blade命令,因为这只是外部的一层封装,使用这个无法使我们能够透彻的理解内部的实现。我们这里使用bin/chaos_burncpu来演示一下具体的用法。调用方式如下:

bin/chaos_burncpu --start

这里命令可以让当前机器的CPU满载负荷。取消CPU满载负荷可以使用如下的命令:

bin/chaos_burncpu --stop

bin/chaos_burncpu命令还可以通过--cpu-count来指定CPU中需要满载负荷的内核个数,示例如下:

# 指定需要满载负荷的CPU的内核个数为4
bin/chaos_burncpu --start --cpu-count 4

假设现在测试所使用的机器的cpu共有4个内核,那么我们让其中3个内核满载,效果如何呢?首先运行sar -u 1 100命令来监测cpu的使用情况,然后运行:

bin/chaos_burncpu --start --cpu-count 3

可以在持续运行sar命令的shell终端中看到CPU的%idle数值变成了25%左右:

02:21:35 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
02:21:44 PM     all     73.95      0.00      1.24      0.00      0.00     24.81

我们还可以指定让某个CPU内核满载,比如下面的示例中让内核编号为1的满载:

bin/chaos_burncpu --start --cpu-list 1

sar命令中还可以通过—P参数查看指定编号的内核的使用情况,比如使用sar -u -P 1 1 100来指定编号为1的CPU内核的使用情况:

hidden@hidden:~$ sar -u -P 1 1 100
Linux 4.4.0-33.bm.1-amd64 (n224-008-172)     08/15/2019  _x86_64_    (4 CPU)
02:45:19 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
02:45:20 PM       1     98.00      0.00      2.00      0.00      0.00      0.00
02:45:21 PM       1     98.99      0.00      1.01      0.00      0.00      0.00

可以看到这个内核已经满载。至于怎么实现挂载单个CPU内核的在下面会有详细的介绍。

我们再来通过-P 0来看一下编号为0的CPU内核的使用情况:

02:47:32 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
02:47:33 PM       0      1.00      0.00      2.00      0.00      0.00     97.00
02:47:34 PM       0      0.00      0.00      0.00      0.00      0.00    100.00

可以看到这个内核还是处于空闲状态(%idle接近100%)。

原理

CPU满载

chaos_burncpu中实现CPU满载负荷的逻辑其实相当简单,通过程序让CPU一直运作即可。代码如下:

func burnCpu() {
    runtime.GOMAXPROCS(cpuCount)
    for i := 0; i < cpuCount; i++ {
        go func() {
            for {
                for i := 0; i < 2147483647; i++ {
                }
                runtime.Gosched() //让出CPU时间片
            }
        }()
    }
    select {} // wait forever
}

读者可以自己比较一下这个和Simain Army中的openssl speed的区别。

关闭

关闭CPU满载负荷的过程也比较简单粗暴,总共分为两步:

使用ps -ef | grep … 命令找出chaos_burncpu的pid。

使用kill -9 pid命令干掉它。

指定内核满载

我们在上面就了解到通过--cpu-count可以指定CPU满载的内核个数,通过--cpu-list可以指定内核满载。ChaosBlade相比于Simian Army中的细腻之处也就体现在这里。

--cpu-count的功能很好实现,在上面的func burnCpu()函数中的cpuCount就是--cpu-count所指定的值。

--cpu-list的功能比较复杂,总共分为3步:

第一步:执行nohup bin/chaos_burncpu --nohup --cpu-count 1 --cpu-processor [cpu内核编号] > /dev/null 2>&1 &。假设我们要指定编号为1的内核满载,那么对应的命令即为:nohup bin/chaos_burncpu --nohup --cpu-count 1 --cpu-processor 1 > /dev/null 2>&1 &。其实这个也只是个烟雾弹,实际上还是调用原本的bin/chaos_burncpu --start --cpu-count 1而已,只不过这里多指定了一个cpu-processor的信息。

第二步:执行ps -ef | grep … 命令找出对应的pid。

第三步:将进程pid绑定到编号为cpu-processor的内核上。那么这一步怎么操作呢?我们先来看一下CPU Affinity。

CPU Affinity

基本概念

CPU affinity (亲和力/亲和性)是一种调度属性(scheduler property), 它可以将一个进程"绑定" 到一个或一组CPU上。

将进程与CPU绑定,最直观的好处就是减少cpu之间的cache同步和切换,提高了cpu cache的命中率,提高代码的效率。

从CPU架构上,NUMA拥有独立的本地内存,节点之间可以通过互换模块做连接和信息交互,因此每个CPU可以访问整个系统的内存,但是访问远地内存访问效率大大降低,绑定CPU操作对此类系统运行速度会有较大提升,UMA架构下,多CPU通过系统总线访问存储模块。不难看出,NUMA使用CPU绑定时,每个核心可以更专注地处理一件事情,资源体系被充分使用,减少了同步的损耗。

表示方法

CPU affinity 使用位掩码(bitmask)表示, 每一位都表示一个CPU, 置1表示"绑定"。最低位表示第一个逻辑CPU, 最高位表示最后一个逻辑CPU。CPU affinity典型的表示方法是使用16进制,具体如下:

0x00000001
    is processor #0
0x00000003
    is processors #0 and #1
0xFFFFFFFF
    is all processors (#0 through #31)

taskset命令

taskset命名用于获取或者设定CPU affinity。

# 命令行形式
Usage: taskset [options] [mask | cpu-list] [pid|cmd [args...]]
PARAMETER
    mask : cpu亲和性,当没有-c选项时, 其值前无论有没有0x标记都是16进制的,
        当有-c选项时,其值是十进制的.
    command : 命令或者可执行程序
    arg : command的参数
    pid : 进程ID,可以通过ps/top/pidof等命令获取

OPTIONS

    -a, --all-tasks (旧版本中没有这个选项)

        这个选项涉及到了linux中TID的概念,他会将一个进程中所有的TID都执行一次CPU亲和性设置.

        TID就是Thread ID,他和POSIX中pthread_t表示的线程ID完全不是同一个东西.

        Linux中的POSIX线程库实现的线程其实也是一个进程(LWP),这个TID就是这个线程的真实PID.

       -p, --pid

              操作已存在的PID,而不是加载一个新的程序

       -c, --cpu-list

              声明CPU的亲和力使用数字表示而不是用位掩码表示. 例如 0,5,7,9-11.

       -h, --help

              display usage information and exit

       -V, --version

              output version information and exit

USAGE

    1) 使用指定的CPU亲和性运行一个新程序

      taskset [-c] mask command [arg]...

        举例:使用CPU0运行ls命令显示/etc/init.d下的所有内容 

          taskset -c 0 ls -al /etc/init.d/

    2) 显示已经运行的进程的CPU亲和性

      taskset -p pid

        举例:查看init进程(PID=1)的CPU亲和性

          taskset -p 1

    3) 改变已经运行进程的CPU亲和力

        taskset -p[c] mask pid

        举例:打开2个终端,在第一个终端运行top命令,第二个终端中

          首先运行:[~]# ps -eo pid,args,psr | grep top #获取top命令的pid和其所运行的CPU号

          其次运行:[~]# taskset -cp 新的CPU号 pid       #更改top命令运行的CPU号

          最后运行:[~]# ps -eo pid,args,psr | grep top #查看是否更改成功

PERMISSIONS

        一个用户要设定一个进程的CPU亲和性,如果目标进程是该用户的,则可以设置,如果是其他用户的,则会设置失败,提示 Operation not permitted.当然root用户没有任何限制.

        任何用户都可以获取任意一个进程的CPU亲和性.

应用taskset

下面我们就来详细实践一下CPU指定内核满载的过程。

第一步,我们让某个内核满载,这里我们还并未指定哪一个内核(对应前面所说第一步):

bin/chaos_burncpu --start --cpu-count 1

第二步,我们找到这个进程的pid:

hidden@hidden:~$ ps -ef | grep chaos_burncpu
zhuzhon+  572792  490371 99 18:20 pts/0    00:00:14 bin/chaos_burncpu --nohup --cpu-count 1 --cpu-processor 1
zhuzhon+  572860  551590  0 18:20 pts/3    00:00:00 grep chaos_burncpu

此时,我们查看pid=572792的进程的亲和力为f(即二进制的1111,也就是CPU内核编号0-3),也就是说CPU中的4个内核都有可能运行这个满载程序。

hidden@hidden:~$ taskset -p 572792
pid 572792's current affinity mask: f
hidden@hidden:~$ taskset -c -p 572792
pid 572792's current affinity list: 0-3

上面第一步中,指定某个单独的内核满载的实际效果应该时每个内核都会有一定的时间处于满载状态。对此有疑问的同学可以通过sar -u -P [cpu-processor] 1 1000来验证一下。

第三步,我们指定编号为0的内核满负荷:

hidden@hidden:~$ taskset -cp 0 572792
pid 572792's current affinity list: 0-3
pid 572792's new affinity list: 0

此时我们可以通过sar -u -P [cpu-processor] 1 1000命令来检测4个内核的各个使用情况。不出意外的话,内核编号为0的检测结果应该和下面的类似:

hidden@hidden:~$ sar -u -P 0 1 1000
Linux 4.4.0-33.bm.1-amd64 (n224-008-172)     08/15/2019  _x86_64_    (4 CPU)
06:22:08 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
06:38:46 PM       0    100.00      0.00      0.00      0.00      0.00      0.00
06:38:47 PM       0    100.00      0.00      0.00      0.00      0.00      0.00
06:38:48 PM       0    100.00      0.00      0.00      0.00      0.00      0.00

而其他内核的%idle应该都接近在100%。

总结

本文不仅介绍了如何“蹂躏”CPU,还附带了一个知识点就是CPU affinity,程序开发者比机器更懂程序,如果用好它,可以有意想不到的效果。

原文链接: https://www.yukx.com/autotesting/article/details/2066.html 优科学习网阿里开源混沌工程(Chaos Engineering)是什么?

<<上一课程
推荐文章
  • 什么是分支管理在版本控制过程中,使用多条线同时推进多个任务就是分支管理。如下图所示:分支管理的好处 ⒈同时并行推进多个功能开发,提高开发效率 ⒉各个分支在开发过程中,如果某一个分支开发失败,不会对其他分支有任何影响。失败的分支删除重新开始即可。  分支操作允许创建另一路线/方向上开发。我们可以使用这
  • 近期在学习java,推荐使用IntelliJIDEA作为开发工具,然而昨天第一次使用,在切换两个项目的时候出现了问题,如下,项目目录下看不到源码文件了,下面记录了一下处理方式:步骤1:打开项目配置:File-ProjectStructure步骤2:导入项目模块Modules,点击+号,如下图:步骤3
  • 删除该子模块后再新建一个同名的子模块,新建的子模块已经被加入了Maven的IgnoreFiles中,需要取消该勾选。此时,子模块中的pom文件带横线。如下图所示:解决办法:打开File–Settings–Maven–IgnoredFiles;将“√”去掉,刷新maven即可点击Apply,点击OK,
  • 在互联网公司的业务发展过程中,用户增长是永恒的主题,因为没有增长也就没有发展,所以在业务发展的早期产品迭代速度往往是越快越好,总之一句话:“怎么快怎么来”,至于系统建设得是否可以满足未来几年的扩展什么的,往往在用户增长面前都会显得很扯淡,因为慢了可能就死掉了。而当业务发展到一定阶段后,野蛮生长的红利
  • Git    是一种版本控制系统,是一个命令,是一种工具,有点像cmd(命令行工具)。Github 是全球最大的面向开源及私有软件项目的托管平台,免费注册并且可以免费托管开源代码。一个基于git实现在线代码托管的仓库,向互联网开放,企业版要收钱。Gitlab  类似github,也是属于第三方基于G
  • 什么是CI/CD?有的同学认为CI/CD就是Jenkins集成,这其实是一种片面的理解,我们可以使用Jenkins来实现CI/CD,也可以借助其他工具来实现,如GitLabCI/CD,在互联网大厂基本也有自研的CI/CD工具本节带大家完整的介绍一下CI/CD的概念,以及CI/CD在大厂是如何进行落地
学习大纲
 为什么要压测?及压测工具有哪些?
阿里开源混沌工程(Chaos Engineering)是什么?