前言

还是想着写一篇技术相关的文章吧,不然显得我的博客完全没有意义了
这次简单介绍一下混沌工程相关的 ChaosBlade 工具,因为我也是刚接触不久,希望能帮助到需要入门的同学,也请大佬在评论里指点赐教。

混沌工程

什么是混沌工程呢?美团技术博客最近刚好有一篇相关的文章:超大规模数据库集群保稳系列之二:数据库攻防演练建设实践。其中是这么介绍的:

简单而言,混沌工程是在系统上进行实验的技术手段,目的是建立对系统抵御生产环境中失控条件的能力以及信心。这主要体现在两个方面,从系统角度来讲,混沌工程可以提升我们架构的容错能力和韧性,降低故障发生率和复发率,提升系统用户在使用时的体验;从人员角度来讲,通过混沌工程我们可以加深对架构的理解,在处理故障时能提高整体的应急效率,并且能主动去探究系统中潜在的一些问题。

我的理解就是简单来说,用来提前做故障演练发现隐藏问题的。所以我们需要这么一个工具来实现这件事情,因此我们可以选择ChaosBlade。

ChaosBlade

介绍

ChaosBlade 是阿里巴巴开源的一款遵循混沌工程原理和混沌实验模型的实验注入工具,帮助企业提升分布式系统的容错能力,并且在企业上云或往云原生系统迁移过程中业务连续性保障。

它支持注入系统资源(仅Linux系统)和Java应用级别的异常。
系统资源异常包括:
• 硬件(CPU负载、内存负载、磁盘填充、磁盘IO负载)
• 文件(文件新增、文件追加、文件移动、文件删除、文件权限变更)
• 网络(网络延迟、网络丢包、网络DNS异常、网络占用)
• 进程(进程挂起、进程杀死)
Java应用异常包括:
• OOM(内存溢出)
• 延迟(方法调用延迟、HTTP请求延迟、Dubbo rpc调用延迟等)
• 抛异常(方法调用异常、HTTP请求异常、Dubbo rpc调用异常等)
• 返回值篡改(基础类型和 null)
• 执行自定义脚本(在注入方法的前或后执行自定义脚本)

更多的介绍可以看中文版介绍文档:ChaosBlade: 一个简单易用且功能强大的混沌实验实施工具
项目结构图

使用

但我们这是入门向文章,所以主要还是面向使用。使用 ChaosBlade 主要通过本体命令行的方式,或者 ChaosBlade-Box 网页端进行操作。如果是 k8s 上部署的话也可以通过 operator 使用 yaml 文件来进行实验。我这里主要使用 ChaosBlade-Box,但是根本上是相通的,因为实验的参数配置都是一样的。而且目前不管是界面还是文档其实都不完善,界面上很多参数其实都没有具体说明,需要去看命令行的文档里的参数配置才知道具体用途,另外我也会把遇到的一些坑做一些说明。

官方文档

首先我先贴上官方文档,有两个:
ChaosBlade 整体介绍 | ChaosBlade
README | chaosblade-help-zh-CN

不过当中很多内容是相同的,主要要看每个实验的配置参数部分。

安装 ChaosBlade-Box

首先看你是要在 k8s 集群中还是机器上安装:

1、如果是 k8s 中,首先用 helm 安装一个 chaosblade-operator

helm repo add chaosblade-io https://chaosblade-io.github.io/charts
helm install chaosblade chaosblade-io/chaosblade-operator --namespace chaosblade

然后安装 box,下载 Github Release 页里提供的 tgz 包,也用 helm 安装:

# 下载(用 1.0.3 而不是 1.0.4 的原因是 github 上发布了 1.0.4 但是作者没把镜像传到 docker hub ……)
wget https://github.com/chaosblade-io/chaosblade-box/releases/download/v1.0.3/chaosblade-box-1.0.3.tgz

# 安装
helm install chaosblade-box chaosblade-box-1.0.3.tgz --namespace chaosblade --set spring.datasource.password=数据库密码

其中 namespace 参数是你要安装的命名空间,记得先创建好。后面的参数是安装时,同时会部署一个 mysql 5.6 的 deployment,你需要为它的 root 账号设置密码。

2、如果是在机器里安装,首先确保你有 java 1.8 的运行环境,以及 mysql。注意 mysql 有要求!如果是 5.7 直接使用即可,但更高版本必须关闭 ONLY_FULL_GROUP_BY 设置,否则后面使用时页面上会报错。
然后下载 Github Release 页里提供的 jar 包:下载链接,接着用命令在后台启动

nohup java -Duser.timezone=Asia/Shanghai -jar chaosblade-box-1.0.4.jar --spring.datasource.url="jdbc:mysql://数据库IP:3306/chaosblade?characterEncoding=utf8&useSSL=false" --spring.datasource.username=数据库用户名 --spring.datasource.password=数据库密码 --chaos.server.domain=服务器地址:端口 > chaosblade-box.log 2>&1 &

记得替换命令中的参数。

然后访问对应的地址就能看到 ChaosBlade-Box 的页面了,账号在页面上注册一个就好,登录进入控制台。
界面

安装探针(Agent)

ChaosBlade 要想在某台机子上真正进行故障注入,就需要在那台机子上有 Agent,因此我们需要进行安装。在探针管理页面分别有介绍 k8s 和 主机情况下的两种安装方法。记得版本手动改到 1.0.3,否则可能出现 Agent 连不上管理端的情况。另外其中一串 hash 值不能改,它是用来标识 Agent 连接到你的账户上的,如果管理端登录了另一个账号,也是不能管理这些 Agent 的。k8s 的好处在于集群中部署后,那么所有节点都能进行注入了,但主机的话必须每台实验的机器上都要独立安装。

开始实验

点击左侧演练场景便可以创建实验。比较简单的实验,我这里就不细讲了。很多界面上写的参数介绍并不详细,请查看上面的使用文档进行填写!!

一些坑

我这里讲一些可能会是坑的点:
1、有一些参数是几选一填写的,比如 k8s 中确定包含在实验范围中的 pod,你可以给 name 或者给 labels,界面上显示都是非必填项,但是如果你都不填,实验开始会报错!
2、如果是 k8s 中实验,我们配置 yaml 文件里标签写的是 app:xxx,那界面上参数要填 app=xxx。
3、有的参数是标志位,相当于命令行中的 -xxx 参数,那么这种参数在界面上留空就是不启用,写 true 就是启用。比如 dubbo 延迟实验里如果实验的是消费者,那么 consumer 这个参数填 true,provider留空。
4、如果是进程相关的实验,要么提供 pid,要么提供 process 关键字。关键字是通过 ps -ef | grep {process填写的值} 来搜索要注入的进程,如果存在多个满足的进程会报错。
5、文件追加实验好像有问题,我就没使用成功过。
6、内存负载和Java的 OOM 实验比较考验参数配置,写的量少了要达到高内存占用很慢,如果写的量大了很容易一下被系统杀掉。
7、文档没说的是,Java 自定义脚本场景中,除了方法输入参数之外,还可以通过 "target" 这个 key 拿到调用注入方法的对象。如果启用 after 参数的话,还可以通过 "return" 这个 key 拿到方法原本的返回值。

Java 自定义脚本

我想着重讲的是 Java 自定义脚本这个实验,因为它的自由度非常之高,因此我们可以拿它实现非常多的故障场景。
首先介绍一下基本参数:

参数名类型说明
classnamestring指定类名,必须是实现类,带全包名,例如 com.xxx.xxx.XController (必填项)
methodnamestring指定方法名,注意相同方法名的方法都会被注入相同故障 (必填项)
afterbool方法执行完成返回前注入故障,比如修改复杂的返回对象
script-contentstring脚本内容,和file二选一
script-filestring脚本文件,文件绝对路径
script-namestring脚本名称,日志记录用,可不填写
script-typestring脚本类型,取值为 java 或 groovy,默认为 java
effect-countstring影响的请求条数
effect-percentstring影响的请求百分比

注意官方文档(命令行版)里 script-content 是要填写 base64 编码后的问题,而 box 版不需要,直接把脚本代码复制进去即可。

假设有一个场景,我想测试如果 HTTP 调用的第三方 API 挂了怎么办?我不能真的去把人家的 API 干掉。因此我们可以选择对自己的 HTTP 客户端方法做注入,篡改返回结果,模拟 API 挂了的场景。假设我用的是 okhttp3 库,设定after参数,脚本如下:

import java.util.Map;
import okhttp3.Call;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
* @author CosineG
*/
public class ChaosController {
    public Object run(Map<String, Object> params) {
        // "target"对应的value为调用原方法的实例
        Object target = params.get("target");
        if (target instanceof Call) {
            Call call = (Call)target;
            // 判断请求地址是否为所需篡改的API 其他请求保持正常
            if (call.request().url().url().toString().contains("/api/xxx")) {
                // "return"对应的value为原方法的原返回值
                // 拷贝一份和原Response基本相同的Response,篡改后返回
                Response response = (Response) params.get("return");
                MediaType mediaType = MediaType.parse("text/html; charset=utf-8");
                
                // 篡改一份404响应
                Response.Builder builder = new Response.Builder()
                    .request(response.request())
                    .protocol(response.protocol())
                    .code(404)
                    .headers(response.headers())
                    .handshake(response.handshake())
                    .message("Not Found")
                    .receivedResponseAtMillis(response.receivedResponseAtMillis())
                    .sentRequestAtMillis(response.sentRequestAtMillis())
                    .body(ResponseBody.create(mediaType, "<html><body>404 Not Found</body></html>"));
                return builder.build();
            }
        }
        // return null时注入方法不修改返回值
        // 不符合条件的不修改返回值
        return null;
    }
}

这样当指定 Java 进程的任何代码通过 okhttp3 库调用指定地址的 API,都会得到一个 404 的 html 响应,可以用来检查程序是否有相对完善的应对措施。同理,也可以注入 es 或其他客户端库,实现错误返回、延时超时、异常报错等情况。

但比较蛋疼的就是脚本在返回 null 的时候代表返回原始返回值,我现在还不知道怎么通过脚本返回 null 值。

目前我了解的大概就是这些了,后续学习了新的技巧的话还会再更新的(大概吧

如果觉得我的文章对你有用,请随意赞赏