目录
ChaosBlade 是阿里开源的混沌工程品牌,包含 chaosblade 工具和 chaosblade-box 平台等项目。ChaosBlade 按照混沌工程的思想,将故障抽象成了一个个实验,并按照科学实验的方法对其过程进行组织。chaosblade 工具支持了大量的故障场景和丰富的命令参数,能够很方便地对常见故障场景进行实验。
相比于 CPU 使用率飙升等基础设施相关故障导致的系统不稳定,我们的关注点目前更加集中在:系统所依赖的两方或三方服务故障导致的不稳定情况。这些故障基本都可以使用修改服务代码逻辑的方式实现,也就是 chaosblade 中 JVM 实验部分所涵盖的内容。所以,以下主要介绍 JVM 实验场景。
(说明:以下代码块中 $ 开头的是需要输入的命令;# 开头的文字是注释;正常无开头标识的为命令返回)
操作人员需要提前申请好与线上服务相同的权限,我们这里通常就是要演练的那台机器上的 work 权限。
环境配置
chaosblade 工具是开箱即用的,只需要下载并解压缩其 release 文件,就可以调用其中的可执行文件来进行故障注入。
# 1) 创建并切换到放置chaosblade的目录
$ mkdir -p /home/work/chaos/bin && cd /home/work/chaos/bin
# 2) 下载release文件
$ curl https://chaosblade.oss-cn-hangzhou.aliyuncs.com/agent/github/1.2.0/chaosblade-1.2.0-linux-amd64.tar.gz -o chaosblade-1.2.0-linux-amd64.tar.gz
# 3) 解压缩
$ tar -zxvf chaosblade-1.2.0-linux-amd64.tar.gz && rm chaosblade-1.2.0-linux-amd64.tar.gz
# 4) 给当前会话新增blade命令,下文都将使用该命令
$ alias blade="/home/work/chaos/bin/chaosblade-1.2.0/blade"
JVM 场景的实验是通过 Java agent 技术来实现的,所以在实验前需要首先将 chaosblade 的 agent 挂载到实验对象的进程中(更多参数请参考 blade prepare jvm):
# 1) 以 test-api 服务为例,拿到进程号。当前例子中进程号为 32676
$ ps -aux | grep test-api
work 32676 4.6 6.7 24081412 8944200 ? Sl 6月02 463:07 /home/work/bin/test-api/java/bin/java *** -server test-api start
# 2) 挂载 java agent (挂载过程需要等十几秒钟)
# -j 参数为服务下的 Java 目录(一般为 /home/work/bin/service-name/java )
# --pid 参数为上一步得到的进程号
$ blade prepare jvm -j /home/work/bin/test-api/java/ --pid 32676
{"code":200,"success":true,"result":"5ee14b7d18d895fd"}
# 返回code为200则说明挂载成功,请记录result对应的实验对象id:5ee14b7d18d895fd,之后会用于实验卸载
挂载成功后,就可以进行实验了。但是一定要注意,所有的实验都结束后,一定要将 agent 卸载掉,具体操作方式见第 7 节。agent 挂载成功后可以进行任意多次第 4, 5, 6 节中描述的实验,各个实验也可以同时进行,但要在卸载 agent 之前将每个创建成功的实验都销毁掉。
# 1) 对一个方法注入8秒的延时
# --time 为延时时长,单位是毫秒
# --classname 为该方法所在类的全限定名
# --methodename 为该方法的方法名
# --pid 为之前挂载好 agent 的进程号
$ blade create jvm delay --time 8000 --classname=com...ChargeController --methodname=payNotify --pid 32676
{"code":200,"success":true,"result":"d0e4be6ee34eab54"}
# 返回code为200则说明注入成功
# 2) 销毁该实验,撤销注入的延时,参数d0e4be6ee34eab54是步骤1)中返回的实验对象Id
$ blade destroy d0e4be6ee34eab54
{"code":200,"success":true,"result":{"target":"jvm","action":"delay","flags":{"classname":"com...ChargeController","methodname":"payNotify","pid":"32676","time":"8000"}}}
# 返回code为200则说明成功销毁了实验
除以上简单案例外,还可以设定影响的请求条数、请求百分比等,具体请参考 blade create jvm delay 。
chaosblade 可直接修改方法的返回为指定值,更多参数见 blade create jvm return 。
# 1) 修改方法的返回值,将该方法的所有调用返回值都修改为false
# 方法的返回值类型需要是基本类型,value参数值的类型需要和方法返回值类型相匹配
# value可以为null,这种情况下不遵从上述约束
# --value 指定方法的固定返回值
$ blade create jvm return --value false --classname com...NotifyManager --methodname verifyNotifyInfo --pid 32676
{"code":200,"success":true,"result":"b847f4d9f8f1cb1f"}
# 2) 销毁该实验
$ blade d b847f4d9f8f1cb1f
{"code":200,"success":true,"result":{"target":"jvm","action":"return","flags":{"classname":"com...NotifyManager","methodname":"verifyNotifyInfo","pid":"32676"}}}
blade create jvm throwCustomException 可以让对特定方法的调用直接抛出一个异常
# 1) 让一个方法抛出自定义的异常
# --exception 参数为要抛出异常的全限定名,必须继承 java.lang.Exception 或 # java.lang.Exception 本身
# --exception-mesage 参数为异常所带的自定义信息
$ blade create jvm throwCustomException --exception java.lang.Exception --exception-message this-is-a-mocked-exception --classname com...NotifyManager --methodname verifyNotifyInfo --pid 32676
{"code":200,"success":true,"result":"06fc1ce09c721f90"}
# 2) 销毁该实验
$ blade d 06fc1ce09c721f90
{"code":200,"success":true,"result":{"target":"jvm","action":"throwCustomException","flags":{"classname":"com...NotifyManager","exception":"java...Exception","exception-message":"this-is-a-mocked-exception","methodname":"verifyNotifyInfo","pid":"32676"}}}
chaosblade 也支持用 Java 或 Groovy 脚本自行实现一个复杂的故障场景,如针对特定用户或商户的过滤等。脚本内容需要符合下文中的规范,更多内容参见 blade create jvm script 。
# 1) 用自定义脚本替换特定方法的实现内容
# --script-file 自定义脚本文件所在的绝对路径,脚本书写规范见下文
# --debug (无参数)将 chaosblade 的日志级别设定为 debug,打印更详细的日志
$ blade create jvm script --classname com...ChargeController --methodname Notify --script-file /home/work/niwanjia/Script.java --pid 32676 --debug
{"code":200,"success":true,"result":"0454edf1f266789d"}
# 2) 销毁该实验
$ blade d 0454edf1f266789d
{"code":200,"success":true,"result":{"target":"jvm","action":"script","flags":{"classname":"com...ChargeController","debug":"true","methodname":"Notify","pid":"32676","script-file":"/home/work/niwanjia/Script.java"}}}
自定义脚本的规范:
上面例子中使用的脚本内容:
package com.anywhere.you.want;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Map;
import net.paoding.rose.web.Invocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Script {
// 该脚本将接收到的通知内容直接丢弃掉
public Object run(Map<String, Object> params) {
Logger log = LoggerFactory.getLogger(Script.class);
Invocation inv = (Invocation) params.get("1");
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(inv.getRequest().getInputStream()))) {
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
log.info("drop notify. content: {}", sb.toString());
return "@SUCCESS";
} catch (Exception e) {
log.error("mock notify error: {}", e.getMessage(), e);
return "@error";
}
}
}
JVM 实验结束后,需要卸载之前加载到目标进程上的 agent。请在卸载 agent 前确认已将所有实验都撤销完毕,以免影响线上业务。
# 1) 如果忘了加载 agent 时返回的实验对象 id,可以通过 status 命令查看
# --type 为 prepare 或者 create
$ blade status --type prepare
{
"code": 200,
"success": true,
"result": [
{
"Uid": "5ee14b7d18d895fd",
"ProgramType": "jvm",
"Process": "",
"Port": "17758",
"Pid": "32676",
"Status": "Running",
"Error": "",
"CreateTime": "2021-06-09T15:31:32.449179887+08:00",
"UpdateTime": "2021-06-09T15:31:45.729185574+08:00"
}
]
}
# 2) 卸载 agent,参数 5ee14b7d18d895fd 为之前 prepare 命令返回的实验对象 Id
$ blade r 5ee14b7d18d895fd
{"code":200,"success":true,"result":"success"}
1) 参数等的错误通常会直接在命令的响应中体现,如:
flag needs an argument: --pid
# 或者
required flag(s) "methodname" not set
2) 如果响应中 code 不是 200,其后的 result 中就会有错误描述,基本上可定位问题,如:
{"code":406,"success":false,"error":"the experiment exists"}
3) 创建实验时响应Code 为 200,但程序表现与预期不符,就需要去 chaosblade 的日志中找原因了。日志的位置为 ~/logs/chaosblade/chaosblade.log ,如:
2021-06-09 17:01:49 INFO Match rule: {"target":"jvm","matcher":{"matchers":{"classname":"com...ChargeController","methodname":"wxpayNotify"}},"action":{"name":"script"},"actionName":"script"}
2021-06-09 17:01:49 WARN inject exception
com.alibaba.chaosblade.exec.plugin.jvm.script.base.ScriptException: Compilation failed.
Source: /com/taobao/csp/monkeyking/script/java/source/Script.java
Line 24: cannot find symbol
symbol: class ChargeController
location: class com.taobao.csp.monkeyking.script.java.source.Script
1 package com.taobao.csp.monkeyking.script.java.source;
2 import com.xiaomi.upay.common.utils.ErrorCounter;
*** 略 ***
22 return "@SUCCESS";
23 } catch (Exception e) {
24 ErrorCounter.countError(ChargeController.class);
25 log.error(e.getMessage(), e);
26 return "@error";
27 }
28 }
29 }
chaosblade 的 server 命令可以启动其内置的服务器,从而将其混沌实验能力通过http接口暴露出来。我们就获得了通过脚本远程创建混沌实验的能力,同时也可以利用该接口同时控制多台机器展开实验。利用 server 命令,增加权限管理等功能后,也可以搭建简单混沌实验平台。
单机案例:
# 1) 在实验目标机器上启动 server
# --port 参数为 http 服务监听的接口
[work@myrboot ~]$ blade server start --port 19526
success, listening on :19526
#———————————————— 以下命令都在另一台机器执行 ——————————————————
# 2) 通过 http 接口,远程启动一个实验
# cmd 参数的内容与本地命令行相同
work@myrboot-PC:~$ curl "http://myrboot:19526/chaosblade?cmd=prepare%20jvm%20--pid%2012984"
{"code":200,"success":true,"result":"cf0683a7be4a506e"}
# 3) 通过 http 接口,远程关闭实验
work@myrboot-PC:~$ curl "http://myrboot:19526/chaosblade?cmd=revoke%20cf0683a7be4a506e"
{"code":200,"success":true,"result":"success"}
# 4) 停止 server
work@myrboot-PC:~$ curl "http://myrboot:19526/chaosblade?cmd=server%20stop"
curl: (52) Empty reply from server
以上代码块中的多个 http 请求可以通过预先编辑好的脚本来执行,这就达到了半自动执行混沌实验的目标。同时在多台机器中启动 chaosblade server,就可以通过脚本操控多台机器同时执行混沌实验。
调用多台机器的简单 shell 脚本:
#!/bin/bash
domains[0]="myrboot"
domains[1]="myrboot1"
set -x
for domain in ${domains[@]};
do
curl "http://${domain}:19526/chaosblade?cmd=prepare%20jvm%20--pid%2012984"
done
set +x
实验完成后请务必停止所有的 chaosblade server,因为该接口没有任何安全认证,并且提供了远程代码注入的能力,非常危险!